diff --git a/.changeset/strong-ravens-film.md b/.changeset/strong-ravens-film.md new file mode 100644 index 000000000000..68b7675f0bc0 --- /dev/null +++ b/.changeset/strong-ravens-film.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-cloudflare-workers': minor +--- + +feat: support `.jsonc` Wrangler configuration files diff --git a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md index b4daaa2fbf53..e0f71c8dcdce 100644 --- a/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md +++ b/documentation/docs/25-build-and-deploy/60-adapter-cloudflare.md @@ -2,7 +2,7 @@ title: Cloudflare Pages --- -To deploy to [Cloudflare Pages](https://developers.cloudflare.com/pages/), use [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare). +To deploy to [Cloudflare Pages](https://pages.cloudflare.com/), use [`adapter-cloudflare`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare). This adapter will be installed by default when you use [`adapter-auto`](adapter-auto). If you plan on staying with Cloudflare Pages, you can switch from [`adapter-auto`](adapter-auto) to using this adapter directly so that values specific to Cloudflare Workers are emulated during local development, type declarations are automatically applied, and the ability to set Cloudflare-specific options is provided. @@ -30,10 +30,9 @@ export default { exclude: [''] }, platformProxy: { - configPath: 'wrangler.toml', + configPath: undefined, environment: undefined, - experimentalJsonConfig: false, - persist: false + persist: undefined } }) } @@ -44,7 +43,7 @@ export default { ### routes -Allows you to customise the [`_routes.json`](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) file generated by `adapter-cloudflare`. +Allows you to customise the [`_routes.json`](https://developers.cloudflare.com/pages/functions/routing/#create-a-_routesjson-file) file generated by `adapter-cloudflare`. - `include` defines routes that will invoke a function, and defaults to `['/*']` - `exclude` defines routes that will _not_ invoke a function — this is a faster and cheaper way to serve your app's static assets. This array can include the following special values: @@ -57,11 +56,11 @@ You can have up to 100 `include` and `exclude` rules combined. Generally you can ### platformProxy -Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#syntax) Wrangler API documentation for a full list of options. +Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options. ## Deployment -Please follow the [Get Started Guide](https://developers.cloudflare.com/pages/get-started) for Cloudflare Pages to begin. +Please follow the [Get Started Guide](https://developers.cloudflare.com/pages/get-started/) for Cloudflare Pages to begin. When configuring your project settings, you must use the following settings: @@ -71,7 +70,7 @@ When configuring your project settings, you must use the following settings: ## Runtime APIs -The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/pages/platform/functions/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`context`](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#contextwaituntil), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties), meaning that you can access it in hooks and endpoints: +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/pages/functions/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`context`](https://developers.cloudflare.com/workers/runtime-apis/context/), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties), meaning that you can access it in hooks and endpoints: ```js // @errors: 7031 @@ -80,9 +79,9 @@ export async function POST({ request, platform }) { } ``` -> [!NOTE] SvelteKit's built-in `$env` module should be preferred for environment variables. +> [!NOTE] SvelteKit's built-in [`$env` module]($env-static-private) should be preferred for environment variables. -To make these types available to your app, install `@cloudflare/workers-types` and reference them in your `src/app.d.ts`: +To make these types available to your app, install [`@cloudflare/workers-types`](https://www.npmjs.com/package/@cloudflare/workers-types) and reference them in your `src/app.d.ts`: ```ts /// file: src/app.d.ts @@ -104,15 +103,15 @@ export {}; ### Testing Locally -Cloudflare Workers specific values in the `platform` property are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on the configuration in your `wrangler.toml` file and are used to populate `platform.env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. +Cloudflare Workers specific values in the `platform` property are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/pages/functions/bindings/) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/pages/functions/wrangler-configuration/#local-development) and are used to populate `platform.env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. -For testing the build, you should use [wrangler](https://developers.cloudflare.com/workers/cli-wrangler) **version 3**. Once you have built your site, run `wrangler pages dev .svelte-kit/cloudflare`. +For testing the build, you should use [Wrangler](https://developers.cloudflare.com/workers/wrangler/) **version 3**. Once you have built your site, run `wrangler pages dev .svelte-kit/cloudflare`. ## Notes -Functions contained in the `/functions` directory at the project's root will _not_ be included in the deployment, which is compiled to a [single `_worker.js` file](https://developers.cloudflare.com/pages/platform/functions/#advanced-mode). Functions should be implemented as [server endpoints](routing#server) in your SvelteKit app. +Functions contained in the [`/functions` directory](https://developers.cloudflare.com/pages/functions/routing/) at the project's root will _not_ be included in the deployment. Instead, functions should be implemented as [server endpoints](routing#server) in your SvelteKit app, which is compiled to a [single `_worker.js` file](https://developers.cloudflare.com/pages/functions/advanced-mode/). -The `_headers` and `_redirects` files specific to Cloudflare Pages can be used for static asset responses (like images) by putting them into the `/static` folder. +The [`_headers`](https://developers.cloudflare.com/pages/configuration/headers/) and [`_redirects`](https://developers.cloudflare.com/pages/configuration/redirects/) files specific to Cloudflare Pages can be used for static asset responses (like images) by putting them into the `/static` folder. However, they will have no effect on responses dynamically rendered by SvelteKit, which should return custom headers or redirect responses from [server endpoints](routing#server) or with the [`handle`](hooks#Server-hooks-handle) hook. @@ -120,7 +119,22 @@ However, they will have no effect on responses dynamically rendered by SvelteKit ### Further reading -You may wish to refer to [Cloudflare's documentation for deploying a SvelteKit site](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-kit-site). +You may wish to refer to [Cloudflare's documentation for deploying a SvelteKit site](https://developers.cloudflare.com/pages/framework-guides/deploy-a-svelte-kit-site/). + +### Node.js compatibility + +If you would like to enable [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/), you can add the `nodejs_compat` compatibility flag to your Wrangler configuration file: + +```jsonc +/// file: wrangler.jsonc +{ + "compatibility_flags": ["nodejs_compat"] +} +``` + +### Worker size limits + +When deploying your application, the server generated by SvelteKit is bundled into a single file. Wrangler will fail to publish your worker if it exceeds [the size limits](https://developers.cloudflare.com/workers/platform/limits/#worker-size) after minification. You're unlikely to hit this limit usually, but some large libraries can cause this to happen. In that case, you can try to reduce the size of your worker by only importing such libraries on the client side. See [the FAQ](./faq#How-do-I-use-a-client-side-library-accessing-document-or-window) for more information. ### Accessing the file system diff --git a/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md b/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md index 601149c438a4..71197d9daabe 100644 --- a/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md +++ b/documentation/docs/25-build-and-deploy/70-adapter-cloudflare-workers.md @@ -4,7 +4,7 @@ title: Cloudflare Workers To deploy to [Cloudflare Workers](https://workers.cloudflare.com/), use [`adapter-cloudflare-workers`](https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare-workers). -> [!NOTE] Unless you have a specific reason to use `adapter-cloudflare-workers`, it's recommended that you use `adapter-cloudflare` instead. Both adapters have equivalent functionality, but Cloudflare Pages offers features like GitHub integration with automatic builds and deploys, preview deployments, instant rollback and so on. +> [!NOTE] Unless you have a specific reason to use `adapter-cloudflare-workers`, it's recommended that you use [`adapter-cloudflare`](adapter-cloudflare) instead. Both adapters have equivalent functionality, but Cloudflare Pages offers features like GitHub integration with automatic builds and deploys, preview deployments, instant rollback and so on. ## Usage @@ -18,13 +18,7 @@ import adapter from '@sveltejs/adapter-cloudflare-workers'; export default { kit: { adapter: adapter({ - config: 'wrangler.toml', - platformProxy: { - configPath: 'wrangler.toml', - environment: undefined, - experimentalJsonConfig: false, - persist: false - } + // see below for options that can be set here }) } }; @@ -34,42 +28,44 @@ export default { ### config -Path to your custom `wrangler.toml` or `wrangler.json` config file. +Path to your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/). If you would like to use a Wrangler configuration filename other than `wrangler.jsonc`, you can specify it using this option. ### platformProxy -Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#syntax) Wrangler API documentation for a full list of options. +Preferences for the emulated `platform.env` local bindings. See the [getPlatformProxy](https://developers.cloudflare.com/workers/wrangler/api/#parameters-1) Wrangler API documentation for a full list of options. ## Basic Configuration -This adapter expects to find a [wrangler.toml/wrangler.json](https://developers.cloudflare.com/workers/platform/sites/configuration) file in the project root. It should look something like this: - -```toml -/// file: wrangler.toml -name = "" -account_id = "" - -main = "./.cloudflare/worker.js" -site.bucket = "./.cloudflare/public" - -build.command = "npm run build" - -compatibility_date = "2021-11-12" -workers_dev = true +This adapter expects to find a [Wrangler configuration file](https://developers.cloudflare.com/workers/configuration/sites/configuration/) in the project root. It should look something like this: + +```jsonc +/// file: wrangler.jsonc +{ + "name": "", + "account_id": "", + "main": "./.cloudflare/worker.js", + "site": { + "bucket": "./.cloudflare/public" + }, + "build": { + "command": "npm run build" + }, + "compatibility_date": "2021-11-12" +} ``` -`` can be anything. `` can be found by logging into your [Cloudflare dashboard](https://dash.cloudflare.com) and grabbing it from the end of the URL: +`` can be anything. `` can be found by running `wrangler whoami` using the Wrangler CLI tool or by logging into your [Cloudflare dashboard](https://dash.cloudflare.com) and grabbing it from the end of the URL: ``` -https://dash.cloudflare.com/ +https://dash.cloudflare.com//home ``` -> [!NOTE] You should add the `.cloudflare` directory (or whichever directories you specified for `main` and `site.bucket`) to your `.gitignore`. +> [!NOTE] You should add the `.cloudflare` directory (or whichever directories you specified for `main` and `site.bucket`) and the `.wrangler` directory to your `.gitignore`. -You will need to install [wrangler](https://developers.cloudflare.com/workers/wrangler/get-started/) and log in, if you haven't already: +You will need to install [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/) and log in, if you haven't already: -``` -npm i -g wrangler +```sh +npm i -D wrangler wrangler login ``` @@ -79,20 +75,9 @@ Then, you can build your app and deploy it: wrangler deploy ``` -## Custom config - -If you would like to use a config file other than `wrangler.toml` you can specify so using the [`config` option](#Options-config). - -If you would like to enable [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/#enable-nodejs-from-the-cloudflare-dashboard), you can add "nodejs_compat" flag to `wrangler.toml`: - -```toml -/// file: wrangler.toml -compatibility_flags = [ "nodejs_compat" ] -``` - ## Runtime APIs -The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/pages/platform/functions/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`context`](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#contextwaituntil), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#the-cf-property-requestinitcfproperties), meaning that you can access it in hooks and endpoints: +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object contains your project's [bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/), which consist of KV/DO namespaces, etc. It is passed to SvelteKit via the `platform` property, along with [`context`](https://developers.cloudflare.com/workers/runtime-apis/context/), [`caches`](https://developers.cloudflare.com/workers/runtime-apis/cache/), and [`cf`](https://developers.cloudflare.com/workers/runtime-apis/request/#incomingrequestcfproperties), meaning that you can access it in hooks and endpoints: ```js // @errors: 7031 @@ -101,9 +86,9 @@ export async function POST({ request, platform }) { } ``` -> [!NOTE] SvelteKit's built-in `$env` module should be preferred for environment variables. +> [!NOTE] SvelteKit's built-in [`$env` module]($env-static-private) should be preferred for environment variables. -To make these types available to your app, install `@cloudflare/workers-types` and reference them in your `src/app.d.ts`: +To make these types available to your app, install [`@cloudflare/workers-types`](https://www.npmjs.com/package/@cloudflare/workers-types) and reference them in your `src/app.d.ts`: ```ts /// file: src/app.d.ts @@ -125,15 +110,26 @@ export {}; ### Testing Locally -Cloudflare Workers specific values in the `platform` property are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on the configuration in your `wrangler.toml` file and are used to populate `platform.env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. +Cloudflare Workers specific values in the `platform` property are emulated during dev and preview modes. Local [bindings](https://developers.cloudflare.com/workers/wrangler/configuration/#bindings) are created based on your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/) and are used to populate `platform.env` during development and preview. Use the adapter config [`platformProxy` option](#Options-platformProxy) to change your preferences for the bindings. -For testing the build, you should use [wrangler](https://developers.cloudflare.com/workers/cli-wrangler) **version 3**. Once you have built your site, run `wrangler dev`. +For testing the build, you should use [Wrangler](https://developers.cloudflare.com/workers/wrangler/) **version 3**. Once you have built your site, run `wrangler dev`. ## Troubleshooting +### Node.js compatibility + +If you would like to enable [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/), you can add the `nodejs_compat` compatibility flag to your Wrangler configuration file: + +```jsonc +/// file: wrangler.jsonc +{ + "compatibility_flags": ["nodejs_compat"] +} +``` + ### Worker size limits -When deploying to workers, the server generated by SvelteKit is bundled into a single file. Wrangler will fail to publish your worker if it exceeds [the size limits](https://developers.cloudflare.com/workers/platform/limits/#worker-size) after minification. You're unlikely to hit this limit usually, but some large libraries can cause this to happen. In that case, you can try to reduce the size of your worker by only importing such libraries on the client side. See [the FAQ](./faq#How-do-I-use-a-client-side-library-accessing-document-or-window) for more information. +When deploying your application, the server generated by SvelteKit is bundled into a single file. Wrangler will fail to publish your worker if it exceeds [the size limits](https://developers.cloudflare.com/workers/platform/limits/#worker-size) after minification. You're unlikely to hit this limit usually, but some large libraries can cause this to happen. In that case, you can try to reduce the size of your worker by only importing such libraries on the client side. See [the FAQ](./faq#How-do-I-use-a-client-side-library-accessing-document-or-window) for more information. ### Accessing the file system diff --git a/package.json b/package.json index 0f5c11cc6a55..ca29cdf8c5a1 100644 --- a/package.json +++ b/package.json @@ -34,5 +34,11 @@ "packageManager": "pnpm@10.4.1", "engines": { "pnpm": ">=9.0.0" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "svelte-preprocess", + "workerd" + ] } } diff --git a/packages/adapter-cloudflare-workers/index.d.ts b/packages/adapter-cloudflare-workers/index.d.ts index b76003735910..878b48649d5e 100644 --- a/packages/adapter-cloudflare-workers/index.d.ts +++ b/packages/adapter-cloudflare-workers/index.d.ts @@ -5,6 +5,9 @@ import { GetPlatformProxyOptions } from 'wrangler'; export default function plugin(options?: AdapterOptions): Adapter; export interface AdapterOptions { + /** + * Path to your {@link https://developers.cloudflare.com/workers/wrangler/configuration/ | Wrangler configuration file}. + */ config?: string; /** * Config object passed to {@link https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy | getPlatformProxy} diff --git a/packages/adapter-cloudflare-workers/index.js b/packages/adapter-cloudflare-workers/index.js index 5da3fe275022..5d13539cd915 100644 --- a/packages/adapter-cloudflare-workers/index.js +++ b/packages/adapter-cloudflare-workers/index.js @@ -1,20 +1,9 @@ -import { existsSync, readFileSync, writeFileSync } from 'node:fs'; -import { posix, dirname } from 'node:path'; import { execSync } from 'node:child_process'; -import esbuild from 'esbuild'; -import toml from '@iarna/toml'; +import { writeFileSync } from 'node:fs'; +import { posix, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { getPlatformProxy } from 'wrangler'; - -/** - * @typedef {{ - * main: string; - * site: { - * bucket: string; - * } - * compatibility_flags?: string[]; - * }} WranglerConfig - */ +import esbuild from 'esbuild'; +import { getPlatformProxy, unstable_readConfig } from 'wrangler'; // list from https://developers.cloudflare.com/workers/runtime-apis/nodejs/ const compatible_node_modules = [ @@ -32,7 +21,7 @@ const compatible_node_modules = [ ]; /** @type {import('./index.js').default} */ -export default function ({ config = 'wrangler.toml', platformProxy = {} } = {}) { +export default function ({ config, platformProxy = {} } = {}) { return { name: '@sveltejs/adapter-cloudflare-workers', @@ -189,68 +178,46 @@ export default function ({ config = 'wrangler.toml', platformProxy = {} } = {}) /** * @param {import('@sveltejs/kit').Builder} builder * @param {string} config_file - * @returns {WranglerConfig} + * @returns {import('wrangler').Unstable_Config} */ -function validate_config(builder, config_file) { - if (!existsSync(config_file) && config_file === 'wrangler.toml' && existsSync('wrangler.json')) { - builder.log.minor('Default wrangler.toml does not exist. Using wrangler.json.'); - config_file = 'wrangler.json'; +function validate_config(builder, config_file = undefined) { + const wrangler_config = unstable_readConfig({ config: config_file }); + + if (!wrangler_config.configPath) { + builder.log.error( + 'Consult https://developers.cloudflare.com/workers/platform/sites/configuration on how to setup your site' + ); + builder.log( + ` +Sample wrangler.jsonc: +{ + "name": "", + "account_id": "", + "main": "./.cloudflare/worker.js", + "site": { + "bucket": "./.cloudflare/public" + }, + "build": { + "command": "npm run build" + }, + "compatibility_date": "2021-11-12" +} + `.trim() + ); + throw new Error('Missing a Wrangler configuration file'); } - if (existsSync(config_file)) { - /** @type {WranglerConfig} */ - let wrangler_config; - - try { - if (config_file.endsWith('.json')) { - wrangler_config = /** @type {WranglerConfig} */ ( - JSON.parse(readFileSync(config_file, 'utf-8')) - ); - } else { - wrangler_config = /** @type {WranglerConfig} */ ( - toml.parse(readFileSync(config_file, 'utf-8')) - ); - } - } catch (err) { - err.message = `Error parsing ${config_file}: ${err.message}`; - throw err; - } - if (!wrangler_config.site?.bucket) { - throw new Error( - `You must specify site.bucket in ${config_file}. Consult https://developers.cloudflare.com/workers/platform/sites/configuration` - ); - } - - if (!wrangler_config.main) { - throw new Error( - `You must specify main option in ${config_file}. Consult https://github.com/sveltejs/kit/tree/main/packages/adapter-cloudflare-workers` - ); - } - - return wrangler_config; + if (!wrangler_config.site?.bucket) { + throw new Error( + `You must specify the \`site.bucket\` key in ${wrangler_config.configPath}. Consult https://developers.cloudflare.com/workers/platform/sites/configuration` + ); } - builder.log.error( - 'Consult https://developers.cloudflare.com/workers/platform/sites/configuration on how to setup your site' - ); - - builder.log( - ` - Sample wrangler.toml: - - name = "" - account_id = "" - - main = "./.cloudflare/worker.js" - site.bucket = "./.cloudflare/public" - - build.command = "npm run build" - - compatibility_date = "2021-11-12" - workers_dev = true` - .replace(/^\t+/gm, '') - .trim() - ); + if (!wrangler_config.main) { + throw new Error( + `You must specify the \`main\` key in ${wrangler_config.configPath}. Consult https://developers.cloudflare.com/workers/platform/sites/configuration` + ); + } - throw new Error(`Missing a ${config_file} file`); + return wrangler_config; } diff --git a/packages/adapter-cloudflare-workers/package.json b/packages/adapter-cloudflare-workers/package.json index 95a07cbdecd1..33ced2718dd6 100644 --- a/packages/adapter-cloudflare-workers/package.json +++ b/packages/adapter-cloudflare-workers/package.json @@ -39,7 +39,6 @@ }, "dependencies": { "@cloudflare/workers-types": "^4.20231121.0", - "@iarna/toml": "^2.2.5", "esbuild": "^0.24.0" }, "devDependencies": { @@ -50,6 +49,6 @@ }, "peerDependencies": { "@sveltejs/kit": "^2.0.0", - "wrangler": "^3.28.4" + "wrangler": "^3.91.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index de02453c01f4..44efef30c9c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,14 +88,11 @@ importers: '@cloudflare/workers-types': specifier: ^4.20231121.0 version: 4.20241205.0 - '@iarna/toml': - specifier: ^2.2.5 - version: 2.2.5 esbuild: specifier: ^0.24.0 version: 0.24.2 wrangler: - specifier: ^3.28.4 + specifier: ^3.91.0 version: 3.109.2(@cloudflare/workers-types@4.20241205.0) devDependencies: '@cloudflare/kv-asset-handler':