Compare commits
1 commit
main
...
cloudflare
Author | SHA1 | Date | |
---|---|---|---|
|
325080c543 |
99 changed files with 76 additions and 5118 deletions
3
packages/integrations/cloudflare/.gitignore
vendored
3
packages/integrations/cloudflare/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
||||||
# Astro cloudflare directory mode creates a function directory
|
|
||||||
functions
|
|
||||||
.mf
|
|
File diff suppressed because one or more lines are too long
|
@ -1,399 +1,3 @@
|
||||||
# @astrojs/cloudflare
|
# @astrojs/cloudflare
|
||||||
|
|
||||||
An SSR adapter for use with Cloudflare Pages Functions targets. Write your code in Astro/Javascript and deploy to Cloudflare Pages.
|
The Cloudflare adapter package has moved. Please see [the new repository for the Cloudflare adapter](https://github.com/withastro/adapters/tree/main/packages/cloudflare).
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
Add the Cloudflare adapter to enable SSR in your Astro project with the following `astro add` command. This will install the adapter and make the appropriate changes to your `astro.config.mjs` file in one step.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Using NPM
|
|
||||||
npx astro add cloudflare
|
|
||||||
# Using Yarn
|
|
||||||
yarn astro add cloudflare
|
|
||||||
# Using PNPM
|
|
||||||
pnpm astro add cloudflare
|
|
||||||
```
|
|
||||||
|
|
||||||
If you prefer to install the adapter manually instead, complete the following two steps:
|
|
||||||
|
|
||||||
1. Add the Cloudflare adapter to your project's dependencies using your preferred package manager. If you’re using npm or aren’t sure, run this in the terminal:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install @astrojs/cloudflare
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Add the following to your `astro.config.mjs` file:
|
|
||||||
|
|
||||||
```diff lang="js"
|
|
||||||
// astro.config.mjs
|
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
+ import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
+ output: 'server',
|
|
||||||
+ adapter: cloudflare(),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Options
|
|
||||||
|
|
||||||
### `mode`
|
|
||||||
|
|
||||||
`mode: "advanced" | "directory"`
|
|
||||||
|
|
||||||
default `"advanced"`
|
|
||||||
|
|
||||||
This configuration option defines how your Astro project is deployed to Cloudflare Pages.
|
|
||||||
|
|
||||||
- `advanced` mode picks up the `_worker.js` file in the `dist` folder
|
|
||||||
- `directory` mode picks up the files in the `functions` folder, by default only one `[[path]].js` file is generated
|
|
||||||
|
|
||||||
Switching to directory mode allows you to add additional files manually such as [Cloudflare Pages Plugins](https://developers.cloudflare.com/pages/platform/functions/plugins/), [Cloudflare Pages Middleware](https://developers.cloudflare.com/pages/platform/functions/middleware/) or custom functions using [Cloudflare Pages Functions Routing](https://developers.cloudflare.com/pages/platform/functions/routing/).
|
|
||||||
|
|
||||||
```js
|
|
||||||
// astro.config.mjs
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare({ mode: 'directory' }),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
To compile a separate bundle for each page, set the `functionPerRoute` option in your Cloudflare adapter config. This option requires some manual maintenance of the `functions` folder. Files emitted by Astro will overwrite existing files with identical names in the `functions` folder, so you must choose unique file names for each file you manually add. Additionally, the adapter will never empty the `functions` folder of outdated files, so you must clean up the folder manually when you remove pages.
|
|
||||||
|
|
||||||
```diff lang="js"
|
|
||||||
// astro.config.mjs
|
|
||||||
import {defineConfig} from "astro/config";
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode: 'directory',
|
|
||||||
+ functionPerRoute: true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
This adapter doesn't support the [`edgeMiddleware`](https://docs.astro.build/en/reference/adapter-reference/#edgemiddleware) option.
|
|
||||||
|
|
||||||
### `routes.strategy`
|
|
||||||
|
|
||||||
`routes.strategy: "auto" | "include" | "exclude"`
|
|
||||||
|
|
||||||
default `"auto"`
|
|
||||||
|
|
||||||
Determines how `routes.json` will be generated if no [custom `_routes.json`](#custom-_routesjson) is provided.
|
|
||||||
|
|
||||||
There are three options available:
|
|
||||||
|
|
||||||
- **`"auto"` (default):** Will automatically select the strategy that generates the fewest entries. This should almost always be sufficient, so choose this option unless you have a specific reason not to.
|
|
||||||
|
|
||||||
- **`include`:** Pages and endpoints that are not pre-rendered are listed as `include` entries, telling Cloudflare to invoke these routes as functions. `exclude` entries are only used to resolve conflicts. Usually the best strategy when your website has mostly static pages and only a few dynamic pages or endpoints.
|
|
||||||
|
|
||||||
Example: For `src/pages/index.astro` (static), `src/pages/company.astro` (static), `src/pages/users/faq.astro` (static) and `/src/pages/users/[id].astro` (SSR) this will produce the following `_routes.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"include": [
|
|
||||||
"/_image", // Astro's image endpoint
|
|
||||||
"/users/*" // Dynamic route
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
// Static routes that needs to be exempted from the dynamic wildcard route above
|
|
||||||
"/users/faq/",
|
|
||||||
"/users/faq/index.html"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- **`exclude`:** Pre-rendered pages are listed as `exclude` entries (telling Cloudflare to handle these routes as static assets). Usually the best strategy when your website has mostly dynamic pages or endpoints and only a few static pages.
|
|
||||||
|
|
||||||
Example: For the same pages as in the previous example this will produce the following `_routes.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": 1,
|
|
||||||
"include": [
|
|
||||||
"/*" // Handle everything as function except the routes below
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
// All static assets
|
|
||||||
"/",
|
|
||||||
"/company/",
|
|
||||||
"/index.html",
|
|
||||||
"/users/faq/",
|
|
||||||
"/favicon.png",
|
|
||||||
"/company/index.html",
|
|
||||||
"/users/faq/index.html"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### `routes.include`
|
|
||||||
|
|
||||||
`routes.include: string[]`
|
|
||||||
|
|
||||||
default `[]`
|
|
||||||
|
|
||||||
If you want to use the automatic `_routes.json` generation, but want to include additional routes (e.g. when having custom functions in the `functions` folder), you can use the `routes.include` option to add additional routes to the `include` array.
|
|
||||||
|
|
||||||
### `routes.exclude`
|
|
||||||
|
|
||||||
`routes.exclude: string[]`
|
|
||||||
|
|
||||||
default `[]`
|
|
||||||
|
|
||||||
If you want to use the automatic `_routes.json` generation, but want to exclude additional routes, you can use the `routes.exclude` option to add additional routes to the `exclude` array.
|
|
||||||
|
|
||||||
The following example automatically generates `_routes.json` while including and excluding additional routes. Note that that is only necessary if you have custom functions in the `functions` folder that are not handled by Astro.
|
|
||||||
|
|
||||||
```diff lang="js"
|
|
||||||
// astro.config.mjs
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode: 'directory',
|
|
||||||
+ routes: {
|
|
||||||
+ strategy: 'include',
|
|
||||||
+ include: ['/users/*'], // handled by custom function: functions/users/[id].js
|
|
||||||
+ exclude: ['/users/faq'], // handled by static page: pages/users/faq.astro
|
|
||||||
+ },
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### `wasmModuleImports`
|
|
||||||
|
|
||||||
`wasmModuleImports: boolean`
|
|
||||||
|
|
||||||
default: `false`
|
|
||||||
|
|
||||||
Whether or not to import `.wasm` files [directly as ES modules](https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration) using the `.wasm?module` import syntax.
|
|
||||||
|
|
||||||
Add `wasmModuleImports: true` to `astro.config.mjs` to enable this functionality in both the Cloudflare build and the Astro dev server. Read more about [using Wasm modules](#use-wasm-modules)
|
|
||||||
|
|
||||||
```diff lang="js"
|
|
||||||
// astro.config.mjs
|
|
||||||
import {defineConfig} from "astro/config";
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare({
|
|
||||||
+ wasmModuleImports: true
|
|
||||||
}),
|
|
||||||
output: 'server'
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### `runtime`
|
|
||||||
|
|
||||||
`runtime: "off" | "local"`
|
|
||||||
|
|
||||||
default `"off"`
|
|
||||||
|
|
||||||
Determines whether and how the Cloudflare Runtime is added to `astro dev`.
|
|
||||||
|
|
||||||
The Cloudflare Runtime includes [Cloudflare bindings](https://developers.cloudflare.com/pages/platform/functions/bindings), [environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables), and the [cf object](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties). Read more about [accessing the Cloudflare Runtime](#cloudflare-runtime).
|
|
||||||
|
|
||||||
- `local`: uses bindings mocking and locally static placeholders
|
|
||||||
- `off`: no access to the Cloudflare runtime using `astro dev`. You can alternatively use [Preview with Wrangler](#preview-with-wrangler)
|
|
||||||
|
|
||||||
```diff lang="js"
|
|
||||||
// astro.config.mjs
|
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
output: 'server',
|
|
||||||
adapter: cloudflare({
|
|
||||||
+ runtime: 'local',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cloudflare runtime
|
|
||||||
|
|
||||||
Gives you access to [environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables), and [Cloudflare bindings](https://developers.cloudflare.com/pages/platform/functions/bindings).
|
|
||||||
|
|
||||||
Currently supported bindings:
|
|
||||||
|
|
||||||
- [Cloudflare D1](https://developers.cloudflare.com/d1/)
|
|
||||||
- [Cloudflare R2](https://developers.cloudflare.com/r2/)
|
|
||||||
- [Cloudflare Workers KV](https://developers.cloudflare.com/kv/)
|
|
||||||
- [Cloudflare Durable Objects](https://developers.cloudflare.com/durable-objects/)
|
|
||||||
|
|
||||||
You can access the runtime from Astro components through `Astro.locals` inside any .astro` file.
|
|
||||||
|
|
||||||
```astro
|
|
||||||
---
|
|
||||||
// src/pages/index.astro
|
|
||||||
const runtime = Astro.locals.runtime;
|
|
||||||
---
|
|
||||||
|
|
||||||
<pre>{JSON.stringify(runtime.env)}</pre>
|
|
||||||
```
|
|
||||||
|
|
||||||
You can access the runtime from API endpoints through `context.locals`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// src/pages/api/someFile.js
|
|
||||||
export function GET(context) {
|
|
||||||
const runtime = context.locals.runtime;
|
|
||||||
|
|
||||||
return new Response('Some body');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Typing
|
|
||||||
|
|
||||||
If you have configured `mode: advanced`, you can type the `runtime` object using `AdvancedRuntime`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// src/env.d.ts
|
|
||||||
/// <reference types="astro/client" />
|
|
||||||
|
|
||||||
type KVNamespace = import('@cloudflare/workers-types/experimental').KVNamespace;
|
|
||||||
type ENV = {
|
|
||||||
SERVER_URL: string;
|
|
||||||
KV_BINDING: KVNamespace;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Runtime = import('@astrojs/cloudflare').AdvancedRuntime<ENV>;
|
|
||||||
|
|
||||||
declare namespace App {
|
|
||||||
interface Locals extends Runtime {
|
|
||||||
user: {
|
|
||||||
name: string;
|
|
||||||
surname: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you have configured `mode: directory`, you can type the `runtime` object using `DirectoryRuntime`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// src/env.d.ts
|
|
||||||
/// <reference types="astro/client" />
|
|
||||||
|
|
||||||
type KVNamespace = import('@cloudflare/workers-types/experimental').KVNamespace;
|
|
||||||
type ENV = {
|
|
||||||
SERVER_URL: string;
|
|
||||||
KV_BINDING: KVNamespace;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Runtime = import('@astrojs/cloudflare').DirectoryRuntime<ENV>;
|
|
||||||
|
|
||||||
declare namespace App {
|
|
||||||
interface Locals extends Runtime {
|
|
||||||
user: {
|
|
||||||
name: string;
|
|
||||||
surname: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Platform
|
|
||||||
|
|
||||||
### Headers
|
|
||||||
|
|
||||||
You can attach [custom headers](https://developers.cloudflare.com/pages/platform/headers/) to your responses by adding a `_headers` file in your Astro project's `public/` folder. This file will be copied to your build output directory.
|
|
||||||
|
|
||||||
### Redirects
|
|
||||||
|
|
||||||
You can declare [custom redirects](https://developers.cloudflare.com/pages/platform/redirects/) using Cloudflare Pages. This allows you to redirect requests to a different URL. You can add a `_redirects` file in your Astro project's `public/` folder. This file will be copied to your build output directory.
|
|
||||||
|
|
||||||
### Routes
|
|
||||||
|
|
||||||
You can define which routes are invoking functions and which are static assets, using [Cloudflare routing](https://developers.cloudflare.com/pages/platform/functions/routing/#functions-invocation-routes) via a `_routes.json` file. This file is automatically generated by Astro.
|
|
||||||
|
|
||||||
#### Custom `_routes.json`
|
|
||||||
|
|
||||||
By default, `@astrojs/cloudflare` will generate a `_routes.json` file with `include` and `exclude` rules based on your applications's dynamic and static routes.
|
|
||||||
This will enable Cloudflare to serve files and process static redirects without a function invocation. Creating a custom `_routes.json` will override this automatic optimization. See [Cloudflare's documentation on creating a custom `routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) for more details.
|
|
||||||
|
|
||||||
## Use Wasm modules
|
|
||||||
|
|
||||||
The following is an example of importing a Wasm module that then responds to requests by adding the request's number parameters together.
|
|
||||||
|
|
||||||
```js
|
|
||||||
// pages/add/[a]/[b].js
|
|
||||||
import mod from '../util/add.wasm?module';
|
|
||||||
|
|
||||||
// instantiate ahead of time to share module
|
|
||||||
const addModule: any = new WebAssembly.Instance(mod);
|
|
||||||
|
|
||||||
export async function GET(context) {
|
|
||||||
const a = Number.parseInt(context.params.a);
|
|
||||||
const b = Number.parseInt(context.params.b);
|
|
||||||
return new Response(`${addModule.exports.add(a, b)}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
While this example is trivial, Wasm can be used to accelerate computationally intensive operations which do not involve significant I/O such as embedding an image processing library.
|
|
||||||
|
|
||||||
## Node.js compatibility
|
|
||||||
|
|
||||||
Astro's Cloudflare adapter allows you to use any Node.js runtime API supported by Cloudflare:
|
|
||||||
|
|
||||||
- assert
|
|
||||||
- AsyncLocalStorage
|
|
||||||
- Buffer
|
|
||||||
- Diagnostics Channel
|
|
||||||
- EventEmitter
|
|
||||||
- path
|
|
||||||
- process
|
|
||||||
- Streams
|
|
||||||
- StringDecoder
|
|
||||||
- util
|
|
||||||
|
|
||||||
To use these APIs, your page or endpoint must be server-side rendered (not pre-rendered) and must use the the `import {} from 'node:*'` import syntax.
|
|
||||||
|
|
||||||
```js
|
|
||||||
// pages/api/endpoint.js
|
|
||||||
export const prerender = false;
|
|
||||||
import { Buffer } from 'node:buffer';
|
|
||||||
```
|
|
||||||
|
|
||||||
Additionally, you'll need to enable the Compatibility Flag in Cloudflare. The configuration for this flag may vary based on where you deploy your Astro site. For detailed guidance, please refer to the [Cloudflare documentation on enabling Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs).
|
|
||||||
|
|
||||||
## Preview with Wrangler
|
|
||||||
|
|
||||||
To use [`wrangler`](https://developers.cloudflare.com/workers/wrangler/) to run your application locally, update the preview script:
|
|
||||||
|
|
||||||
```json
|
|
||||||
//package.json
|
|
||||||
"preview": "wrangler pages dev ./dist"
|
|
||||||
```
|
|
||||||
|
|
||||||
[`wrangler`](https://developers.cloudflare.com/workers/wrangler/) gives you access to [Cloudflare bindings](https://developers.cloudflare.com/pages/platform/functions/bindings), [environment variables](https://developers.cloudflare.com/pages/platform/functions/bindings/#environment-variables), and the [cf object](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties). Getting hot reloading or the astro dev server to work with Wrangler might require custom setup. See [community examples](https://github.com/withastro/roadmap/discussions/590).
|
|
||||||
|
|
||||||
### Meaningful error messages
|
|
||||||
|
|
||||||
Currently, errors during running your application in Wrangler are not very useful, due to the minification of your code. For better debugging, you can add `vite.build.minify = false` setting to your `astro.config.mjs`.
|
|
||||||
|
|
||||||
```diff lang="js"
|
|
||||||
// astro.config.mjs
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare(),
|
|
||||||
output: 'server',
|
|
||||||
|
|
||||||
+ vite: {
|
|
||||||
+ build: {
|
|
||||||
+ minify: false,
|
|
||||||
+ },
|
|
||||||
+ },
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
For help, check out the `#support` channel on [Discord](https://astro.build/chat). Our friendly Support Squad members are here to help!
|
|
||||||
|
|
||||||
You can also check our [Astro Integration Documentation][astro-integration] for more on integrations.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
This package is maintained by Astro's Core team. You're welcome to submit an issue or PR!
|
|
||||||
|
|
||||||
[astro-integration]: https://docs.astro.build/en/guides/integrations-guide/
|
|
||||||
|
|
|
@ -1,65 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@astrojs/cloudflare",
|
"name": "@astrojs/cloudflare",
|
||||||
"description": "Deploy your site to Cloudflare Workers/Pages",
|
"version": "0.0.0",
|
||||||
"version": "7.5.1",
|
"private": true,
|
||||||
"type": "module",
|
"keywords": [],
|
||||||
"types": "./dist/index.d.ts",
|
"dont_remove": "This is a placeholder for the same of the docs smoke test"
|
||||||
"author": "withastro",
|
|
||||||
"license": "MIT",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/withastro/astro.git",
|
|
||||||
"directory": "packages/integrations/cloudflare"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"withastro",
|
|
||||||
"astro-adapter"
|
|
||||||
],
|
|
||||||
"bugs": "https://github.com/withastro/astro/issues",
|
|
||||||
"homepage": "https://docs.astro.build/en/guides/integrations-guide/cloudflare/",
|
|
||||||
"exports": {
|
|
||||||
".": "./dist/index.js",
|
|
||||||
"./entrypoints/server.advanced.js": "./dist/entrypoints/server.advanced.js",
|
|
||||||
"./entrypoints/server.directory.js": "./dist/entrypoints/server.directory.js",
|
|
||||||
"./package.json": "./package.json"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
|
||||||
"build:ci": "astro-scripts build \"src/**/*.ts\"",
|
|
||||||
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
|
||||||
"test": "mocha --exit --timeout 30000 test/",
|
|
||||||
"test:match": "mocha --exit --timeout 30000 -g"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/underscore-redirects": "workspace:*",
|
|
||||||
"@cloudflare/workers-types": "^4.20230821.0",
|
|
||||||
"miniflare": "^3.20230918.0",
|
|
||||||
"@iarna/toml": "^2.2.5",
|
|
||||||
"@miniflare/cache": "^2.14.1",
|
|
||||||
"@miniflare/shared": "^2.14.1",
|
|
||||||
"@miniflare/storage-memory": "^2.14.1",
|
|
||||||
"dotenv": "^16.3.1",
|
|
||||||
"esbuild": "^0.19.2",
|
|
||||||
"find-up": "^6.3.0",
|
|
||||||
"tiny-glob": "^0.2.9",
|
|
||||||
"vite": "^4.4.9"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"astro": "workspace:^3.2.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/iarna__toml": "^2.0.2",
|
|
||||||
"astro": "workspace:*",
|
|
||||||
"astro-scripts": "workspace:*",
|
|
||||||
"chai": "^4.3.7",
|
|
||||||
"cheerio": "1.0.0-rc.12",
|
|
||||||
"mocha": "^10.2.0",
|
|
||||||
"wrangler": "^3.5.1"
|
|
||||||
},
|
|
||||||
"publishConfig": {
|
|
||||||
"provenance": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
import type { Request as CFRequest, ExecutionContext } from '@cloudflare/workers-types';
|
|
||||||
import type { SSRManifest } from 'astro';
|
|
||||||
import { App } from 'astro/app';
|
|
||||||
import { getProcessEnvProxy, isNode } from '../util.js';
|
|
||||||
|
|
||||||
if (!isNode) {
|
|
||||||
process.env = getProcessEnvProxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
type Env = {
|
|
||||||
ASSETS: { fetch: (req: Request) => Promise<Response> };
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface AdvancedRuntime<T extends object = object> {
|
|
||||||
runtime: {
|
|
||||||
waitUntil: (promise: Promise<any>) => void;
|
|
||||||
env: Env & T;
|
|
||||||
cf: CFRequest['cf'];
|
|
||||||
caches: typeof caches;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createExports(manifest: SSRManifest) {
|
|
||||||
const app = new App(manifest);
|
|
||||||
|
|
||||||
const fetch = async (request: Request & CFRequest, env: Env, context: ExecutionContext) => {
|
|
||||||
// TODO: remove this any cast in the future
|
|
||||||
// REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
|
|
||||||
process.env = env as any;
|
|
||||||
|
|
||||||
const { pathname } = new URL(request.url);
|
|
||||||
|
|
||||||
// static assets fallback, in case default _routes.json is not used
|
|
||||||
if (manifest.assets.has(pathname)) {
|
|
||||||
return env.ASSETS.fetch(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
let routeData = app.match(request, { matchNotFound: true });
|
|
||||||
if (routeData) {
|
|
||||||
Reflect.set(
|
|
||||||
request,
|
|
||||||
Symbol.for('astro.clientAddress'),
|
|
||||||
request.headers.get('cf-connecting-ip')
|
|
||||||
);
|
|
||||||
|
|
||||||
const locals: AdvancedRuntime = {
|
|
||||||
runtime: {
|
|
||||||
waitUntil: (promise: Promise<any>) => {
|
|
||||||
context.waitUntil(promise);
|
|
||||||
},
|
|
||||||
env: env,
|
|
||||||
cf: request.cf,
|
|
||||||
caches: caches,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = await app.render(request, routeData, locals);
|
|
||||||
|
|
||||||
if (app.setCookieHeaders) {
|
|
||||||
for (const setCookieHeader of app.setCookieHeaders(response)) {
|
|
||||||
response.headers.append('Set-Cookie', setCookieHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: 404,
|
|
||||||
statusText: 'Not found',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return { default: { fetch } };
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
import type { Request as CFRequest, EventContext } from '@cloudflare/workers-types';
|
|
||||||
import type { SSRManifest } from 'astro';
|
|
||||||
import { App } from 'astro/app';
|
|
||||||
import { getProcessEnvProxy, isNode } from '../util.js';
|
|
||||||
|
|
||||||
if (!isNode) {
|
|
||||||
process.env = getProcessEnvProxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DirectoryRuntime<T extends object = object> {
|
|
||||||
runtime: {
|
|
||||||
waitUntil: (promise: Promise<any>) => void;
|
|
||||||
env: EventContext<unknown, string, unknown>['env'] & T;
|
|
||||||
cf: CFRequest['cf'];
|
|
||||||
caches: typeof caches;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createExports(manifest: SSRManifest) {
|
|
||||||
const app = new App(manifest);
|
|
||||||
|
|
||||||
const onRequest = async (context: EventContext<unknown, string, unknown>) => {
|
|
||||||
const request = context.request as CFRequest & Request;
|
|
||||||
const { env } = context;
|
|
||||||
|
|
||||||
// TODO: remove this any cast in the future
|
|
||||||
// REF: the type cast to any is needed because the Cloudflare Env Type is not assignable to type 'ProcessEnv'
|
|
||||||
process.env = env as any;
|
|
||||||
|
|
||||||
const { pathname } = new URL(request.url);
|
|
||||||
// static assets fallback, in case default _routes.json is not used
|
|
||||||
if (manifest.assets.has(pathname)) {
|
|
||||||
return env.ASSETS.fetch(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
let routeData = app.match(request, { matchNotFound: true });
|
|
||||||
if (routeData) {
|
|
||||||
Reflect.set(
|
|
||||||
request,
|
|
||||||
Symbol.for('astro.clientAddress'),
|
|
||||||
request.headers.get('cf-connecting-ip')
|
|
||||||
);
|
|
||||||
|
|
||||||
const locals: DirectoryRuntime = {
|
|
||||||
runtime: {
|
|
||||||
waitUntil: (promise: Promise<any>) => {
|
|
||||||
context.waitUntil(promise);
|
|
||||||
},
|
|
||||||
env: context.env,
|
|
||||||
cf: request.cf,
|
|
||||||
caches: caches,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = await app.render(request, routeData, locals);
|
|
||||||
|
|
||||||
if (app.setCookieHeaders) {
|
|
||||||
for (const setCookieHeader of app.setCookieHeaders(response)) {
|
|
||||||
response.headers.append('Set-Cookie', setCookieHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(null, {
|
|
||||||
status: 404,
|
|
||||||
statusText: 'Not found',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return { onRequest, manifest };
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import type { AstroAdapter, AstroFeatureMap } from 'astro';
|
|
||||||
|
|
||||||
export function getAdapter({
|
|
||||||
isModeDirectory,
|
|
||||||
functionPerRoute,
|
|
||||||
}: {
|
|
||||||
isModeDirectory: boolean;
|
|
||||||
functionPerRoute: boolean;
|
|
||||||
}): AstroAdapter {
|
|
||||||
const astroFeatures = {
|
|
||||||
hybridOutput: 'stable',
|
|
||||||
staticOutput: 'unsupported',
|
|
||||||
serverOutput: 'stable',
|
|
||||||
assets: {
|
|
||||||
supportKind: 'stable',
|
|
||||||
isSharpCompatible: false,
|
|
||||||
isSquooshCompatible: false,
|
|
||||||
},
|
|
||||||
} satisfies AstroFeatureMap;
|
|
||||||
|
|
||||||
if (isModeDirectory) {
|
|
||||||
return {
|
|
||||||
name: '@astrojs/cloudflare',
|
|
||||||
serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.directory.js',
|
|
||||||
exports: ['onRequest', 'manifest'],
|
|
||||||
adapterFeatures: {
|
|
||||||
functionPerRoute,
|
|
||||||
edgeMiddleware: false,
|
|
||||||
},
|
|
||||||
supportedAstroFeatures: astroFeatures,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: '@astrojs/cloudflare',
|
|
||||||
serverEntrypoint: '@astrojs/cloudflare/entrypoints/server.advanced.js',
|
|
||||||
exports: ['default'],
|
|
||||||
supportedAstroFeatures: astroFeatures,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,611 +0,0 @@
|
||||||
import type { AstroConfig, AstroIntegration, RouteData } from 'astro';
|
|
||||||
|
|
||||||
import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
|
|
||||||
import { AstroError } from 'astro/errors';
|
|
||||||
import esbuild from 'esbuild';
|
|
||||||
import { Miniflare } from 'miniflare';
|
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import * as os from 'node:os';
|
|
||||||
import { dirname, relative, sep } from 'node:path';
|
|
||||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
||||||
import glob from 'tiny-glob';
|
|
||||||
import { getAdapter } from './getAdapter.js';
|
|
||||||
import { deduplicatePatterns } from './utils/deduplicatePatterns.js';
|
|
||||||
import { getCFObject } from './utils/getCFObject.js';
|
|
||||||
import {
|
|
||||||
getD1Bindings,
|
|
||||||
getDOBindings,
|
|
||||||
getEnvVars,
|
|
||||||
getKVBindings,
|
|
||||||
getR2Bindings,
|
|
||||||
} from './utils/parser.js';
|
|
||||||
import { prependForwardSlash } from './utils/prependForwardSlash.js';
|
|
||||||
import { rewriteWasmImportPath } from './utils/rewriteWasmImportPath.js';
|
|
||||||
import { wasmModuleLoader } from './utils/wasm-module-loader.js';
|
|
||||||
|
|
||||||
export type { AdvancedRuntime } from './entrypoints/server.advanced.js';
|
|
||||||
export type { DirectoryRuntime } from './entrypoints/server.directory.js';
|
|
||||||
|
|
||||||
type Options = {
|
|
||||||
mode?: 'directory' | 'advanced';
|
|
||||||
functionPerRoute?: boolean;
|
|
||||||
/** Configure automatic `routes.json` generation */
|
|
||||||
routes?: {
|
|
||||||
/** Strategy for generating `include` and `exclude` patterns
|
|
||||||
* - `auto`: Will use the strategy that generates the least amount of entries.
|
|
||||||
* - `include`: For each page or endpoint in your application that is not prerendered, an entry in the `include` array will be generated. For each page that is prerendered and whoose path is matched by an `include` entry, an entry in the `exclude` array will be generated.
|
|
||||||
* - `exclude`: One `"/*"` entry in the `include` array will be generated. For each page that is prerendered, an entry in the `exclude` array will be generated.
|
|
||||||
* */
|
|
||||||
strategy?: 'auto' | 'include' | 'exclude';
|
|
||||||
/** Additional `include` patterns */
|
|
||||||
include?: string[];
|
|
||||||
/** Additional `exclude` patterns */
|
|
||||||
exclude?: string[];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* 'off': current behaviour (wrangler is needed)
|
|
||||||
* 'local': use a static req.cf object, and env vars defined in wrangler.toml & .dev.vars (astro dev is enough)
|
|
||||||
* 'remote': use a dynamic real-live req.cf object, and env vars defined in wrangler.toml & .dev.vars (astro dev is enough)
|
|
||||||
*/
|
|
||||||
runtime?: 'off' | 'local' | 'remote';
|
|
||||||
wasmModuleImports?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface BuildConfig {
|
|
||||||
server: URL;
|
|
||||||
client: URL;
|
|
||||||
assets: string;
|
|
||||||
serverEntry: string;
|
|
||||||
split?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function createIntegration(args?: Options): AstroIntegration {
|
|
||||||
let _config: AstroConfig;
|
|
||||||
let _buildConfig: BuildConfig;
|
|
||||||
let _mf: Miniflare;
|
|
||||||
let _entryPoints = new Map<RouteData, URL>();
|
|
||||||
|
|
||||||
const SERVER_BUILD_FOLDER = '/$server_build/';
|
|
||||||
|
|
||||||
const isModeDirectory = args?.mode === 'directory';
|
|
||||||
const functionPerRoute = args?.functionPerRoute ?? false;
|
|
||||||
const runtimeMode = args?.runtime ?? 'off';
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: '@astrojs/cloudflare',
|
|
||||||
hooks: {
|
|
||||||
'astro:config:setup': ({ config, updateConfig }) => {
|
|
||||||
updateConfig({
|
|
||||||
build: {
|
|
||||||
client: new URL(`.${config.base}`, config.outDir),
|
|
||||||
server: new URL(`.${SERVER_BUILD_FOLDER}`, config.outDir),
|
|
||||||
serverEntry: '_worker.mjs',
|
|
||||||
redirects: false,
|
|
||||||
},
|
|
||||||
vite: {
|
|
||||||
// load .wasm files as WebAssembly modules
|
|
||||||
plugins: [
|
|
||||||
wasmModuleLoader({
|
|
||||||
disabled: !args?.wasmModuleImports,
|
|
||||||
assetsDirectory: config.build.assets,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
'astro:config:done': ({ setAdapter, config }) => {
|
|
||||||
setAdapter(getAdapter({ isModeDirectory, functionPerRoute }));
|
|
||||||
_config = config;
|
|
||||||
_buildConfig = config.build;
|
|
||||||
|
|
||||||
if (_config.output === 'static') {
|
|
||||||
throw new AstroError(
|
|
||||||
'[@astrojs/cloudflare] `output: "server"` or `output: "hybrid"` is required to use this adapter. Otherwise, this adapter is not necessary to deploy a static site to Cloudflare.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_config.base === SERVER_BUILD_FOLDER) {
|
|
||||||
throw new AstroError(
|
|
||||||
'[@astrojs/cloudflare] `base: "${SERVER_BUILD_FOLDER}"` is not allowed. Please change your `base` config to something else.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'astro:server:setup': ({ server }) => {
|
|
||||||
if (runtimeMode !== 'off') {
|
|
||||||
server.middlewares.use(async function middleware(req, res, next) {
|
|
||||||
try {
|
|
||||||
const cf = await getCFObject(runtimeMode);
|
|
||||||
const vars = await getEnvVars();
|
|
||||||
const D1Bindings = await getD1Bindings();
|
|
||||||
const R2Bindings = await getR2Bindings();
|
|
||||||
const KVBindings = await getKVBindings();
|
|
||||||
const DOBindings = await getDOBindings();
|
|
||||||
let bindingsEnv = new Object({});
|
|
||||||
|
|
||||||
// fix for the error "kj/filesystem-disk-unix.c++:1709: warning: PWD environment variable doesn't match current directory."
|
|
||||||
// note: This mismatch might be primarily due to the test runner.
|
|
||||||
const originalPWD = process.env.PWD;
|
|
||||||
process.env.PWD = process.cwd();
|
|
||||||
|
|
||||||
_mf = new Miniflare({
|
|
||||||
modules: true,
|
|
||||||
script: '',
|
|
||||||
cache: true,
|
|
||||||
cachePersist: true,
|
|
||||||
cacheWarnUsage: true,
|
|
||||||
d1Databases: D1Bindings,
|
|
||||||
d1Persist: true,
|
|
||||||
r2Buckets: R2Bindings,
|
|
||||||
r2Persist: true,
|
|
||||||
kvNamespaces: KVBindings,
|
|
||||||
kvPersist: true,
|
|
||||||
durableObjects: DOBindings,
|
|
||||||
durableObjectsPersist: true,
|
|
||||||
});
|
|
||||||
await _mf.ready;
|
|
||||||
|
|
||||||
for (const D1Binding of D1Bindings) {
|
|
||||||
const db = await _mf.getD1Database(D1Binding);
|
|
||||||
Reflect.set(bindingsEnv, D1Binding, db);
|
|
||||||
}
|
|
||||||
for (const R2Binding of R2Bindings) {
|
|
||||||
const bucket = await _mf.getR2Bucket(R2Binding);
|
|
||||||
Reflect.set(bindingsEnv, R2Binding, bucket);
|
|
||||||
}
|
|
||||||
for (const KVBinding of KVBindings) {
|
|
||||||
const namespace = await _mf.getKVNamespace(KVBinding);
|
|
||||||
Reflect.set(bindingsEnv, KVBinding, namespace);
|
|
||||||
}
|
|
||||||
for (const key in DOBindings) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(DOBindings, key)) {
|
|
||||||
const DO = await _mf.getDurableObjectNamespace(key);
|
|
||||||
Reflect.set(bindingsEnv, key, DO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mfCache = await _mf.getCaches();
|
|
||||||
|
|
||||||
process.env.PWD = originalPWD;
|
|
||||||
const clientLocalsSymbol = Symbol.for('astro.locals');
|
|
||||||
Reflect.set(req, clientLocalsSymbol, {
|
|
||||||
runtime: {
|
|
||||||
env: {
|
|
||||||
// default binding for static assets will be dynamic once we support mocking of bindings
|
|
||||||
ASSETS: {},
|
|
||||||
// this is just a VAR for CF to change build behavior, on dev it should be 0
|
|
||||||
CF_PAGES: '0',
|
|
||||||
// will be fetched from git dynamically once we support mocking of bindings
|
|
||||||
CF_PAGES_BRANCH: 'TBA',
|
|
||||||
// will be fetched from git dynamically once we support mocking of bindings
|
|
||||||
CF_PAGES_COMMIT_SHA: 'TBA',
|
|
||||||
CF_PAGES_URL: `http://${req.headers.host}`,
|
|
||||||
...bindingsEnv,
|
|
||||||
...vars,
|
|
||||||
},
|
|
||||||
cf: cf,
|
|
||||||
waitUntil: (_promise: Promise<any>) => {
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
caches: mfCache,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
next();
|
|
||||||
} catch {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'astro:server:done': async ({ logger }) => {
|
|
||||||
if (_mf) {
|
|
||||||
logger.info('Cleaning up the Miniflare instance, and shutting down the workerd server.');
|
|
||||||
await _mf.dispose();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'astro:build:setup': ({ vite, target }) => {
|
|
||||||
if (target === 'server') {
|
|
||||||
vite.resolve ||= {};
|
|
||||||
vite.resolve.alias ||= {};
|
|
||||||
|
|
||||||
const aliases = [{ find: 'react-dom/server', replacement: 'react-dom/server.browser' }];
|
|
||||||
|
|
||||||
if (Array.isArray(vite.resolve.alias)) {
|
|
||||||
vite.resolve.alias = [...vite.resolve.alias, ...aliases];
|
|
||||||
} else {
|
|
||||||
for (const alias of aliases) {
|
|
||||||
(vite.resolve.alias as Record<string, string>)[alias.find] = alias.replacement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vite.ssr ||= {};
|
|
||||||
vite.ssr.target = 'webworker';
|
|
||||||
|
|
||||||
// Cloudflare env is only available per request. This isn't feasible for code that access env vars
|
|
||||||
// in a global way, so we shim their access as `process.env.*`. We will populate `process.env` later
|
|
||||||
// in its fetch handler.
|
|
||||||
vite.define = {
|
|
||||||
'process.env': 'process.env',
|
|
||||||
...vite.define,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'astro:build:ssr': ({ entryPoints }) => {
|
|
||||||
_entryPoints = entryPoints;
|
|
||||||
},
|
|
||||||
'astro:build:done': async ({ pages, routes, dir }) => {
|
|
||||||
const functionsUrl = new URL('functions/', _config.root);
|
|
||||||
const assetsUrl = new URL(_buildConfig.assets, _buildConfig.client);
|
|
||||||
|
|
||||||
if (isModeDirectory) {
|
|
||||||
await fs.promises.mkdir(functionsUrl, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove _buildConfig.split in Astro 4.0
|
|
||||||
if (isModeDirectory && (_buildConfig.split || functionPerRoute)) {
|
|
||||||
const entryPointsURL = [..._entryPoints.values()];
|
|
||||||
const entryPaths = entryPointsURL.map((entry) => fileURLToPath(entry));
|
|
||||||
const outputUrl = new URL('$astro', _buildConfig.server);
|
|
||||||
const outputDir = fileURLToPath(outputUrl);
|
|
||||||
//
|
|
||||||
// Sadly, when wasmModuleImports is enabled, this needs to build esbuild for each depth of routes/entrypoints
|
|
||||||
// independently so that relative import paths to the assets are the correct depth of '../' traversals
|
|
||||||
// This is inefficient, so wasmModuleImports is opt-in. This could potentially be improved in the future by
|
|
||||||
// taking advantage of the esbuild "onEnd" hook to rewrite import code per entry point relative to where the final
|
|
||||||
// destination of the entrypoint is
|
|
||||||
const entryPathsGroupedByDepth = !args.wasmModuleImports
|
|
||||||
? [entryPaths]
|
|
||||||
: entryPaths
|
|
||||||
.reduce((sum, thisPath) => {
|
|
||||||
const depthFromRoot = thisPath.split(sep).length;
|
|
||||||
sum.set(depthFromRoot, (sum.get(depthFromRoot) || []).concat(thisPath));
|
|
||||||
return sum;
|
|
||||||
}, new Map<number, string[]>())
|
|
||||||
.values();
|
|
||||||
|
|
||||||
for (const pathsGroup of entryPathsGroupedByDepth) {
|
|
||||||
// for some reason this exports to "entry.pages" on windows instead of "pages" on unix environments.
|
|
||||||
// This deduces the name of the "pages" build directory
|
|
||||||
const pagesDirname = relative(fileURLToPath(_buildConfig.server), pathsGroup[0]).split(
|
|
||||||
sep
|
|
||||||
)[0];
|
|
||||||
const absolutePagesDirname = fileURLToPath(new URL(pagesDirname, _buildConfig.server));
|
|
||||||
const urlWithinFunctions = new URL(
|
|
||||||
relative(absolutePagesDirname, pathsGroup[0]),
|
|
||||||
functionsUrl
|
|
||||||
);
|
|
||||||
const relativePathToAssets = relative(
|
|
||||||
dirname(fileURLToPath(urlWithinFunctions)),
|
|
||||||
fileURLToPath(assetsUrl)
|
|
||||||
);
|
|
||||||
await esbuild.build({
|
|
||||||
target: 'es2022',
|
|
||||||
platform: 'browser',
|
|
||||||
conditions: ['workerd', 'worker', 'browser'],
|
|
||||||
external: [
|
|
||||||
'node:assert',
|
|
||||||
'node:async_hooks',
|
|
||||||
'node:buffer',
|
|
||||||
'node:diagnostics_channel',
|
|
||||||
'node:events',
|
|
||||||
'node:path',
|
|
||||||
'node:process',
|
|
||||||
'node:stream',
|
|
||||||
'node:string_decoder',
|
|
||||||
'node:util',
|
|
||||||
],
|
|
||||||
entryPoints: pathsGroup,
|
|
||||||
outbase: absolutePagesDirname,
|
|
||||||
outdir: outputDir,
|
|
||||||
allowOverwrite: true,
|
|
||||||
format: 'esm',
|
|
||||||
bundle: true,
|
|
||||||
minify: _config.vite?.build?.minify !== false,
|
|
||||||
banner: {
|
|
||||||
js: `globalThis.process = {
|
|
||||||
argv: [],
|
|
||||||
env: {},
|
|
||||||
};`,
|
|
||||||
},
|
|
||||||
logOverride: {
|
|
||||||
'ignored-bare-import': 'silent',
|
|
||||||
},
|
|
||||||
plugins: !args?.wasmModuleImports
|
|
||||||
? []
|
|
||||||
: [rewriteWasmImportPath({ relativePathToAssets })],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const outputFiles: Array<string> = await glob(`**/*`, {
|
|
||||||
cwd: outputDir,
|
|
||||||
filesOnly: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// move the files into the functions folder
|
|
||||||
// & make sure the file names match Cloudflare syntax for routing
|
|
||||||
for (const outputFile of outputFiles) {
|
|
||||||
const path = outputFile.split(sep);
|
|
||||||
|
|
||||||
const finalSegments = path.map((segment) =>
|
|
||||||
segment
|
|
||||||
.replace(/(\_)(\w+)(\_)/g, (_, __, prop) => {
|
|
||||||
return `[${prop}]`;
|
|
||||||
})
|
|
||||||
.replace(/(\_\-\-\-)(\w+)(\_)/g, (_, __, prop) => {
|
|
||||||
return `[[${prop}]]`;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
finalSegments[finalSegments.length - 1] = finalSegments[finalSegments.length - 1]
|
|
||||||
.replace('entry.', '')
|
|
||||||
.replace(/(.*)\.(\w+)\.(\w+)$/g, (_, fileName, __, newExt) => {
|
|
||||||
return `${fileName}.${newExt}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const finalDirPath = finalSegments.slice(0, -1).join(sep);
|
|
||||||
const finalPath = finalSegments.join(sep);
|
|
||||||
|
|
||||||
const newDirUrl = new URL(finalDirPath, functionsUrl);
|
|
||||||
await fs.promises.mkdir(newDirUrl, { recursive: true });
|
|
||||||
|
|
||||||
const oldFileUrl = new URL(`$astro/${outputFile}`, outputUrl);
|
|
||||||
const newFileUrl = new URL(finalPath, functionsUrl);
|
|
||||||
await fs.promises.rename(oldFileUrl, newFileUrl);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const entryPath = fileURLToPath(new URL(_buildConfig.serverEntry, _buildConfig.server));
|
|
||||||
const entryUrl = new URL(_buildConfig.serverEntry, _config.outDir);
|
|
||||||
const buildPath = fileURLToPath(entryUrl);
|
|
||||||
// A URL for the final build path after renaming
|
|
||||||
const finalBuildUrl = pathToFileURL(buildPath.replace(/\.mjs$/, '.js'));
|
|
||||||
|
|
||||||
await esbuild.build({
|
|
||||||
target: 'es2022',
|
|
||||||
platform: 'browser',
|
|
||||||
conditions: ['workerd', 'worker', 'browser'],
|
|
||||||
external: [
|
|
||||||
'node:assert',
|
|
||||||
'node:async_hooks',
|
|
||||||
'node:buffer',
|
|
||||||
'node:diagnostics_channel',
|
|
||||||
'node:events',
|
|
||||||
'node:path',
|
|
||||||
'node:process',
|
|
||||||
'node:stream',
|
|
||||||
'node:string_decoder',
|
|
||||||
'node:util',
|
|
||||||
],
|
|
||||||
entryPoints: [entryPath],
|
|
||||||
outfile: buildPath,
|
|
||||||
allowOverwrite: true,
|
|
||||||
format: 'esm',
|
|
||||||
bundle: true,
|
|
||||||
minify: _config.vite?.build?.minify !== false,
|
|
||||||
banner: {
|
|
||||||
js: `globalThis.process = {
|
|
||||||
argv: [],
|
|
||||||
env: {},
|
|
||||||
};`,
|
|
||||||
},
|
|
||||||
logOverride: {
|
|
||||||
'ignored-bare-import': 'silent',
|
|
||||||
},
|
|
||||||
plugins: !args?.wasmModuleImports
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
rewriteWasmImportPath({
|
|
||||||
relativePathToAssets: isModeDirectory
|
|
||||||
? relative(fileURLToPath(functionsUrl), fileURLToPath(assetsUrl))
|
|
||||||
: relative(fileURLToPath(_buildConfig.client), fileURLToPath(assetsUrl)),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rename to worker.js
|
|
||||||
await fs.promises.rename(buildPath, finalBuildUrl);
|
|
||||||
|
|
||||||
if (isModeDirectory) {
|
|
||||||
const directoryUrl = new URL('[[path]].js', functionsUrl);
|
|
||||||
await fs.promises.rename(finalBuildUrl, directoryUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// throw the server folder in the bin
|
|
||||||
const serverUrl = new URL(_buildConfig.server);
|
|
||||||
await fs.promises.rm(serverUrl, { recursive: true, force: true });
|
|
||||||
|
|
||||||
// move cloudflare specific files to the root
|
|
||||||
const cloudflareSpecialFiles = ['_headers', '_redirects', '_routes.json'];
|
|
||||||
|
|
||||||
if (_config.base !== '/') {
|
|
||||||
for (const file of cloudflareSpecialFiles) {
|
|
||||||
try {
|
|
||||||
await fs.promises.rename(
|
|
||||||
new URL(file, _buildConfig.client),
|
|
||||||
new URL(file, _config.outDir)
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add also the worker file so it's excluded from the _routes.json generation
|
|
||||||
if (!isModeDirectory) {
|
|
||||||
cloudflareSpecialFiles.push('_worker.js');
|
|
||||||
}
|
|
||||||
|
|
||||||
const routesExists = await fs.promises
|
|
||||||
.stat(new URL('./_routes.json', _config.outDir))
|
|
||||||
.then((stat) => stat.isFile())
|
|
||||||
.catch(() => false);
|
|
||||||
|
|
||||||
// this creates a _routes.json, in case there is none present to enable
|
|
||||||
// cloudflare to handle static files and support _redirects configuration
|
|
||||||
if (!routesExists) {
|
|
||||||
/**
|
|
||||||
* These route types are candiates for being part of the `_routes.json` `include` array.
|
|
||||||
*/
|
|
||||||
const potentialFunctionRouteTypes = ['endpoint', 'page'];
|
|
||||||
|
|
||||||
const functionEndpoints = routes
|
|
||||||
// Certain route types, when their prerender option is set to false, run on the server as function invocations
|
|
||||||
.filter((route) => potentialFunctionRouteTypes.includes(route.type) && !route.prerender)
|
|
||||||
.map((route) => {
|
|
||||||
const includePattern =
|
|
||||||
'/' +
|
|
||||||
route.segments
|
|
||||||
.flat()
|
|
||||||
.map((segment) => (segment.dynamic ? '*' : segment.content))
|
|
||||||
.join('/');
|
|
||||||
|
|
||||||
const regexp = new RegExp(
|
|
||||||
'^\\/' +
|
|
||||||
route.segments
|
|
||||||
.flat()
|
|
||||||
.map((segment) => (segment.dynamic ? '(.*)' : segment.content))
|
|
||||||
.join('\\/') +
|
|
||||||
'$'
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
includePattern,
|
|
||||||
regexp,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const staticPathList: Array<string> = (
|
|
||||||
await glob(`${fileURLToPath(_buildConfig.client)}/**/*`, {
|
|
||||||
cwd: fileURLToPath(_config.outDir),
|
|
||||||
filesOnly: true,
|
|
||||||
dot: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.filter((file: string) => cloudflareSpecialFiles.indexOf(file) < 0)
|
|
||||||
.map((file: string) => `/${file.replace(/\\/g, '/')}`);
|
|
||||||
|
|
||||||
for (let page of pages) {
|
|
||||||
let pagePath = prependForwardSlash(page.pathname);
|
|
||||||
if (_config.base !== '/') {
|
|
||||||
const base = _config.base.endsWith('/') ? _config.base.slice(0, -1) : _config.base;
|
|
||||||
pagePath = `${base}${pagePath}`;
|
|
||||||
}
|
|
||||||
staticPathList.push(pagePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirectsExists = await fs.promises
|
|
||||||
.stat(new URL('./_redirects', _config.outDir))
|
|
||||||
.then((stat) => stat.isFile())
|
|
||||||
.catch(() => false);
|
|
||||||
|
|
||||||
// convert all redirect source paths into a list of routes
|
|
||||||
// and add them to the static path
|
|
||||||
if (redirectsExists) {
|
|
||||||
const redirects = (
|
|
||||||
await fs.promises.readFile(new URL('./_redirects', _config.outDir), 'utf-8')
|
|
||||||
)
|
|
||||||
.split(os.EOL)
|
|
||||||
.map((line) => {
|
|
||||||
const parts = line.split(' ');
|
|
||||||
if (parts.length < 2) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
// convert /products/:id to /products/*
|
|
||||||
return (
|
|
||||||
parts[0]
|
|
||||||
.replace(/\/:.*?(?=\/|$)/g, '/*')
|
|
||||||
// remove query params as they are not supported by cloudflare
|
|
||||||
.replace(/\?.*$/, '')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.filter(
|
|
||||||
(line, index, arr) => line !== null && arr.indexOf(line) === index
|
|
||||||
) as string[];
|
|
||||||
|
|
||||||
if (redirects.length > 0) {
|
|
||||||
staticPathList.push(...redirects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirectRoutes: [RouteData, string][] = routes
|
|
||||||
.filter((r) => r.type === 'redirect')
|
|
||||||
.map((r) => {
|
|
||||||
return [r, ''];
|
|
||||||
});
|
|
||||||
const trueRedirects = createRedirectsFromAstroRoutes({
|
|
||||||
config: _config,
|
|
||||||
routeToDynamicTargetMap: new Map(Array.from(redirectRoutes)),
|
|
||||||
dir,
|
|
||||||
});
|
|
||||||
if (!trueRedirects.empty()) {
|
|
||||||
await fs.promises.appendFile(
|
|
||||||
new URL('./_redirects', _config.outDir),
|
|
||||||
trueRedirects.print()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
staticPathList.push(...routes.filter((r) => r.type === 'redirect').map((r) => r.route));
|
|
||||||
|
|
||||||
const strategy = args?.routes?.strategy ?? 'auto';
|
|
||||||
|
|
||||||
// Strategy `include`: include all function endpoints, and then exclude static paths that would be matched by an include pattern
|
|
||||||
const includeStrategy =
|
|
||||||
strategy === 'exclude'
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
include: deduplicatePatterns(
|
|
||||||
functionEndpoints
|
|
||||||
.map((endpoint) => endpoint.includePattern)
|
|
||||||
.concat(args?.routes?.include ?? [])
|
|
||||||
),
|
|
||||||
exclude: deduplicatePatterns(
|
|
||||||
staticPathList
|
|
||||||
.filter((file: string) =>
|
|
||||||
functionEndpoints.some((endpoint) => endpoint.regexp.test(file))
|
|
||||||
)
|
|
||||||
.concat(args?.routes?.exclude ?? [])
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Cloudflare requires at least one include pattern:
|
|
||||||
// https://developers.cloudflare.com/pages/platform/functions/routing/#limits
|
|
||||||
// So we add a pattern that we immediately exclude again
|
|
||||||
if (includeStrategy?.include.length === 0) {
|
|
||||||
includeStrategy.include = ['/'];
|
|
||||||
includeStrategy.exclude = ['/'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strategy `exclude`: include everything, and then exclude all static paths
|
|
||||||
const excludeStrategy =
|
|
||||||
strategy === 'include'
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
include: ['/*'],
|
|
||||||
exclude: deduplicatePatterns(staticPathList.concat(args?.routes?.exclude ?? [])),
|
|
||||||
};
|
|
||||||
|
|
||||||
const includeStrategyLength = includeStrategy
|
|
||||||
? includeStrategy.include.length + includeStrategy.exclude.length
|
|
||||||
: Infinity;
|
|
||||||
|
|
||||||
const excludeStrategyLength = excludeStrategy
|
|
||||||
? excludeStrategy.include.length + excludeStrategy.exclude.length
|
|
||||||
: Infinity;
|
|
||||||
|
|
||||||
const winningStrategy =
|
|
||||||
includeStrategyLength <= excludeStrategyLength ? includeStrategy : excludeStrategy;
|
|
||||||
|
|
||||||
await fs.promises.writeFile(
|
|
||||||
new URL('./_routes.json', _config.outDir),
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
version: 1,
|
|
||||||
...winningStrategy,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
export const isNode =
|
|
||||||
typeof process === 'object' && Object.prototype.toString.call(process) === '[object process]';
|
|
||||||
|
|
||||||
export function getProcessEnvProxy() {
|
|
||||||
return new Proxy(
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
get: (target, prop) => {
|
|
||||||
console.warn(
|
|
||||||
// NOTE: \0 prevents Vite replacement
|
|
||||||
`Unable to access \`import.meta\0.env.${prop.toString()}\` on initialization ` +
|
|
||||||
`as the Cloudflare platform only provides the environment variables per request. ` +
|
|
||||||
`Please move the environment variable access inside a function ` +
|
|
||||||
`that's only called after a request has been received.`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
/**
|
|
||||||
* Remove duplicates and redundant patterns from an `include` or `exclude` list.
|
|
||||||
* Otherwise Cloudflare will throw an error on deployment. Plus, it saves more entries.
|
|
||||||
* E.g. `['/foo/*', '/foo/*', '/foo/bar'] => ['/foo/*']`
|
|
||||||
* @param patterns a list of `include` or `exclude` patterns
|
|
||||||
* @returns a deduplicated list of patterns
|
|
||||||
*/
|
|
||||||
export function deduplicatePatterns(patterns: string[]) {
|
|
||||||
const openPatterns: RegExp[] = [];
|
|
||||||
|
|
||||||
// A value in the set may only occur once; it is unique in the set's collection.
|
|
||||||
// ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
|
|
||||||
return [...new Set(patterns)]
|
|
||||||
.sort((a, b) => a.length - b.length)
|
|
||||||
.filter((pattern) => {
|
|
||||||
if (openPatterns.some((p) => p.test(pattern))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pattern.endsWith('*')) {
|
|
||||||
openPatterns.push(new RegExp(`^${pattern.replace(/(\*\/)*\*$/g, '.*')}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
import type { IncomingRequestCfProperties } from '@cloudflare/workers-types/experimental';
|
|
||||||
|
|
||||||
export async function getCFObject(
|
|
||||||
runtimeMode: string
|
|
||||||
): Promise<IncomingRequestCfProperties | void> {
|
|
||||||
const CF_ENDPOINT = 'https://workers.cloudflare.com/cf.json';
|
|
||||||
const CF_FALLBACK: IncomingRequestCfProperties = {
|
|
||||||
asOrganization: '',
|
|
||||||
asn: 395747,
|
|
||||||
colo: 'DFW',
|
|
||||||
city: 'Austin',
|
|
||||||
region: 'Texas',
|
|
||||||
regionCode: 'TX',
|
|
||||||
metroCode: '635',
|
|
||||||
postalCode: '78701',
|
|
||||||
country: 'US',
|
|
||||||
continent: 'NA',
|
|
||||||
timezone: 'America/Chicago',
|
|
||||||
latitude: '30.27130',
|
|
||||||
longitude: '-97.74260',
|
|
||||||
clientTcpRtt: 0,
|
|
||||||
httpProtocol: 'HTTP/1.1',
|
|
||||||
requestPriority: 'weight=192;exclusive=0',
|
|
||||||
tlsCipher: 'AEAD-AES128-GCM-SHA256',
|
|
||||||
tlsVersion: 'TLSv1.3',
|
|
||||||
tlsClientAuth: {
|
|
||||||
certPresented: '0',
|
|
||||||
certVerified: 'NONE',
|
|
||||||
certRevoked: '0',
|
|
||||||
certIssuerDN: '',
|
|
||||||
certSubjectDN: '',
|
|
||||||
certIssuerDNRFC2253: '',
|
|
||||||
certSubjectDNRFC2253: '',
|
|
||||||
certIssuerDNLegacy: '',
|
|
||||||
certSubjectDNLegacy: '',
|
|
||||||
certSerial: '',
|
|
||||||
certIssuerSerial: '',
|
|
||||||
certSKI: '',
|
|
||||||
certIssuerSKI: '',
|
|
||||||
certFingerprintSHA1: '',
|
|
||||||
certFingerprintSHA256: '',
|
|
||||||
certNotBefore: '',
|
|
||||||
certNotAfter: '',
|
|
||||||
},
|
|
||||||
edgeRequestKeepAliveStatus: 0,
|
|
||||||
hostMetadata: undefined,
|
|
||||||
clientTrustScore: 99,
|
|
||||||
botManagement: {
|
|
||||||
corporateProxy: false,
|
|
||||||
verifiedBot: false,
|
|
||||||
ja3Hash: '25b4882c2bcb50cd6b469ff28c596742',
|
|
||||||
staticResource: false,
|
|
||||||
detectionIds: [],
|
|
||||||
score: 99,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (runtimeMode === 'local') {
|
|
||||||
return CF_FALLBACK;
|
|
||||||
} else if (runtimeMode === 'remote') {
|
|
||||||
try {
|
|
||||||
const res = await fetch(CF_ENDPOINT);
|
|
||||||
const cfText = await res.text();
|
|
||||||
const storedCf = JSON.parse(cfText);
|
|
||||||
return storedCf;
|
|
||||||
} catch (e: any) {
|
|
||||||
return CF_FALLBACK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,191 +0,0 @@
|
||||||
/**
|
|
||||||
* This file is a derivative work of wrangler by Cloudflare
|
|
||||||
* An upstream request for exposing this API was made here:
|
|
||||||
* https://github.com/cloudflare/workers-sdk/issues/3897
|
|
||||||
*
|
|
||||||
* Until further notice, we will be using this file as a workaround
|
|
||||||
* TODO: Tackle this file, once their is an decision on the upstream request
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type {} from '@cloudflare/workers-types/experimental';
|
|
||||||
import TOML from '@iarna/toml';
|
|
||||||
import dotenv from 'dotenv';
|
|
||||||
import { findUpSync } from 'find-up';
|
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import { dirname, resolve } from 'node:path';
|
|
||||||
let _wrangler: any;
|
|
||||||
|
|
||||||
function findWranglerToml(
|
|
||||||
referencePath: string = process.cwd(),
|
|
||||||
preferJson = false
|
|
||||||
): string | undefined {
|
|
||||||
if (preferJson) {
|
|
||||||
return (
|
|
||||||
findUpSync(`wrangler.json`, { cwd: referencePath }) ??
|
|
||||||
findUpSync(`wrangler.toml`, { cwd: referencePath })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return findUpSync(`wrangler.toml`, { cwd: referencePath });
|
|
||||||
}
|
|
||||||
type File = {
|
|
||||||
file?: string;
|
|
||||||
fileText?: string;
|
|
||||||
};
|
|
||||||
type Location = File & {
|
|
||||||
line: number;
|
|
||||||
column: number;
|
|
||||||
length?: number;
|
|
||||||
lineText?: string;
|
|
||||||
suggestion?: string;
|
|
||||||
};
|
|
||||||
type Message = {
|
|
||||||
text: string;
|
|
||||||
location?: Location;
|
|
||||||
notes?: Message[];
|
|
||||||
kind?: 'warning' | 'error';
|
|
||||||
};
|
|
||||||
class ParseError extends Error implements Message {
|
|
||||||
readonly text: string;
|
|
||||||
readonly notes: Message[];
|
|
||||||
readonly location?: Location;
|
|
||||||
readonly kind: 'warning' | 'error';
|
|
||||||
|
|
||||||
constructor({ text, notes, location, kind }: Message) {
|
|
||||||
super(text);
|
|
||||||
this.name = this.constructor.name;
|
|
||||||
this.text = text;
|
|
||||||
this.notes = notes ?? [];
|
|
||||||
this.location = location;
|
|
||||||
this.kind = kind ?? 'error';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const TOML_ERROR_NAME = 'TomlError';
|
|
||||||
const TOML_ERROR_SUFFIX = ' at row ';
|
|
||||||
type TomlError = Error & {
|
|
||||||
line: number;
|
|
||||||
col: number;
|
|
||||||
};
|
|
||||||
function parseTOML(input: string, file?: string): TOML.JsonMap | never {
|
|
||||||
try {
|
|
||||||
// Normalize CRLF to LF to avoid hitting https://github.com/iarna/iarna-toml/issues/33.
|
|
||||||
const normalizedInput = input.replace(/\r\n/g, '\n');
|
|
||||||
return TOML.parse(normalizedInput);
|
|
||||||
} catch (err) {
|
|
||||||
const { name, message, line, col } = err as TomlError;
|
|
||||||
if (name !== TOML_ERROR_NAME) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
const text = message.substring(0, message.lastIndexOf(TOML_ERROR_SUFFIX));
|
|
||||||
const lineText = input.split('\n')[line];
|
|
||||||
const location = {
|
|
||||||
lineText,
|
|
||||||
line: line + 1,
|
|
||||||
column: col - 1,
|
|
||||||
file,
|
|
||||||
fileText: input,
|
|
||||||
};
|
|
||||||
throw new ParseError({ text, location });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DotEnv {
|
|
||||||
path: string;
|
|
||||||
parsed: dotenv.DotenvParseOutput;
|
|
||||||
}
|
|
||||||
function tryLoadDotEnv(path: string): DotEnv | undefined {
|
|
||||||
try {
|
|
||||||
const parsed = dotenv.parse(fs.readFileSync(path));
|
|
||||||
return { path, parsed };
|
|
||||||
} catch (e) {
|
|
||||||
// logger.debug(`Failed to load .env file "${path}":`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Loads a dotenv file from <path>, preferring to read <path>.<environment> if
|
|
||||||
* <environment> is defined and that file exists.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function loadDotEnv(path: string): DotEnv | undefined {
|
|
||||||
return tryLoadDotEnv(path);
|
|
||||||
}
|
|
||||||
function getVarsForDev(config: any, configPath: string | undefined): any {
|
|
||||||
const configDir = resolve(dirname(configPath ?? '.'));
|
|
||||||
const devVarsPath = resolve(configDir, '.dev.vars');
|
|
||||||
const loaded = loadDotEnv(devVarsPath);
|
|
||||||
if (loaded !== undefined) {
|
|
||||||
return {
|
|
||||||
...config.vars,
|
|
||||||
...loaded.parsed,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return config.vars;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseConfig() {
|
|
||||||
if (_wrangler) return _wrangler;
|
|
||||||
let rawConfig;
|
|
||||||
const configPath = findWranglerToml(process.cwd(), false); // false = args.experimentalJsonConfig
|
|
||||||
if (!configPath) {
|
|
||||||
throw new Error('Could not find wrangler.toml');
|
|
||||||
}
|
|
||||||
// Load the configuration from disk if available
|
|
||||||
if (configPath?.endsWith('toml')) {
|
|
||||||
rawConfig = parseTOML(fs.readFileSync(configPath).toString(), configPath);
|
|
||||||
}
|
|
||||||
_wrangler = { rawConfig, configPath };
|
|
||||||
return { rawConfig, configPath };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getEnvVars() {
|
|
||||||
const { rawConfig, configPath } = parseConfig();
|
|
||||||
const vars = getVarsForDev(rawConfig, configPath);
|
|
||||||
return vars;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getD1Bindings() {
|
|
||||||
const { rawConfig } = parseConfig();
|
|
||||||
if (!rawConfig) return [];
|
|
||||||
if (!rawConfig?.d1_databases) return [];
|
|
||||||
const bindings = (rawConfig?.d1_databases as []).map(
|
|
||||||
(binding: { binding: string }) => binding.binding
|
|
||||||
);
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getR2Bindings() {
|
|
||||||
const { rawConfig } = parseConfig();
|
|
||||||
if (!rawConfig) return [];
|
|
||||||
if (!rawConfig?.r2_buckets) return [];
|
|
||||||
const bindings = (rawConfig?.r2_buckets as []).map(
|
|
||||||
(binding: { binding: string }) => binding.binding
|
|
||||||
);
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getKVBindings() {
|
|
||||||
const { rawConfig } = parseConfig();
|
|
||||||
if (!rawConfig) return [];
|
|
||||||
if (!rawConfig?.kv_namespaces) return [];
|
|
||||||
const bindings = (rawConfig?.kv_namespaces as []).map(
|
|
||||||
(binding: { binding: string }) => binding.binding
|
|
||||||
);
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDOBindings(): Record<
|
|
||||||
string,
|
|
||||||
{ scriptName?: string | undefined; unsafeUniqueKey?: string | undefined; className: string }
|
|
||||||
> {
|
|
||||||
const { rawConfig } = parseConfig();
|
|
||||||
if (!rawConfig) return {};
|
|
||||||
if (!rawConfig?.durable_objects) return {};
|
|
||||||
const output = new Object({}) as Record<
|
|
||||||
string,
|
|
||||||
{ scriptName?: string | undefined; unsafeUniqueKey?: string | undefined; className: string }
|
|
||||||
>;
|
|
||||||
for (const binding of rawConfig?.durable_objects.bindings) {
|
|
||||||
Reflect.set(output, binding.name, { className: binding.class_name });
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
export function prependForwardSlash(path: string) {
|
|
||||||
return path[0] === '/' ? path : '/' + path;
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
import esbuild from 'esbuild';
|
|
||||||
import { basename } from 'node:path';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param relativePathToAssets - relative path from the final location for the current esbuild output bundle, to the assets directory.
|
|
||||||
*/
|
|
||||||
export function rewriteWasmImportPath({
|
|
||||||
relativePathToAssets,
|
|
||||||
}: {
|
|
||||||
relativePathToAssets: string;
|
|
||||||
}): esbuild.Plugin {
|
|
||||||
return {
|
|
||||||
name: 'wasm-loader',
|
|
||||||
setup(build) {
|
|
||||||
build.onResolve({ filter: /.*\.wasm.mjs$/ }, (args) => {
|
|
||||||
const updatedPath = [
|
|
||||||
relativePathToAssets.replaceAll('\\', '/'),
|
|
||||||
basename(args.path).replace(/\.mjs$/, ''),
|
|
||||||
].join('/');
|
|
||||||
|
|
||||||
return {
|
|
||||||
path: updatedPath,
|
|
||||||
external: true, // mark it as external in the bundle
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
import * as fs from 'node:fs';
|
|
||||||
import * as path from 'node:path';
|
|
||||||
import { type Plugin } from 'vite';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads '*.wasm?module' imports as WebAssembly modules, which is the only way to load WASM in cloudflare workers.
|
|
||||||
* Current proposal for WASM modules: https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration
|
|
||||||
* Cloudflare worker WASM from javascript support: https://developers.cloudflare.com/workers/runtime-apis/webassembly/javascript/
|
|
||||||
* @param disabled - if true throws a helpful error message if wasm is encountered and wasm imports are not enabled,
|
|
||||||
* otherwise it will error obscurely in the esbuild and vite builds
|
|
||||||
* @param assetsDirectory - the folder name for the assets directory in the build directory. Usually '_astro'
|
|
||||||
* @returns Vite plugin to load WASM tagged with '?module' as a WASM modules
|
|
||||||
*/
|
|
||||||
export function wasmModuleLoader({
|
|
||||||
disabled,
|
|
||||||
assetsDirectory,
|
|
||||||
}: {
|
|
||||||
disabled: boolean;
|
|
||||||
assetsDirectory: string;
|
|
||||||
}): Plugin {
|
|
||||||
const postfix = '.wasm?module';
|
|
||||||
let isDev = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: 'vite:wasm-module-loader',
|
|
||||||
enforce: 'pre',
|
|
||||||
configResolved(config) {
|
|
||||||
isDev = config.command === 'serve';
|
|
||||||
},
|
|
||||||
config(_, __) {
|
|
||||||
// let vite know that file format and the magic import string is intentional, and will be handled in this plugin
|
|
||||||
return {
|
|
||||||
assetsInclude: ['**/*.wasm?module'],
|
|
||||||
build: { rollupOptions: { external: /^__WASM_ASSET__.+\.wasm\.mjs$/i } },
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
load(id, _) {
|
|
||||||
if (!id.endsWith(postfix)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (disabled) {
|
|
||||||
throw new Error(
|
|
||||||
`WASM module's cannot be loaded unless you add \`wasmModuleImports: true\` to your astro config.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const filePath = id.slice(0, -1 * '?module'.length);
|
|
||||||
|
|
||||||
const data = fs.readFileSync(filePath);
|
|
||||||
const base64 = data.toString('base64');
|
|
||||||
|
|
||||||
const base64Module = `
|
|
||||||
const wasmModule = new WebAssembly.Module(Uint8Array.from(atob("${base64}"), c => c.charCodeAt(0)));
|
|
||||||
export default wasmModule
|
|
||||||
`;
|
|
||||||
if (isDev) {
|
|
||||||
// no need to wire up the assets in dev mode, just rewrite
|
|
||||||
return base64Module;
|
|
||||||
} else {
|
|
||||||
// just some shared ID
|
|
||||||
let hash = hashString(base64);
|
|
||||||
// emit the wasm binary as an asset file, to be picked up later by the esbuild bundle for the worker.
|
|
||||||
// give it a shared deterministic name to make things easy for esbuild to switch on later
|
|
||||||
const assetName = path.basename(filePath).split('.')[0] + '.' + hash + '.wasm';
|
|
||||||
this.emitFile({
|
|
||||||
type: 'asset',
|
|
||||||
// put it explicitly in the _astro assets directory with `fileName` rather than `name` so that
|
|
||||||
// vite doesn't give it a random id in its name. We need to be able to easily rewrite from
|
|
||||||
// the .mjs loader and the actual wasm asset later in the ESbuild for the worker
|
|
||||||
fileName: path.join(assetsDirectory, assetName),
|
|
||||||
source: fs.readFileSync(filePath),
|
|
||||||
});
|
|
||||||
|
|
||||||
// however, by default, the SSG generator cannot import the .wasm as a module, so embed as a base64 string
|
|
||||||
const chunkId = this.emitFile({
|
|
||||||
type: 'prebuilt-chunk',
|
|
||||||
fileName: assetName + '.mjs',
|
|
||||||
code: base64Module,
|
|
||||||
});
|
|
||||||
|
|
||||||
return `
|
|
||||||
import wasmModule from "__WASM_ASSET__${chunkId}.wasm.mjs";
|
|
||||||
export default wasmModule;
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// output original wasm file relative to the chunk
|
|
||||||
renderChunk(code, chunk, _) {
|
|
||||||
if (isDev) return;
|
|
||||||
|
|
||||||
if (!/__WASM_ASSET__/g.test(code)) return;
|
|
||||||
|
|
||||||
const final = code.replaceAll(/__WASM_ASSET__([a-z\d]+).wasm.mjs/g, (s, assetId) => {
|
|
||||||
const fileName = this.getFileName(assetId);
|
|
||||||
const relativePath = path
|
|
||||||
.relative(path.dirname(chunk.fileName), fileName)
|
|
||||||
.replaceAll('\\', '/'); // fix windows paths for import
|
|
||||||
return `./${relativePath}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { code: final };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a deterministic 32 bit hash code from a string
|
|
||||||
*/
|
|
||||||
function hashString(str: string): string {
|
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
const char = str.charCodeAt(i);
|
|
||||||
hash = (hash << 5) - hash + char;
|
|
||||||
hash &= hash; // Convert to 32bit integer
|
|
||||||
}
|
|
||||||
return new Uint32Array([hash])[0].toString(36);
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { loadFixture, runCLI } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import * as cheerio from 'cheerio';
|
|
||||||
|
|
||||||
describe('Basic app', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
/** @type {import('./test-utils').WranglerCLI} */
|
|
||||||
let cli;
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/basics/',
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
|
|
||||||
cli = await runCLI('./fixtures/basics/', {
|
|
||||||
silent: true,
|
|
||||||
onTimeout: (ex) => {
|
|
||||||
console.log(ex);
|
|
||||||
// if fail to start, skip for now as it's very flaky
|
|
||||||
this.skip();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await cli?.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can render', async () => {
|
|
||||||
let res = await fetch(`http://127.0.0.1:${cli.port}/`);
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
expect($('h1').text()).to.equal('Testing');
|
|
||||||
expect($('#env').text()).to.equal('secret');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,127 +0,0 @@
|
||||||
import { loadFixture, runCLI } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import * as cheerio from 'cheerio';
|
|
||||||
import cloudflare from '../dist/index.js';
|
|
||||||
|
|
||||||
describe('Wrangler Cloudflare Runtime', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
/** @type {import('./test-utils').WranglerCLI} */
|
|
||||||
let cli;
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/cf/',
|
|
||||||
output: 'server',
|
|
||||||
adapter: cloudflare(),
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
|
|
||||||
cli = await runCLI('./fixtures/cf/', {
|
|
||||||
silent: true,
|
|
||||||
onTimeout: (ex) => {
|
|
||||||
console.log(ex);
|
|
||||||
// if fail to start, skip for now as it's very flaky
|
|
||||||
this.skip();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await cli?.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Load cf and caches API', async () => {
|
|
||||||
let res = await fetch(`http://127.0.0.1:${cli.port}/`);
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
|
|
||||||
expect($('#hasRuntime').text()).to.equal('true');
|
|
||||||
expect($('#hasCache').text()).to.equal('true');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Astro Cloudflare Runtime', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
let devServer;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/cf/',
|
|
||||||
output: 'server',
|
|
||||||
adapter: cloudflare({
|
|
||||||
runtime: 'local',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
process.chdir('./test/fixtures/cf');
|
|
||||||
devServer = await fixture.startDevServer();
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await devServer?.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds cf object', async () => {
|
|
||||||
let res = await fixture.fetch('/');
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
expect($('#hasCF').text()).to.equal('true');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds cache mocking', async () => {
|
|
||||||
let res = await fixture.fetch('/caches');
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
expect($('#hasCACHE').text()).to.equal('true');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds D1 mocking', async () => {
|
|
||||||
expect(await fixture.pathExists('../.mf/d1')).to.be.true;
|
|
||||||
|
|
||||||
let res = await fixture.fetch('/d1');
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
expect($('#hasDB').text()).to.equal('true');
|
|
||||||
expect($('#hasPRODDB').text()).to.equal('true');
|
|
||||||
expect($('#hasACCESS').text()).to.equal('true');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds R2 mocking', async () => {
|
|
||||||
expect(await fixture.pathExists('../.mf/r2')).to.be.true;
|
|
||||||
|
|
||||||
let res = await fixture.fetch('/r2');
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
expect($('#hasBUCKET').text()).to.equal('true');
|
|
||||||
expect($('#hasPRODBUCKET').text()).to.equal('true');
|
|
||||||
expect($('#hasACCESS').text()).to.equal('true');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds KV mocking', async () => {
|
|
||||||
expect(await fixture.pathExists('../.mf/kv')).to.be.true;
|
|
||||||
|
|
||||||
let res = await fixture.fetch('/kv');
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
expect($('#hasKV').text()).to.equal('true');
|
|
||||||
expect($('#hasPRODKV').text()).to.equal('true');
|
|
||||||
expect($('#hasACCESS').text()).to.equal('true');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('adds DO mocking', async () => {
|
|
||||||
expect(await fixture.pathExists('../.mf/do')).to.be.true;
|
|
||||||
|
|
||||||
let res = await fixture.fetch('/do');
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
expect($('#hasDO').text()).to.equal('true');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { loadFixture } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import cloudflare from '../dist/index.js';
|
|
||||||
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
describe('mode: "directory"', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/basics/',
|
|
||||||
output: 'server',
|
|
||||||
adapter: cloudflare({ mode: 'directory' }),
|
|
||||||
redirects: {
|
|
||||||
'/old': '/',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('generates functions folder inside the project root', async () => {
|
|
||||||
expect(await fixture.pathExists('../functions')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/[[path]].js')).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('generates a redirects file', async () => {
|
|
||||||
try {
|
|
||||||
let _redirects = await fixture.readFile('/_redirects');
|
|
||||||
let parts = _redirects.split(/\s+/);
|
|
||||||
expect(parts).to.deep.equal(['/old', '/', '301']);
|
|
||||||
} catch {
|
|
||||||
expect(false).to.equal(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
// test env var
|
|
||||||
process.env.SECRET_STUFF = 'secret'
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare(),
|
|
||||||
output: 'server'
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-basics",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Testing</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Testing</h1>
|
|
||||||
<div id="env">{import.meta.env.SECRET_STUFF}</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1 +0,0 @@
|
||||||
DATABASE_URL="postgresql://lorem"
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-cf",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
---
|
|
||||||
const runtime = Astro.locals.runtime;
|
|
||||||
---
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>CACHES</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<pre id="hasCACHE">{!!runtime.caches}</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
const runtime = Astro.locals.runtime;
|
|
||||||
const db = runtime.env?.D1;
|
|
||||||
await db.exec("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)");
|
|
||||||
await db.exec("INSERT INTO test (name) VALUES ('true')");
|
|
||||||
const result = await db.prepare("SELECT * FROM test").all();
|
|
||||||
---
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>D1</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<pre id="hasDB">{!!runtime.env?.D1}</pre>
|
|
||||||
<pre id="hasPRODDB">{!!runtime.env?.D1_PROD}</pre>
|
|
||||||
<pre id="hasACCESS">{!!result.results[0].name}</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,15 +0,0 @@
|
||||||
---
|
|
||||||
const runtime = Astro.locals.runtime;
|
|
||||||
---
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>DO</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<pre id="hasDO">{!!runtime.env.DO}</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,12 +0,0 @@
|
||||||
---
|
|
||||||
const runtime = Astro.locals.runtime;
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Testing</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Testing</h1>
|
|
||||||
<div id="hasCF">{!!runtime.cf?.colo}</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
const runtime = Astro.locals.runtime;
|
|
||||||
const kv = runtime.env?.KV;
|
|
||||||
await kv.put("test", "true");
|
|
||||||
const result = await kv.get("test")
|
|
||||||
---
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>KV</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<pre id="hasKV">{!!runtime.env?.KV}</pre>
|
|
||||||
<pre id="hasPRODKV">{!!runtime.env?.KV_PROD}</pre>
|
|
||||||
<pre id="hasACCESS">{!!result}</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
const runtime = Astro.locals.runtime;
|
|
||||||
const bucket = runtime.env?.R2;
|
|
||||||
await bucket.put("test", "true");
|
|
||||||
const result = await (await bucket.get("test")).text()
|
|
||||||
---
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>R2</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<pre id="hasBUCKET">{!!runtime.env?.R2}</pre>
|
|
||||||
<pre id="hasPRODBUCKET">{!!runtime.env?.R2_PROD}</pre>
|
|
||||||
<pre id="hasACCESS">{!!result}</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,37 +0,0 @@
|
||||||
name = "test"
|
|
||||||
|
|
||||||
kv_namespaces = [
|
|
||||||
{ binding = "KV", id = "<YOUR_ID>", preview_id = "<YOUR_ID>" },
|
|
||||||
{ binding = "KV_PROD", id = "<YOUR_ID>", preview_id = "<YOUR_ID>" }
|
|
||||||
]
|
|
||||||
|
|
||||||
[vars]
|
|
||||||
COOL = "ME"
|
|
||||||
|
|
||||||
[[d1_databases]]
|
|
||||||
binding = "D1" # Should match preview_database_id, i.e. available in your Worker on env.DB
|
|
||||||
database_name = "<DATABASE_NAME>"
|
|
||||||
database_id = "<unique-ID-for-your-database>"
|
|
||||||
preview_database_id = "D1" # Required for Pages local development
|
|
||||||
|
|
||||||
[[d1_databases]]
|
|
||||||
binding = "D1_PROD" # Should match preview_database_id
|
|
||||||
database_name = "<DATABASE_NAME>"
|
|
||||||
database_id = "<unique-ID-for-your-database>"
|
|
||||||
preview_database_id = "D1_PROD" # Required for Pages local development
|
|
||||||
|
|
||||||
[[r2_buckets]]
|
|
||||||
binding = 'R2' # <~ valid JavaScript variable name
|
|
||||||
bucket_name = '<YOUR_BUCKET_NAME>'
|
|
||||||
|
|
||||||
[[r2_buckets]]
|
|
||||||
binding = 'R2_PROD' # <~ valid JavaScript variable name
|
|
||||||
bucket_name = '<YOUR_BUCKET_NAME>'
|
|
||||||
|
|
||||||
[[durable_objects.bindings]]
|
|
||||||
name = "DO"
|
|
||||||
class_name = "DurableObjectExample"
|
|
||||||
|
|
||||||
[[durable_objects.bindings]]
|
|
||||||
name = "DO_PROD"
|
|
||||||
class_name = "DurableObjectProductionExample"
|
|
|
@ -1,15 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode: 'directory',
|
|
||||||
functionPerRoute: true
|
|
||||||
}),
|
|
||||||
output: 'server',
|
|
||||||
vite: {
|
|
||||||
build: {
|
|
||||||
minify: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-function-per-route",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { defineMiddleware } from "astro/middleware";
|
|
||||||
|
|
||||||
export const onRequest = defineMiddleware(({ locals, request }, next) => {
|
|
||||||
// intercept response data from a request
|
|
||||||
// optionally, transform the response by modifying `locals`
|
|
||||||
locals.title = "New title"
|
|
||||||
|
|
||||||
// return a Response or the result of calling `next()`
|
|
||||||
return next()
|
|
||||||
});
|
|
|
@ -1,37 +0,0 @@
|
||||||
---
|
|
||||||
const files = [
|
|
||||||
{
|
|
||||||
slug: undefined,
|
|
||||||
title: 'Root level',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'test.png',
|
|
||||||
title: "One level"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'assets/test.png',
|
|
||||||
title: "Two levels"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'assets/images/test.png',
|
|
||||||
title: 'Three levels',
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const { path } = Astro.params;
|
|
||||||
const page = files.find((page) => page.slug === path);
|
|
||||||
const { title } = page;
|
|
||||||
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>Files / Rest Parameters / {title}</h1>
|
|
||||||
<p>DEBUG: {path} </p>
|
|
||||||
<p><a href="/">index</a></p>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
h1 {
|
|
||||||
background-color: yellow;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
|
@ -1,14 +0,0 @@
|
||||||
---
|
|
||||||
const { person, car } = Astro.params;
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1> {person} / {car}</h1>
|
|
||||||
<p><a href="/">index</a></p>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
h1 {
|
|
||||||
background-color: blue;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
|
@ -1,14 +0,0 @@
|
||||||
---
|
|
||||||
const { post } = Astro.params;
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>Blog / {post}</h1>
|
|
||||||
<p><a href="/">index</a></p>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
h1 {
|
|
||||||
background-color: pink;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>Blog / Cool</h1>
|
|
||||||
<p><a href="/">index</a></p>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
h1 {
|
|
||||||
background-color: orange;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
|
@ -1,37 +0,0 @@
|
||||||
---
|
|
||||||
const files = [
|
|
||||||
{
|
|
||||||
slug: undefined,
|
|
||||||
title: 'Root level',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'test.png',
|
|
||||||
title: "One level"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'assets/test.png',
|
|
||||||
title: "Two levels"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
slug: 'assets/images/test.png',
|
|
||||||
title: 'Three levels',
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const { path } = Astro.params;
|
|
||||||
const page = files.find((page) => page.slug === path);
|
|
||||||
const { title } = page;
|
|
||||||
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>Files / Rest Parameters / {title}</h1>
|
|
||||||
<p>DEBUG: {path} </p>
|
|
||||||
<p><a href="/">index</a></p>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
h1 {
|
|
||||||
background-color: yellow;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
|
@ -1,22 +0,0 @@
|
||||||
---
|
|
||||||
const data = Astro.locals;
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>Index</h1>
|
|
||||||
<p>Middleware ({data.title})</p>
|
|
||||||
<p><a href="/prerender/">prerender</a></p>
|
|
||||||
<p><a href="/blog/cool/">sub-route</a></p>
|
|
||||||
<p><a href="/blog/dynamic-post/">dynamic route in static sub-route</a></p>
|
|
||||||
<p><a href="/mustermann/bmw/">dynamic route in dynamic sub-route</a></p>
|
|
||||||
<p><a href="/files/">rest parameters root level</a></p>
|
|
||||||
<p><a href="/files/test.png/">rest parameters one level</a></p>
|
|
||||||
<p><a href="/files/assets/test.png/">rest parameters two level</a></p>
|
|
||||||
<p><a href="/files/assets/images/test.png/">rest parameters three level</a></p>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
h1 {
|
|
||||||
background-color: red;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
|
@ -1,14 +0,0 @@
|
||||||
---
|
|
||||||
export const prerender = true;
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>Prerender</h1>
|
|
||||||
<p><a href="/">index</a></p>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
h1 {
|
|
||||||
background-color: yellow;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare()
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-no-output",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare(),
|
|
||||||
output: 'server',
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-prerender",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Testing</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Testing</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,11 +0,0 @@
|
||||||
---
|
|
||||||
export const prerender = import.meta.env.PRERENDER;
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Testing</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Testing</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
// adapter will be set dynamically by the test
|
|
||||||
output: 'hybrid',
|
|
||||||
redirects: {
|
|
||||||
'/a/redirect': '/',
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-routes-json",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
/redirectme / 302
|
|
|
@ -1 +0,0 @@
|
||||||
ok
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
export const prerender=false;
|
|
||||||
---
|
|
||||||
|
|
||||||
ok
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
export const prerender=false;
|
|
||||||
---
|
|
||||||
|
|
||||||
ok
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
export const prerender=false;
|
|
||||||
---
|
|
||||||
|
|
||||||
ok
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
export const prerender=false;
|
|
||||||
---
|
|
||||||
|
|
||||||
ok
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
export const prerender=false;
|
|
||||||
---
|
|
||||||
|
|
||||||
ok
|
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
export const prerender=false;
|
|
||||||
---
|
|
||||||
|
|
||||||
ok
|
|
|
@ -1 +0,0 @@
|
||||||
export const prerender = false;
|
|
|
@ -1 +0,0 @@
|
||||||
ok
|
|
|
@ -1 +0,0 @@
|
||||||
ok
|
|
|
@ -1 +0,0 @@
|
||||||
ok
|
|
|
@ -1,8 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare(),
|
|
||||||
output: 'server',
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-runtime",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
---
|
|
||||||
const runtime = Astro.locals.runtime;
|
|
||||||
const env = runtime.env;
|
|
||||||
---
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Testing</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Testing</h1>
|
|
||||||
<div id="env">{JSON.stringify(env)}</div>
|
|
||||||
<div id="hasRuntime">{!!runtime.cf?.colo}</div>
|
|
||||||
<div id="hasCache">{!!runtime.caches}</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode: 'directory',
|
|
||||||
wasmModuleImports: true
|
|
||||||
}),
|
|
||||||
output: 'server'
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-wasm-function-per-route",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import { type APIContext, type EndpointOutput } from 'astro';
|
|
||||||
// @ts-ignore
|
|
||||||
import mod from '../util/add.wasm?module';
|
|
||||||
|
|
||||||
const addModule: any = new WebAssembly.Instance(mod);
|
|
||||||
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
context: APIContext
|
|
||||||
): Promise<EndpointOutput | Response> {
|
|
||||||
|
|
||||||
return new Response(JSON.stringify({ answer: addModule.exports.add(40, 2) }), {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,12 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode: 'directory',
|
|
||||||
functionPerRoute: true,
|
|
||||||
wasmModuleImports: true
|
|
||||||
}),
|
|
||||||
output: 'server',
|
|
||||||
vite: { build: { minify: false } }
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-wasm-directory",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { type APIContext, type EndpointOutput } from 'astro';
|
|
||||||
import { add } from '../../../util/add';
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
context: APIContext
|
|
||||||
): Promise<EndpointOutput | Response> {
|
|
||||||
|
|
||||||
return new Response(JSON.stringify({ answer: add(80, 4) }), {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { type APIContext, type EndpointOutput } from 'astro';
|
|
||||||
import { add } from '../util/add';
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
context: APIContext
|
|
||||||
): Promise<EndpointOutput | Response> {
|
|
||||||
|
|
||||||
return new Response(JSON.stringify({ answer: add(40, 2) }), {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
// extra layer of indirection to stress the esbuild
|
|
||||||
import { addImpl } from "./indirection";
|
|
||||||
|
|
||||||
export function add(a: number, b: number): number {
|
|
||||||
return addImpl(a, b);
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,9 +0,0 @@
|
||||||
// extra layer of indirection to stress the esbuild
|
|
||||||
// @ts-ignore
|
|
||||||
import mod from './add.wasm?module';
|
|
||||||
|
|
||||||
const addModule: any = new WebAssembly.Instance(mod);
|
|
||||||
|
|
||||||
export function addImpl(a: number, b: number): number {
|
|
||||||
return addModule.exports.add(a, b);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
adapter: cloudflare({
|
|
||||||
wasmModuleImports: true
|
|
||||||
}),
|
|
||||||
output: 'server'
|
|
||||||
});
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-wasm",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"astro": "workspace:*"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import { type APIContext, type EndpointOutput } from 'astro';
|
|
||||||
// @ts-ignore
|
|
||||||
import mod from '../../../util/add.wasm?module';
|
|
||||||
|
|
||||||
const addModule: any = new WebAssembly.Instance(mod);
|
|
||||||
|
|
||||||
export const prerender = false;
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
context: APIContext
|
|
||||||
): Promise<EndpointOutput | Response> {
|
|
||||||
const a = Number.parseInt(context.params.a!);
|
|
||||||
const b = Number.parseInt(context.params.b!);
|
|
||||||
return new Response(JSON.stringify({ answer: addModule.exports.add(a, b) }), {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { type APIContext, type EndpointOutput } from 'astro';
|
|
||||||
// @ts-ignore
|
|
||||||
import mod from '../util/add.wasm?module';
|
|
||||||
|
|
||||||
const addModule: any = new WebAssembly.Instance(mod);
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
context: APIContext
|
|
||||||
): Promise<EndpointOutput | Response> {
|
|
||||||
return new Response(JSON.stringify({ answer: addModule.exports.add(20, 1) }), {
|
|
||||||
status: 200,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,9 +0,0 @@
|
||||||
import { defineConfig } from 'astro/config';
|
|
||||||
import cloudflare from '@astrojs/cloudflare';
|
|
||||||
import solidJs from "@astrojs/solid-js";
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
integrations: [solidJs()],
|
|
||||||
adapter: cloudflare(),
|
|
||||||
output: 'server',
|
|
||||||
});
|
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@test/astro-cloudflare-with-solid-js",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@astrojs/cloudflare": "workspace:*",
|
|
||||||
"@astrojs/solid-js": "workspace:*",
|
|
||||||
"astro": "workspace:*",
|
|
||||||
"solid-js": "^1.7.11"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export const Component = () => <div class="solid">Solid Content</div>
|
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
import {Component} from "../components/Component";
|
|
||||||
---
|
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Testing</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Testing</h1>
|
|
||||||
<Component />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { loadFixture } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
|
|
||||||
/** @type {import('./test-utils.js').Fixture} */
|
|
||||||
describe('Cloudflare SSR functionPerRoute', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/function-per-route/',
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
fixture?.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('generates functions folders inside the project root, and checks that each page is emitted by astro', async () => {
|
|
||||||
expect(await fixture.pathExists('../functions')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/index.js')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/blog/cool.js')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/blog/[post].js')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/[person]/[car].js')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/files/[[path]].js')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/[language]/files/[[path]].js')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/trpc/[trpc].js')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/javascript.js')).to.be.true;
|
|
||||||
expect(await fixture.pathExists('../functions/test.json.js')).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('generates pre-rendered files', async () => {
|
|
||||||
expect(await fixture.pathExists('./prerender/index.html')).to.be.true;
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { loadFixture } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
|
|
||||||
describe('Missing output config', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/no-output/',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('throws during the build', async () => {
|
|
||||||
let error = undefined;
|
|
||||||
try {
|
|
||||||
await fixture.build();
|
|
||||||
} catch (err) {
|
|
||||||
error = err;
|
|
||||||
}
|
|
||||||
expect(error).to.not.be.equal(undefined);
|
|
||||||
expect(error.message).to.include(`output: "server"`);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,58 +0,0 @@
|
||||||
import { loadFixture } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
|
|
||||||
describe('Prerendering', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
process.env.PRERENDER = true;
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/prerender/',
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
delete process.env.PRERENDER;
|
|
||||||
fixture.clean();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('includes non prerendered routes in the routes.json config', async () => {
|
|
||||||
const foundRoutes = JSON.parse(await fixture.readFile('/_routes.json'));
|
|
||||||
|
|
||||||
expect(foundRoutes).to.deep.equal({
|
|
||||||
version: 1,
|
|
||||||
include: ['/', '/_image'],
|
|
||||||
exclude: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Hybrid rendering', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
process.env.PRERENDER = false;
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/prerender/',
|
|
||||||
output: 'hybrid',
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
after(() => {
|
|
||||||
delete process.env.PRERENDER;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('includes non prerendered routes in the routes.json config', async () => {
|
|
||||||
const foundRoutes = JSON.parse(await fixture.readFile('/_routes.json'));
|
|
||||||
|
|
||||||
expect(foundRoutes).to.deep.equal({
|
|
||||||
version: 1,
|
|
||||||
include: ['/one', '/_image'],
|
|
||||||
exclude: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,211 +0,0 @@
|
||||||
import { expect } from 'chai';
|
|
||||||
import { loadFixture } from './test-utils.js';
|
|
||||||
import cloudflare from '../dist/index.js';
|
|
||||||
|
|
||||||
/** @type {import('./test-utils.js').Fixture} */
|
|
||||||
describe('_routes.json generation', () => {
|
|
||||||
for (const mode of ['directory', 'advanced']) {
|
|
||||||
for (const functionPerRoute of [false, true]) {
|
|
||||||
describe(`with mode=${mode}, functionPerRoute=${functionPerRoute}`, () => {
|
|
||||||
describe('of both functions and static files', () => {
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/routes-json/',
|
|
||||||
srcDir: './src/mixed',
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode,
|
|
||||||
functionPerRoute,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates `include` for functions and `exclude` for static files where needed', async () => {
|
|
||||||
const _routesJson = await fixture.readFile('/_routes.json');
|
|
||||||
const routes = JSON.parse(_routesJson);
|
|
||||||
|
|
||||||
expect(routes).to.deep.equal({
|
|
||||||
version: 1,
|
|
||||||
include: ['/a/*', '/_image'],
|
|
||||||
exclude: ['/a/', '/a/redirect', '/a/index.html'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('of only functions', () => {
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/routes-json/',
|
|
||||||
srcDir: './src/dynamicOnly',
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode,
|
|
||||||
functionPerRoute,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates a wildcard `include` and `exclude` only for static assets and redirects', async () => {
|
|
||||||
const _routesJson = await fixture.readFile('/_routes.json');
|
|
||||||
const routes = JSON.parse(_routesJson);
|
|
||||||
|
|
||||||
expect(routes).to.deep.equal({
|
|
||||||
version: 1,
|
|
||||||
include: ['/*'],
|
|
||||||
exclude: ['/public.txt', '/redirectme', '/a/redirect'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('of only static files', () => {
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/routes-json/',
|
|
||||||
srcDir: './src/staticOnly',
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode,
|
|
||||||
functionPerRoute,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('create only one `include` and `exclude` that are supposed to match nothing', async () => {
|
|
||||||
const _routesJson = await fixture.readFile('/_routes.json');
|
|
||||||
const routes = JSON.parse(_routesJson);
|
|
||||||
|
|
||||||
expect(routes).to.deep.equal({
|
|
||||||
version: 1,
|
|
||||||
include: ['/_image'],
|
|
||||||
exclude: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with strategy `"include"`', () => {
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/routes-json/',
|
|
||||||
srcDir: './src/dynamicOnly',
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode,
|
|
||||||
functionPerRoute,
|
|
||||||
routes: { strategy: 'include' },
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates `include` entries even though the `"exclude"` strategy would have produced less entries.', async () => {
|
|
||||||
const _routesJson = await fixture.readFile('/_routes.json');
|
|
||||||
const routes = JSON.parse(_routesJson);
|
|
||||||
|
|
||||||
expect(routes).to.deep.equal({
|
|
||||||
version: 1,
|
|
||||||
include: ['/', '/_image', '/dynamic1', '/dynamic2', '/dynamic3'],
|
|
||||||
exclude: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with strategy `"exclude"`', () => {
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/routes-json/',
|
|
||||||
srcDir: './src/staticOnly',
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode,
|
|
||||||
functionPerRoute,
|
|
||||||
routes: { strategy: 'exclude' },
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates `exclude` entries even though the `"include"` strategy would have produced less entries.', async () => {
|
|
||||||
const _routesJson = await fixture.readFile('/_routes.json');
|
|
||||||
const routes = JSON.parse(_routesJson);
|
|
||||||
|
|
||||||
expect(routes).to.deep.equal({
|
|
||||||
version: 1,
|
|
||||||
include: ['/*'],
|
|
||||||
exclude: ['/', '/index.html', '/public.txt', '/redirectme', '/a/redirect'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with additional `include` entries', () => {
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/routes-json/',
|
|
||||||
srcDir: './src/mixed',
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode,
|
|
||||||
functionPerRoute,
|
|
||||||
routes: {
|
|
||||||
strategy: 'include',
|
|
||||||
include: ['/another', '/a/redundant'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates `include` for functions and `exclude` for static files where needed', async () => {
|
|
||||||
const _routesJson = await fixture.readFile('/_routes.json');
|
|
||||||
const routes = JSON.parse(_routesJson);
|
|
||||||
|
|
||||||
expect(routes).to.deep.equal({
|
|
||||||
version: 1,
|
|
||||||
include: ['/a/*', '/_image', '/another'],
|
|
||||||
exclude: ['/a/', '/a/redirect', '/a/index.html'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with additional `exclude` entries', () => {
|
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/routes-json/',
|
|
||||||
srcDir: './src/mixed',
|
|
||||||
adapter: cloudflare({
|
|
||||||
mode,
|
|
||||||
functionPerRoute,
|
|
||||||
routes: {
|
|
||||||
strategy: 'include',
|
|
||||||
exclude: ['/another', '/a/*', '/a/index.html'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('creates `include` for functions and `exclude` for static files where needed', async () => {
|
|
||||||
const _routesJson = await fixture.readFile('/_routes.json');
|
|
||||||
const routes = JSON.parse(_routesJson);
|
|
||||||
|
|
||||||
expect(routes).to.deep.equal({
|
|
||||||
version: 1,
|
|
||||||
include: ['/a/*', '/_image'],
|
|
||||||
exclude: ['/a/', '/a/*', '/another'],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { loadFixture, runCLI } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import * as cheerio from 'cheerio';
|
|
||||||
import cloudflare from '../dist/index.js';
|
|
||||||
|
|
||||||
describe('Runtime Locals', () => {
|
|
||||||
/** @type {import('./test-utils.js').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
/** @type {import('./test-utils.js').WranglerCLI} */
|
|
||||||
let cli;
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/runtime/',
|
|
||||||
output: 'server',
|
|
||||||
adapter: cloudflare(),
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
|
|
||||||
cli = await runCLI('./fixtures/runtime/', {
|
|
||||||
silent: true,
|
|
||||||
onTimeout: (ex) => {
|
|
||||||
console.log(ex);
|
|
||||||
// if fail to start, skip for now as it's very flaky
|
|
||||||
this.skip();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await cli?.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('has CF and Caches', async () => {
|
|
||||||
let res = await fetch(`http://127.0.0.1:${cli.port}/`);
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
expect($('#env').text()).to.contain('SECRET_STUFF');
|
|
||||||
expect($('#env').text()).to.contain('secret');
|
|
||||||
expect($('#hasRuntime').text()).to.contain('true');
|
|
||||||
expect($('#hasCache').text()).to.equal('true');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,169 +0,0 @@
|
||||||
import { spawn } from 'node:child_process';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import { loadFixture as baseLoadFixture } from '../../../astro/test/test-utils.js';
|
|
||||||
import * as net from 'node:net';
|
|
||||||
export { fixLineEndings } from '../../../astro/test/test-utils.js';
|
|
||||||
/**
|
|
||||||
* @typedef {{ stop: Promise<void>, port: number }} WranglerCLI
|
|
||||||
* @typedef {import('../../../astro/test/test-utils').Fixture} Fixture
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function loadFixture(config) {
|
|
||||||
if (config?.root) {
|
|
||||||
config.root = new URL(config.root, import.meta.url);
|
|
||||||
}
|
|
||||||
return baseLoadFixture(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
const wranglerPath = fileURLToPath(
|
|
||||||
new URL('../node_modules/wrangler/bin/wrangler.js', import.meta.url)
|
|
||||||
);
|
|
||||||
|
|
||||||
let lastPort = 8788;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise<WranglerCLI>}
|
|
||||||
*/
|
|
||||||
export async function runCLI(
|
|
||||||
basePath,
|
|
||||||
{
|
|
||||||
silent,
|
|
||||||
maxAttempts = 3,
|
|
||||||
timeoutMillis = 2500, // really short because it often seems to just hang on the first try, but work subsequently, no matter the wait
|
|
||||||
backoffFactor = 2, // | - 2.5s -- 5s ---- 10s -> onTimeout
|
|
||||||
onTimeout = (ex) => {
|
|
||||||
new Error(`Timed out starting the wrangler CLI after ${maxAttempts} tries.`, { cause: ex });
|
|
||||||
},
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
let triesRemaining = maxAttempts;
|
|
||||||
let timeout = timeoutMillis;
|
|
||||||
let cli;
|
|
||||||
let lastErr;
|
|
||||||
while (triesRemaining > 0) {
|
|
||||||
cli = await tryRunCLI(basePath, {
|
|
||||||
silent,
|
|
||||||
timeout,
|
|
||||||
forceRotatePort: triesRemaining !== maxAttempts,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await cli.ready;
|
|
||||||
return cli;
|
|
||||||
} catch (err) {
|
|
||||||
lastErr = err;
|
|
||||||
console.error((err.message || err.name || err) + ' after ' + timeout + 'ms');
|
|
||||||
cli.stop();
|
|
||||||
triesRemaining -= 1;
|
|
||||||
timeout *= backoffFactor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onTimeout(lastErr);
|
|
||||||
return cli;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function tryRunCLI(basePath, { silent, timeout, forceRotatePort = false }) {
|
|
||||||
const port = await getNextOpenPort(lastPort + (forceRotatePort ? 1 : 0));
|
|
||||||
lastPort = port;
|
|
||||||
|
|
||||||
const fixtureDir = fileURLToPath(new URL(`${basePath}`, import.meta.url));
|
|
||||||
const p = spawn(
|
|
||||||
'node',
|
|
||||||
[
|
|
||||||
wranglerPath,
|
|
||||||
'pages',
|
|
||||||
'dev',
|
|
||||||
'dist',
|
|
||||||
'--port',
|
|
||||||
port,
|
|
||||||
'--log-level',
|
|
||||||
'info',
|
|
||||||
'--persist-to',
|
|
||||||
'.wrangler/state',
|
|
||||||
],
|
|
||||||
{
|
|
||||||
cwd: fixtureDir,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
p.stderr.setEncoding('utf-8');
|
|
||||||
p.stdout.setEncoding('utf-8');
|
|
||||||
|
|
||||||
const ready = new Promise(async (resolve, reject) => {
|
|
||||||
const failed = setTimeout(() => {
|
|
||||||
p.kill('SIGKILL');
|
|
||||||
reject(new Error(`Timed out starting the wrangler CLI`));
|
|
||||||
}, timeout);
|
|
||||||
|
|
||||||
const success = () => {
|
|
||||||
clearTimeout(failed);
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
p.on('exit', (code) => reject(`wrangler terminated unexpectedly with exit code ${code}`));
|
|
||||||
|
|
||||||
p.stderr.on('data', (data) => {
|
|
||||||
if (!silent) {
|
|
||||||
process.stdout.write(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let allData = '';
|
|
||||||
p.stdout.on('data', (data) => {
|
|
||||||
if (!silent) {
|
|
||||||
process.stdout.write(data);
|
|
||||||
}
|
|
||||||
allData += data;
|
|
||||||
if (allData.includes(`[mf:inf] Ready on`)) {
|
|
||||||
success();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
port,
|
|
||||||
ready,
|
|
||||||
stop() {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
p.kill('SIGKILL');
|
|
||||||
}, 1000);
|
|
||||||
p.on('close', () => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
p.on('error', (err) => reject(err));
|
|
||||||
p.kill();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPortOpen = async (port) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let s = net.createServer();
|
|
||||||
s.once('error', (err) => {
|
|
||||||
s.close();
|
|
||||||
if (err['code'] == 'EADDRINUSE') {
|
|
||||||
resolve(false);
|
|
||||||
} else {
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
s.once('listening', () => {
|
|
||||||
resolve(true);
|
|
||||||
s.close();
|
|
||||||
});
|
|
||||||
s.listen(port, '0.0.0.0');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getNextOpenPort = async (startFrom) => {
|
|
||||||
let openPort = null;
|
|
||||||
while (startFrom < 65535 || !!openPort) {
|
|
||||||
if (await isPortOpen(startFrom)) {
|
|
||||||
openPort = startFrom;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
startFrom++;
|
|
||||||
}
|
|
||||||
return openPort;
|
|
||||||
};
|
|
|
@ -1,36 +0,0 @@
|
||||||
import { loadFixture, runCLI } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
|
|
||||||
describe('Wasm directory mode import', () => {
|
|
||||||
/** @type {import('./test-utils.js').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
/** @type {import('./test-utils.js').WranglerCLI} */
|
|
||||||
let cli;
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/wasm-directory/',
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
|
|
||||||
cli = await runCLI('./fixtures/wasm-directory/', {
|
|
||||||
silent: true,
|
|
||||||
onTimeout: (ex) => {
|
|
||||||
console.log(ex);
|
|
||||||
// if fail to start, skip for now as it's very flaky
|
|
||||||
this.skip();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await cli?.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can render', async () => {
|
|
||||||
let res = await fetch(`http://127.0.0.1:${cli.port}/`);
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
const json = await res.json();
|
|
||||||
expect(json).to.deep.equal({ answer: 42 });
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { loadFixture, runCLI } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
|
|
||||||
describe('Wasm function per route import', () => {
|
|
||||||
/** @type {import('./test-utils.js').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
/** @type {import('./test-utils.js').WranglerCLI} */
|
|
||||||
let cli;
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/wasm-function-per-route/',
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
|
|
||||||
cli = await runCLI('./fixtures/wasm-function-per-route/', {
|
|
||||||
silent: true,
|
|
||||||
onTimeout: (ex) => {
|
|
||||||
console.log(ex);
|
|
||||||
// if fail to start, skip for now as it's very flaky
|
|
||||||
this.skip();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await cli?.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can render', async () => {
|
|
||||||
let res = await fetch(`http://127.0.0.1:${cli.port}/`);
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let json = await res.json();
|
|
||||||
expect(json).to.deep.equal({ answer: 42 });
|
|
||||||
|
|
||||||
res = await fetch(`http://127.0.0.1:${cli.port}/deeply/nested/route`);
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
json = await res.json();
|
|
||||||
expect(json).to.deep.equal({ answer: 84 });
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,85 +0,0 @@
|
||||||
import { loadFixture, runCLI } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import cloudflare from '../dist/index.js';
|
|
||||||
|
|
||||||
describe('Wasm import', () => {
|
|
||||||
describe('in cloudflare workerd', () => {
|
|
||||||
/** @type {import('./test-utils.js').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
/** @type {import('./test-utils.js').WranglerCLI} */
|
|
||||||
let cli;
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/wasm/',
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
|
|
||||||
cli = await runCLI('./fixtures/wasm/', {
|
|
||||||
silent: true,
|
|
||||||
onTimeout: (ex) => {
|
|
||||||
console.log(ex);
|
|
||||||
// if fail to start, skip for now as it's very flaky
|
|
||||||
this.skip();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await cli?.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can render', async () => {
|
|
||||||
let res = await fetch(`http://127.0.0.1:${cli.port}/add/40/2`);
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
const json = await res.json();
|
|
||||||
expect(json).to.deep.equal({ answer: 42 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('astro dev server', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
let devServer;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/wasm/',
|
|
||||||
});
|
|
||||||
devServer = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await devServer?.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can serve wasm', async () => {
|
|
||||||
devServer = await fixture.startDevServer();
|
|
||||||
let res = await fetch(`http://localhost:${devServer.address.port}/add/60/3`);
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
const json = await res.json();
|
|
||||||
expect(json).to.deep.equal({ answer: 63 });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('fails to build intelligently when wasm is disabled', async () => {
|
|
||||||
let ex;
|
|
||||||
try {
|
|
||||||
await fixture.build({
|
|
||||||
adapter: cloudflare({
|
|
||||||
wasmModuleImports: false,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
ex = err;
|
|
||||||
}
|
|
||||||
expect(ex?.message).to.have.string('add `wasmModuleImports: true` to your astro config');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can import wasm in both SSR and SSG pages', async () => {
|
|
||||||
await fixture.build({ output: 'hybrid' });
|
|
||||||
const staticContents = await fixture.readFile('./hybrid');
|
|
||||||
expect(staticContents).to.be.equal('{"answer":21}');
|
|
||||||
const assets = await fixture.readdir('./_astro');
|
|
||||||
expect(assets.map((x) => x.slice(x.lastIndexOf('.')))).to.contain('.wasm');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { loadFixture, runCLI } from './test-utils.js';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import * as cheerio from 'cheerio';
|
|
||||||
|
|
||||||
describe('With SolidJS', () => {
|
|
||||||
/** @type {import('./test-utils').Fixture} */
|
|
||||||
let fixture;
|
|
||||||
/** @type {import('./test-utils').WranglerCLI} */
|
|
||||||
let cli;
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
fixture = await loadFixture({
|
|
||||||
root: './fixtures/with-solid-js/',
|
|
||||||
});
|
|
||||||
await fixture.build();
|
|
||||||
|
|
||||||
cli = await runCLI('./fixtures/with-solid-js/', {
|
|
||||||
silent: true,
|
|
||||||
onTimeout: (ex) => {
|
|
||||||
console.log(ex);
|
|
||||||
// if fail to start, skip for now as it's very flaky
|
|
||||||
this.skip();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
await cli?.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders the solid component', async () => {
|
|
||||||
let res = await fetch(`http://127.0.0.1:${cli.port}/`);
|
|
||||||
expect(res.status).to.equal(200);
|
|
||||||
let html = await res.text();
|
|
||||||
let $ = cheerio.load(html);
|
|
||||||
expect($('.solid').text()).to.equal('Solid Content');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,6 +0,0 @@
|
||||||
# for tests only
|
|
||||||
|
|
||||||
send_metrics = false
|
|
||||||
|
|
||||||
[vars]
|
|
||||||
SECRET_STUFF = "secret"
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../../../tsconfig.base.json",
|
|
||||||
"include": ["src"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"outDir": "./dist"
|
|
||||||
}
|
|
||||||
}
|
|
954
pnpm-lock.yaml
954
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue