From cca7885857833ad05c42340cce53ae990e85b98c Mon Sep 17 00:00:00 2001 From: ANT Bot <116369605+workers-devprod@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:51:02 +0100 Subject: [PATCH 01/24] Version Packages (#7803) Co-authored-by: github-actions[bot] --- .changeset/cold-turtles-impress.md | 5 ----- .changeset/little-pugs-change.md | 5 ----- .changeset/old-panthers-brush.md | 9 --------- packages/pages-shared/CHANGELOG.md | 6 ++++++ packages/pages-shared/package.json | 2 +- packages/unenv-preset/CHANGELOG.md | 10 ++++++++++ packages/unenv-preset/package.json | 2 +- packages/vitest-pool-workers/CHANGELOG.md | 7 +++++++ packages/vitest-pool-workers/package.json | 2 +- packages/wrangler/CHANGELOG.md | 6 ++++++ packages/wrangler/package.json | 2 +- 11 files changed, 33 insertions(+), 23 deletions(-) delete mode 100644 .changeset/cold-turtles-impress.md delete mode 100644 .changeset/little-pugs-change.md delete mode 100644 .changeset/old-panthers-brush.md diff --git a/.changeset/cold-turtles-impress.md b/.changeset/cold-turtles-impress.md deleted file mode 100644 index fbc015a97fae9..0000000000000 --- a/.changeset/cold-turtles-impress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@cloudflare/pages-shared": patch ---- - -fix: Key the Early Hints cache entries off of the asset key rather than the request path diff --git a/.changeset/little-pugs-change.md b/.changeset/little-pugs-change.md deleted file mode 100644 index 2008cb4c65261..0000000000000 --- a/.changeset/little-pugs-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"wrangler": patch ---- - -fix(wrangler): use require.resolve to resolve unenv path diff --git a/.changeset/old-panthers-brush.md b/.changeset/old-panthers-brush.md deleted file mode 100644 index 8c62e5a33d7da..0000000000000 --- a/.changeset/old-panthers-brush.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@cloudflare/unenv-preset": patch ---- - -refactor(unenv-preset): misc minor changes - -- Bump the Typescript dependency to ^5.7.3 as required by unbuild -- Install a local version of `@types/node` (`@types/node-unenv`) -- Add more details to the README diff --git a/packages/pages-shared/CHANGELOG.md b/packages/pages-shared/CHANGELOG.md index 4dbe565e60636..b40925d5a3d10 100644 --- a/packages/pages-shared/CHANGELOG.md +++ b/packages/pages-shared/CHANGELOG.md @@ -1,5 +1,11 @@ # @cloudflare/pages-shared +## 0.13.2 + +### Patch Changes + +- [#7564](https://github.com/cloudflare/workers-sdk/pull/7564) [`147ab7d`](https://github.com/cloudflare/workers-sdk/commit/147ab7dda6af0546c9ba7b589390aa590e4d6d02) Thanks [@GregBrimble](https://github.com/GregBrimble)! - fix: Key the Early Hints cache entries off of the asset key rather than the request path + ## 0.13.1 ### Patch Changes diff --git a/packages/pages-shared/package.json b/packages/pages-shared/package.json index 76bf639be56ce..3bc0203f7d309 100644 --- a/packages/pages-shared/package.json +++ b/packages/pages-shared/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/pages-shared", - "version": "0.13.1", + "version": "0.13.2", "repository": { "type": "git", "url": "https://github.com/cloudflare/workers-sdk.git", diff --git a/packages/unenv-preset/CHANGELOG.md b/packages/unenv-preset/CHANGELOG.md index 875a27720adef..c3bc157542a0b 100644 --- a/packages/unenv-preset/CHANGELOG.md +++ b/packages/unenv-preset/CHANGELOG.md @@ -1,5 +1,15 @@ # @cloudflare/unenv-preset +## 1.0.1 + +### Patch Changes + +- [#7789](https://github.com/cloudflare/workers-sdk/pull/7789) [`facb3ff`](https://github.com/cloudflare/workers-sdk/commit/facb3ffc9b1973b16b8c3d30de790505c03e1554) Thanks [@vicb](https://github.com/vicb)! - refactor(unenv-preset): misc minor changes + + - Bump the Typescript dependency to ^5.7.3 as required by unbuild + - Install a local version of `@types/node` (`@types/node-unenv`) + - Add more details to the README + ## 1.0.0 ### Major Changes diff --git a/packages/unenv-preset/package.json b/packages/unenv-preset/package.json index 63382d313267c..fcf017529143e 100644 --- a/packages/unenv-preset/package.json +++ b/packages/unenv-preset/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/unenv-preset", - "version": "1.0.0", + "version": "1.0.1", "description": "cloudflare preset for unenv", "keywords": [ "cloudflare", diff --git a/packages/vitest-pool-workers/CHANGELOG.md b/packages/vitest-pool-workers/CHANGELOG.md index 98a30a809ba5e..867afc08f2c75 100644 --- a/packages/vitest-pool-workers/CHANGELOG.md +++ b/packages/vitest-pool-workers/CHANGELOG.md @@ -1,5 +1,12 @@ # @cloudflare/vitest-pool-workers +## 0.6.4 + +### Patch Changes + +- Updated dependencies [[`16a9460`](https://github.com/cloudflare/workers-sdk/commit/16a9460ea6c7daaadcdf2f2e921c66521549bc58)]: + - wrangler@3.103.2 + ## 0.6.3 ### Patch Changes diff --git a/packages/vitest-pool-workers/package.json b/packages/vitest-pool-workers/package.json index d7d6a658cdec6..d2674530e5c18 100644 --- a/packages/vitest-pool-workers/package.json +++ b/packages/vitest-pool-workers/package.json @@ -1,6 +1,6 @@ { "name": "@cloudflare/vitest-pool-workers", - "version": "0.6.3", + "version": "0.6.4", "description": "Workers Vitest integration for writing Vitest unit and integration tests that run inside the Workers runtime", "keywords": [ "cloudflare", diff --git a/packages/wrangler/CHANGELOG.md b/packages/wrangler/CHANGELOG.md index 070802bc316ed..32fd1477a6b11 100644 --- a/packages/wrangler/CHANGELOG.md +++ b/packages/wrangler/CHANGELOG.md @@ -1,5 +1,11 @@ # wrangler +## 3.103.2 + +### Patch Changes + +- [#7804](https://github.com/cloudflare/workers-sdk/pull/7804) [`16a9460`](https://github.com/cloudflare/workers-sdk/commit/16a9460ea6c7daaadcdf2f2e921c66521549bc58) Thanks [@vicb](https://github.com/vicb)! - fix(wrangler): use require.resolve to resolve unenv path + ## 3.103.1 ### Patch Changes diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index e4caa8cbd5504..38ce93d13f2ad 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -1,6 +1,6 @@ { "name": "wrangler", - "version": "3.103.1", + "version": "3.103.2", "description": "Command-line interface for all things Cloudflare Workers", "keywords": [ "wrangler", From 7e0449340caba36b8db0e8121623bf286acacd3b Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 20 Jan 2025 10:55:08 +0000 Subject: [PATCH 02/24] Migrate vite-plugin-cloudflare to workers-sdk (#7763) * copied over vite-plugin-cloudflare * packages/vite-plugin-cloudflare -> packages/vite-plugin-cloudflare * playground -> packages/vite-plugin-cloudflare/playground * vitest.config.e2e.ts -> packages/vite-plugin-cloudflare/playground/vitest.config.ts Co-authored-by: Peter Bacon Darwin Co-authored-by: Dario Piotrowicz * update vite-plugin-cloudflare files to work with workers-sdk * add playground to CI tests * add frameworks team to unenv-preset and vite-plugin-cloudflare CODEOWNERS * Rename @flarelabs-net/vite-plugin-cloudflare to @cloudflare/vite-plugin * Remove the use of playground-temp This is probably not needed because any generated files are already gitignored * Remove unnecessary workers-types catalog entry * Add changeset * add Vite plugin beta releases off main * Fix up package.json repo URL * Add test script * Remove changes to vitest-pool-workers test config, which is not relevant to this PR * update CODEOWNERS so frameworks team can also see changes to workers-tsconfig * Fix package-lock file * Fix up minimum Vite version for the Cloudflare Vite Plugin * Add README * Update packages/vite-plugin-cloudflare/package.json Co-authored-by: James Opstad <13586373+jamesopstad@users.noreply.github.com> * Update packages/vite-plugin-cloudflare/package.json Co-authored-by: James Opstad <13586373+jamesopstad@users.noreply.github.com> * use Cloudflare environments terminology consistently in README * Fix whitespace --------- Co-authored-by: James Opstad <13586373+jamesopstad@users.noreply.github.com> Co-authored-by: Dario Piotrowicz --- .changeset/chilled-maps-learn.md | 5 + .github/workflows/prereleases.yml | 6 + .github/workflows/test-and-check.yml | 7 +- .prettierignore | 4 +- .prettierrc | 11 + CODEOWNERS | 7 + packages/create-cloudflare/package.json | 3 + packages/unenv-preset/package.json | 2 +- packages/vite-plugin-cloudflare/README.md | 465 ++++++ packages/vite-plugin-cloudflare/package.json | 70 + .../playground/__test-utils__/index.ts | 2 + .../playground/__test-utils__/responses.ts | 21 + .../cloudflare-env/.env.custom-mode | 1 + .../__tests__/cloudflare-env.spec.ts | 6 + .../custom-mode/cloudflare-env.spec.ts | 6 + .../playground/cloudflare-env/package.json | 22 + .../playground/cloudflare-env/src/index.ts | 9 + .../playground/cloudflare-env/tsconfig.json | 7 + .../cloudflare-env/tsconfig.node.json | 4 + .../cloudflare-env/tsconfig.worker.json | 4 + .../playground/cloudflare-env/turbo.json | 9 + .../cloudflare-env/vite.config.custom-mode.ts | 7 + .../playground/cloudflare-env/vite.config.ts | 6 + .../playground/cloudflare-env/wrangler.toml | 8 + .../__tests__/durable-objects.spec.ts | 19 + .../playground/durable-objects/package.json | 19 + .../playground/durable-objects/src/index.ts | 62 + .../playground/durable-objects/tsconfig.json | 7 + .../durable-objects/tsconfig.node.json | 4 + .../durable-objects/tsconfig.worker.json | 4 + .../playground/durable-objects/turbo.json | 9 + .../playground/durable-objects/vite.config.ts | 6 + .../playground/durable-objects/wrangler.toml | 10 + .../__tests__/external-durable-object.spec.ts | 19 + .../external-durable-objects/package.json | 19 + .../external-durable-objects/tsconfig.json | 7 + .../tsconfig.node.json | 4 + .../tsconfig.worker.json | 4 + .../external-durable-objects/turbo.json | 9 + .../external-durable-objects/vite.config.ts | 12 + .../worker-a/index.ts | 35 + .../worker-a/wrangler.toml | 12 + .../worker-b/index.ts | 17 + .../worker-b/wrangler.toml | 3 + .../__tests__/workflows.spec.ts | 40 + .../external-workflows/package.json | 19 + .../external-workflows/tsconfig.json | 7 + .../external-workflows/tsconfig.node.json | 4 + .../external-workflows/tsconfig.worker.json | 4 + .../playground/external-workflows/turbo.json | 9 + .../external-workflows/vite.config.ts | 12 + .../external-workflows/worker-a/index.ts | 37 + .../external-workflows/worker-a/wrangler.toml | 9 + .../external-workflows/worker-b/index.ts | 22 + .../external-workflows/worker-b/wrangler.toml | 3 + .../hot-channel/__tests__/hot-channel.spec.ts | 10 + .../playground/hot-channel/package.json | 18 + .../playground/hot-channel/src/index.ts | 15 + .../playground/hot-channel/tsconfig.json | 7 + .../playground/hot-channel/tsconfig.node.json | 4 + .../hot-channel/tsconfig.worker.json | 4 + .../playground/hot-channel/turbo.json | 9 + .../playground/hot-channel/vite.config.ts | 26 + .../playground/hot-channel/wrangler.toml | 3 + .../__tests__/module-resolution.spec.ts | 121 ++ .../module-resolution-no-prebundling.spec.ts | 50 + .../playground/module-resolution/package.json | 29 + .../packages/imports/cloudflare-builtins.d.ts | 1 + .../packages/imports/cloudflare-builtins.js | 3 + .../packages/imports/package.json | 12 + .../packages/requires/ext.d.ts | 4 + .../packages/requires/ext.js | 13 + .../packages/requires/hello.js | 1 + .../packages/requires/index.d.ts | 1 + .../packages/requires/index.js | 1 + .../packages/requires/json.d.ts | 3 + .../packages/requires/json.js | 3 + .../packages/requires/no-ext.d.ts | 1 + .../packages/requires/no-ext.js | 1 + .../packages/requires/package.json | 19 + .../packages/requires/world.cjs | 1 + .../module-resolution/src/aliasing.ts | 3 + .../src/cloudflare-imports.ts | 8 + .../src/external-cloudflare-imports.ts | 5 + .../playground/module-resolution/src/index.ts | 45 + .../module-resolution/src/require-ext.ts | 11 + .../module-resolution/src/require-json.ts | 9 + .../module-resolution/src/require-no-ext.ts | 5 + .../src/third-party/discord-api-types.ts | 11 + .../src/third-party/react.ts | 7 + .../src/third-party/remix.ts | 7 + .../src/third-party/slash-create.ts | 22 + .../module-resolution/tsconfig.json | 7 + .../module-resolution/tsconfig.node.json | 4 + .../module-resolution/tsconfig.worker.json | 9 + .../playground/module-resolution/turbo.json | 9 + .../vite.config.no-prebundling.ts | 26 + .../module-resolution/vite.config.ts | 12 + .../module-resolution/wrangler.toml | 3 + .../playground/multi-worker/.gitignore | 3 + .../multi-worker.spec.ts | 24 + .../__tests__/multi-worker.spec.ts | 49 + .../multi-worker.spec.ts | 22 + .../playground/multi-worker/package.json | 24 + .../playground/multi-worker/tsconfig.json | 7 + .../multi-worker/tsconfig.node.json | 8 + .../multi-worker/tsconfig.worker.json | 4 + .../playground/multi-worker/turbo.json | 9 + .../vite.config.custom-output-directories.ts | 22 + .../playground/multi-worker/vite.config.ts | 12 + ...vite.config.with-worker-configs-warning.ts | 14 + .../playground/multi-worker/worker-a/index.ts | 39 + .../multi-worker/worker-a/wrangler.toml | 8 + .../worker-a/wrangler.with-warning.toml | 11 + .../playground/multi-worker/worker-b/index.ts | 21 + .../multi-worker/worker-b/wrangler.toml | 3 + .../worker-b/wrangler.with-warning.toml | 6 + .../__tests__/worker-basic/basic.spec.ts | 7 + .../worker-cross-env/cross-env.spec.ts | 7 + .../__tests__/worker-crypto/crypto.spec.ts | 24 + .../worker-postgres/postgres.spec.ts | 17 + .../__tests__/worker-process/process.spec.ts | 7 + .../__tests__/worker-random/random.spec.ts | 12 + .../playground/node-compat/package.json | 47 + .../playground/node-compat/tsconfig.json | 7 + .../playground/node-compat/tsconfig.node.json | 4 + .../node-compat/tsconfig.worker.json | 11 + .../playground/node-compat/turbo.json | 9 + .../node-compat/vite.config.worker-basic.ts | 14 + .../vite.config.worker-cross-env.ts | 14 + .../node-compat/vite.config.worker-crypto.ts | 14 + .../vite.config.worker-postgres.ts | 14 + .../node-compat/vite.config.worker-process.ts | 14 + .../node-compat/vite.config.worker-random.ts | 14 + .../node-compat/worker-basic/global.d.ts | 2 + .../node-compat/worker-basic/index.ts | 41 + .../node-compat/worker-basic/wrangler.toml | 4 + .../node-compat/worker-cross-env/index.ts | 31 + .../worker-cross-env/wrangler.toml | 4 + .../node-compat/worker-crypto/index.ts | 30 + .../node-compat/worker-crypto/wrangler.toml | 4 + .../node-compat/worker-postgres/index.ts | 39 + .../node-compat/worker-postgres/wrangler.toml | 14 + .../node-compat/worker-process/index.ts | 86 ++ .../node-compat/worker-process/wrangler.toml | 4 + .../node-compat/worker-random/global.d.ts | 2 + .../node-compat/worker-random/index.ts | 25 + .../node-compat/worker-random/wrangler.toml | 14 + .../playground/package.json | 19 + .../react-spa/__tests__/assets.spec.ts | 22 + .../playground/react-spa/index.html | 13 + .../playground/react-spa/package.json | 26 + .../playground/react-spa/public/vite.svg | 1 + .../playground/react-spa/src/App.css | 42 + .../playground/react-spa/src/App.tsx | 38 + .../playground/react-spa/src/assets/react.svg | 1 + .../playground/react-spa/src/index.css | 68 + .../playground/react-spa/src/main.tsx | 10 + .../playground/react-spa/src/vite-env.d.ts | 1 + .../playground/react-spa/tsconfig.client.json | 4 + .../playground/react-spa/tsconfig.json | 7 + .../playground/react-spa/tsconfig.node.json | 4 + .../playground/react-spa/turbo.json | 9 + .../playground/react-spa/vite.config.ts | 7 + .../playground/react-spa/wrangler.toml | 3 + .../playground/spa-with-api/.gitignore | 2 + .../spa-with-api.spec.ts | 33 + .../__tests__/spa-with-api.spec.ts | 20 + .../playground/spa-with-api/api/index.ts | 17 + .../playground/spa-with-api/index.html | 13 + .../playground/spa-with-api/package.json | 28 + .../playground/spa-with-api/public/vite.svg | 1 + .../playground/spa-with-api/src/App.css | 42 + .../playground/spa-with-api/src/App.tsx | 54 + .../spa-with-api/src/assets/react.svg | 1 + .../playground/spa-with-api/src/index.css | 68 + .../playground/spa-with-api/src/main.tsx | 10 + .../playground/spa-with-api/src/vite-env.d.ts | 1 + .../spa-with-api/tsconfig.client.json | 4 + .../playground/spa-with-api/tsconfig.json | 8 + .../spa-with-api/tsconfig.node.json | 4 + .../spa-with-api/tsconfig.worker.json | 4 + .../playground/spa-with-api/turbo.json | 9 + .../vite.config.custom-output-directories.ts | 17 + .../playground/spa-with-api/vite.config.ts | 7 + .../playground/spa-with-api/wrangler.toml | 4 + .../playground/static-mpa/404.html | 12 + .../static-mpa/__tests__/assets.spec.ts | 41 + .../assets.spec.ts | 15 + .../playground/static-mpa/about/404.html | 12 + .../playground/static-mpa/about/index.html | 12 + .../playground/static-mpa/contact.html | 12 + .../playground/static-mpa/index.html | 12 + .../playground/static-mpa/package.json | 22 + .../playground/static-mpa/public/vite.svg | 1 + .../playground/static-mpa/tsconfig.json | 4 + .../playground/static-mpa/tsconfig.node.json | 4 + .../playground/static-mpa/turbo.json | 9 + .../playground/static-mpa/vite.config.ts | 22 + ...vite.config.with-worker-configs-warning.ts | 27 + .../playground/static-mpa/wrangler.toml | 3 + .../static-mpa/wrangler.with-warning.toml | 5 + .../playground/tsconfig.json | 4 + .../__tests__/basic-functionality.spec.ts | 6 + .../playground/virtual-modules/package.json | 19 + .../playground/virtual-modules/src/index.ts | 8 + .../playground/virtual-modules/tsconfig.json | 7 + .../virtual-modules/tsconfig.node.json | 4 + .../virtual-modules/tsconfig.worker.json | 4 + .../playground/virtual-modules/turbo.json | 9 + .../playground/virtual-modules/vite.config.ts | 21 + .../playground/virtual-modules/wrangler.toml | 3 + .../playground/vitest-global-setup.ts | 24 + .../playground/vitest-setup.ts | 373 +++++ .../playground/vitest.config.e2e.ts | 22 + .../websockets/__tests__/websockets.spec.ts | 41 + .../playground/websockets/package.json | 19 + .../playground/websockets/src/index.html | 69 + .../playground/websockets/src/index.ts | 44 + .../playground/websockets/tsconfig.json | 7 + .../playground/websockets/tsconfig.node.json | 4 + .../websockets/tsconfig.worker.json | 4 + .../playground/websockets/turbo.json | 9 + .../playground/websockets/vite.config.ts | 6 + .../playground/websockets/wrangler.toml | 10 + .../__tests__/basic-functionality.spec.ts | 12 + .../playground/worker/package.json | 19 + .../playground/worker/src/index.ts | 9 + .../playground/worker/tsconfig.json | 7 + .../playground/worker/tsconfig.node.json | 4 + .../playground/worker/tsconfig.worker.json | 4 + .../playground/worker/turbo.json | 9 + .../playground/worker/vite.config.ts | 6 + .../playground/worker/wrangler.toml | 3 + .../workflows/__tests__/workflows.spec.ts | 40 + .../playground/workflows/package.json | 19 + .../playground/workflows/src/index.ts | 60 + .../playground/workflows/tsconfig.json | 7 + .../playground/workflows/tsconfig.node.json | 4 + .../playground/workflows/tsconfig.worker.json | 4 + .../playground/workflows/turbo.json | 9 + .../playground/workflows/vite.config.ts | 6 + .../playground/workflows/wrangler.toml | 8 + .../__tests__/fixtures/simple-wrangler.toml | 3 + .../wrangler-with-fields-to-ignore.toml | 37 + ...rning-for-workers-resolved-configs.spec.ts | 166 ++ .../src/__tests__/get-worker-config.spec.ts | 165 ++ .../src/__tests__/utils.spec.ts | 35 + .../src/asset-workers/asset-worker.ts | 40 + .../src/asset-workers/router-worker.ts | 4 + .../src/cloudflare-environment.ts | 203 +++ .../vite-plugin-cloudflare/src/constants.ts | 3 + .../src/deploy-config.ts | 109 ++ packages/vite-plugin-cloudflare/src/dev.ts | 22 + packages/vite-plugin-cloudflare/src/index.ts | 292 ++++ .../src/miniflare-options.ts | 525 +++++++ .../src/node-js-compat.ts | 140 ++ .../src/plugin-config.ts | 171 +++ .../src/runner-worker/__tests__/env.spec.ts | 65 + .../src/runner-worker/env.ts | 23 + .../src/runner-worker/index.ts | 382 +++++ .../src/runner-worker/module-runner.ts | 106 ++ packages/vite-plugin-cloudflare/src/shared.ts | 2 + packages/vite-plugin-cloudflare/src/utils.ts | 45 + .../vite-plugin-cloudflare/src/websockets.ts | 81 + .../src/workers-configs.ts | 382 +++++ packages/vite-plugin-cloudflare/tsconfig.json | 7 + .../tsconfig.plugin.json | 9 + .../tsconfig.worker.json | 4 + .../vite-plugin-cloudflare/tsup.config.ts | 32 + packages/vite-plugin-cloudflare/turbo.json | 9 + .../vite-plugin-cloudflare/vitest.config.ts | 9 + packages/workers-playground/package.json | 1 + packages/workers-tsconfig/base.json | 18 + packages/workers-tsconfig/client.json | 7 + packages/workers-tsconfig/react.json | 8 + .../workers-tsconfig/worker-node-compat.json | 11 + packages/workers-tsconfig/worker.json | 7 + packages/wrangler/package.json | 2 + .../__tests__/helpers/msw/handlers/script.ts | 4 +- pnpm-lock.yaml | 1365 +++++++++++++++-- pnpm-workspace.yaml | 12 +- .../__tests__/validate-changesets.test.ts | 1 + 283 files changed, 8713 insertions(+), 100 deletions(-) create mode 100644 .changeset/chilled-maps-learn.md create mode 100644 packages/vite-plugin-cloudflare/README.md create mode 100644 packages/vite-plugin-cloudflare/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/__test-utils__/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/__test-utils__/responses.ts create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/.env.custom-mode create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/__tests__/cloudflare-env.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/__tests__/custom-mode/cloudflare-env.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/vite.config.custom-mode.ts create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/cloudflare-env/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/durable-objects/__tests__/durable-objects.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/durable-objects/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/durable-objects/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/durable-objects/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/durable-objects/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/durable-objects/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/__tests__/external-durable-object.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-a/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-a/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-b/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-b/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/__tests__/workflows.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/worker-a/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/worker-a/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/worker-b/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/external-workflows/worker-b/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/hot-channel/__tests__/hot-channel.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/hot-channel/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/hot-channel/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/hot-channel/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/hot-channel/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/hot-channel/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/__tests__/module-resolution.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/__tests__/no-prebundling/module-resolution-no-prebundling.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/cloudflare-builtins.d.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/cloudflare-builtins.js create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/ext.d.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/ext.js create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/hello.js create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/index.d.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/index.js create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/json.d.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/json.js create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/no-ext.d.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/no-ext.js create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/world.cjs create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/aliasing.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/cloudflare-imports.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/external-cloudflare-imports.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/require-ext.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/require-json.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/require-no-ext.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/discord-api-types.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/react.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/remix.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/slash-create.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/vite.config.no-prebundling.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/module-resolution/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/.gitignore create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/custom-output-directories/multi-worker.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/multi-worker.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/with-worker-configs-warning/multi-worker.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.custom-output-directories.ts create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.with-worker-configs-warning.ts create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/wrangler.with-warning.toml create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/wrangler.with-warning.toml create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-basic/basic.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-cross-env/cross-env.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-crypto/crypto.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-postgres/postgres.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process/process.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-random/random.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-basic.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-cross-env.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-crypto.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-postgres.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-process.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-random.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/global.d.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-cross-env/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-cross-env/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-crypto/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-crypto/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-postgres/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-postgres/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-process/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-process/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-random/global.d.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-random/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/node-compat/worker-random/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/__tests__/assets.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/index.html create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/public/vite.svg create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/src/App.css create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/src/App.tsx create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/src/assets/react.svg create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/src/index.css create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/src/main.tsx create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/src/vite-env.d.ts create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.client.json create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/react-spa/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/.gitignore create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/__tests__/custom-output-directories/spa-with-api.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/__tests__/spa-with-api.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/api/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/index.html create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/public/vite.svg create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/src/App.css create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/src/App.tsx create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/src/assets/react.svg create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/src/index.css create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/src/main.tsx create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/src/vite-env.d.ts create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.client.json create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/vite.config.custom-output-directories.ts create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/spa-with-api/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/404.html create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/__tests__/assets.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/__tests__/with-worker-configs-warning/assets.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/about/404.html create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/about/index.html create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/contact.html create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/index.html create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/public/vite.svg create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/vite.config.with-worker-configs-warning.ts create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/static-mpa/wrangler.with-warning.toml create mode 100644 packages/vite-plugin-cloudflare/playground/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/virtual-modules/__tests__/basic-functionality.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/virtual-modules/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/virtual-modules/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/virtual-modules/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/virtual-modules/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/virtual-modules/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/vitest-global-setup.ts create mode 100644 packages/vite-plugin-cloudflare/playground/vitest-setup.ts create mode 100644 packages/vite-plugin-cloudflare/playground/vitest.config.e2e.ts create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/__tests__/websockets.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/src/index.html create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/websockets/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/worker/__tests__/basic-functionality.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/worker/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/worker/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/worker/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/worker/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/worker/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/worker/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/worker/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/worker/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/playground/workflows/__tests__/workflows.spec.ts create mode 100644 packages/vite-plugin-cloudflare/playground/workflows/package.json create mode 100644 packages/vite-plugin-cloudflare/playground/workflows/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/playground/workflows/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/playground/workflows/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/playground/workflows/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/playground/workflows/turbo.json create mode 100644 packages/vite-plugin-cloudflare/playground/workflows/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/playground/workflows/wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/src/__tests__/fixtures/simple-wrangler.toml create mode 100644 packages/vite-plugin-cloudflare/src/__tests__/fixtures/wrangler-with-fields-to-ignore.toml create mode 100644 packages/vite-plugin-cloudflare/src/__tests__/get-warning-for-workers-resolved-configs.spec.ts create mode 100644 packages/vite-plugin-cloudflare/src/__tests__/get-worker-config.spec.ts create mode 100644 packages/vite-plugin-cloudflare/src/__tests__/utils.spec.ts create mode 100644 packages/vite-plugin-cloudflare/src/asset-workers/asset-worker.ts create mode 100644 packages/vite-plugin-cloudflare/src/asset-workers/router-worker.ts create mode 100644 packages/vite-plugin-cloudflare/src/cloudflare-environment.ts create mode 100644 packages/vite-plugin-cloudflare/src/constants.ts create mode 100644 packages/vite-plugin-cloudflare/src/deploy-config.ts create mode 100644 packages/vite-plugin-cloudflare/src/dev.ts create mode 100644 packages/vite-plugin-cloudflare/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/src/miniflare-options.ts create mode 100644 packages/vite-plugin-cloudflare/src/node-js-compat.ts create mode 100644 packages/vite-plugin-cloudflare/src/plugin-config.ts create mode 100644 packages/vite-plugin-cloudflare/src/runner-worker/__tests__/env.spec.ts create mode 100644 packages/vite-plugin-cloudflare/src/runner-worker/env.ts create mode 100644 packages/vite-plugin-cloudflare/src/runner-worker/index.ts create mode 100644 packages/vite-plugin-cloudflare/src/runner-worker/module-runner.ts create mode 100644 packages/vite-plugin-cloudflare/src/shared.ts create mode 100644 packages/vite-plugin-cloudflare/src/utils.ts create mode 100644 packages/vite-plugin-cloudflare/src/websockets.ts create mode 100644 packages/vite-plugin-cloudflare/src/workers-configs.ts create mode 100644 packages/vite-plugin-cloudflare/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/tsconfig.plugin.json create mode 100644 packages/vite-plugin-cloudflare/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/tsup.config.ts create mode 100644 packages/vite-plugin-cloudflare/turbo.json create mode 100644 packages/vite-plugin-cloudflare/vitest.config.ts create mode 100644 packages/workers-tsconfig/base.json create mode 100644 packages/workers-tsconfig/client.json create mode 100644 packages/workers-tsconfig/react.json create mode 100644 packages/workers-tsconfig/worker-node-compat.json create mode 100644 packages/workers-tsconfig/worker.json diff --git a/.changeset/chilled-maps-learn.md b/.changeset/chilled-maps-learn.md new file mode 100644 index 0000000000000..46e07600f26fd --- /dev/null +++ b/.changeset/chilled-maps-learn.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/vite-plugin": patch +--- + +Initial beta release of the Cloudflare Vite plugin diff --git a/.github/workflows/prereleases.yml b/.github/workflows/prereleases.yml index 9af0452867f45..c1c685cba0f45 100644 --- a/.github/workflows/prereleases.yml +++ b/.github/workflows/prereleases.yml @@ -35,6 +35,7 @@ jobs: node .github/version-script.js create-cloudflare node .github/version-script.js workers-shared node .github/version-script.js unenv-preset + node .github/version-script.js vite-plugin-cloudflare - name: Build run: pnpm run build @@ -79,6 +80,11 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + - name: Publish vite-plugin-cloudflare@beta to NPM + run: pnpm --filter vite-plugin-cloudflare publish --tag beta + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + - name: Get Package Version run: echo "WRANGLER_VERSION=$(npm view wrangler@beta version)" >> $GITHUB_ENV working-directory: packages/wrangler diff --git a/.github/workflows/test-and-check.yml b/.github/workflows/test-and-check.yml index c9d4cb32956be..92278510111f1 100644 --- a/.github/workflows/test-and-check.yml +++ b/.github/workflows/test-and-check.yml @@ -101,7 +101,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-13] - filter: ["./packages/*", "./fixtures/*"] + filter: + [ + "./packages/*", + "./fixtures/*", + "./packages/vite-plugin-cloudflare/playground", + ] # Things in the tools folder are for running in CI, and so only need to run on linux include: - os: ubuntu-latest diff --git a/.prettierignore b/.prettierignore index aee96a33703ff..86367f47a72fb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -43,4 +43,6 @@ fixtures/interactive-dev-tests/src/startup-error.ts # These are generated by the build step fixtures/pages-redirected-config/build/* -fixtures/redirected-config-worker/build/* \ No newline at end of file +fixtures/redirected-config-worker/build/* + +packages/vite-plugin-cloudflare/playground/**/*.d.ts \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index b13350c6d7f35..897577d5f75b6 100644 --- a/.prettierrc +++ b/.prettierrc @@ -24,5 +24,16 @@ "jsx", "decorators", "explicitResourceManagement" + ], + "overrides": [ + { + "files": "packages/vite-plugin-cloudflare/README.md", + "options": { + "useTabs": false, + "semi": false, + "singleQuote": true, + "plugins": [] + } + } ] } diff --git a/CODEOWNERS b/CODEOWNERS index 947acc0410ca4..cd393f3971535 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -32,6 +32,13 @@ # Workflows ownership /packages/workflows-shared/ @cloudflare/workflows @cloudflare/wrangler +# unenv-preset ownership +/packages/unenv-preset/ @cloudflare/workers-frameworks @cloudflare/wrangler + +# vite-plugin-cloudflare ownership +/packages/vite-plugin-cloudflare/ @cloudflare/workers-frameworks @cloudflare/wrangler +/packages/workers-tsconfig/ @cloudflare/workers-frameworks @cloudflare/wrangler + # Owners intentionally left blank on these shared directories / files # to avoid noisy review requests /.changeset/ diff --git a/packages/create-cloudflare/package.json b/packages/create-cloudflare/package.json index cc4114e4bd849..df76020e48549 100644 --- a/packages/create-cloudflare/package.json +++ b/packages/create-cloudflare/package.json @@ -79,8 +79,11 @@ "open": "^8.4.0", "recast": "^0.22.0", "semver": "^7.5.1", + "typescript": "catalog:default", "undici": "catalog:default", + "vite": "catalog:default", "vite-tsconfig-paths": "^4.0.8", + "vitest": "catalog:default", "which-pm-runs": "^1.1.0", "wrangler": "workspace:*", "wrap-ansi": "^9.0.0", diff --git a/packages/unenv-preset/package.json b/packages/unenv-preset/package.json index fcf017529143e..aa79e0897adc5 100644 --- a/packages/unenv-preset/package.json +++ b/packages/unenv-preset/package.json @@ -47,7 +47,7 @@ }, "devDependencies": { "@types/node-unenv": "npm:@types/node@^22.10.5", - "typescript": "^5.7.3", + "typescript": "catalog:default", "unbuild": "^3.2.0", "undici": "catalog:default", "vitest": "catalog:default", diff --git a/packages/vite-plugin-cloudflare/README.md b/packages/vite-plugin-cloudflare/README.md new file mode 100644 index 0000000000000..20a3f4e79b6b0 --- /dev/null +++ b/packages/vite-plugin-cloudflare/README.md @@ -0,0 +1,465 @@ +# `@cloudflare/vite-plugin` + +[Intro](#intro) | [Quick start](#quick-start) | [Tutorial](#tutorial) | [API](#api) | [Cloudflare environments](#worker-environments) | [Migrating from `wrangler dev`](#migrating-from-wrangler-dev) + +## Intro + +The Cloudflare Vite plugin enables a full-featured integration between Vite and the Workers runtime. +Your Worker code runs inside [workerd](https://github.com/cloudflare/workerd), matching the production behavior as closely as possible and providing confidence as you develop and deploy your applications. + +### Features + +- Provides direct access to Workers runtime APIs and bindings +- Supports Workers Assets, enabling you to build static sites, SPAs, and full-stack applications +- Leverages Vite's hot module replacement for consistently fast updates +- Supports `vite preview` for previewing your build output in the Workers runtime prior to deployment + +## Quick start + +### Install the dependencies + +```sh +npm install @cloudflare/vite-plugin wrangler --save-dev +``` + +### Add the plugin to your Vite config + +```ts +// vite.config.ts + +import { defineConfig } from 'vite' +import { cloudflare } from '@cloudflare/vite-plugin' + +export default defineConfig({ + plugins: [cloudflare()], +}) +``` + +### Create your Worker config file + +```toml +# wrangler.toml + +name = "my-worker" +compatibility_date = "2024-12-30" +main = "./src/index.ts" +``` + +### Create your Worker entry file + +```ts +// src/index.ts + +export default { + fetch() { + return new Response(`Running in ${navigator.userAgent}!`) + }, +} +``` + +You can now develop (`vite dev`), build (`vite build`), preview (`vite preview`), and deploy (`wrangler deploy`) your application. + +## Tutorial + +In this tutorial, you will create a React SPA that can be deployed as a Worker with Workers Assets. +Then, you will add an API Worker that can be accessed from the front-end code. +You will develop, build, and preview the application using Vite before finally deploying to Cloudflare. + +### Set up and configure the React SPA + +#### Scaffold a Vite project + +Start by creating a React TypeScript project with Vite. + +```sh +npm create vite@latest cloudflare-vite-tutorial -- --template react-ts +``` + +Open the `cloudflare-vite-tutorial` directory in your editor of choice. + +#### Add the Cloudflare dependencies + +```sh +npm install @cloudflare/vite-plugin wrangler --save-dev +``` + +#### Add the plugin to your Vite config + +```ts +// vite.config.ts + +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { cloudflare } from '@cloudflare/vite-plugin' + +export default defineConfig({ + plugins: [react(), cloudflare()], +}) +``` + +#### Create your Worker config file + +```toml +# wrangler.toml + +name = "cloudflare-vite-tutorial" +compatibility_date = "2024-12-30" +assets = { not_found_handling = "single-page-application" } +``` + +The [`not_found_handling`](https://developers.cloudflare.com/workers/static-assets/routing/#not_found_handling--404-page--single-page-application--none) value has been set to `single-page-application`. +This means that all not found requests will serve the `index.html` file. +With the Cloudflare plugin, the `assets` routing configuration is used in place of Vite's default behavior. +This ensures that your application's routing works the same way while developing as it does when deployed to production. + +Note that the [`directory`](https://developers.cloudflare.com/workers/static-assets/binding/#directory) field is not used when configuring assets with Vite. +The `directory` in the output configuration will automatically point to the client build output. + +> [!NOTE] +> When using the Cloudflare Vite plugin, the Worker config (for example, `wrangler.toml`) that you provide is the input configuration file. +> A separate output `wrangler.json` file is created when you run `vite build`. +> This output file is a snapshot of your configuration at the time of the build and is modified to reference your build artifacts. +> It is the configuration that is used for preview and deployment. + +#### Run the development server + +Run `npm run dev` to verify that your application is working as expected. + +For a purely front-end application, you could now build (`npm run build`), preview (`npm run preview`), and deploy (`npm exec wrangler deploy`) your application. +However, this tutorial will show you how to go a step further and add an API Worker. + +### Add an API Worker + +#### Configure TypeScript for your Worker code + +```sh +npm install @cloudflare/workers-types --save-dev +``` + +```jsonc +// tsconfig.worker.json + +{ + "extends": "./tsconfig.node.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.worker.tsbuildinfo", + "types": ["@cloudflare/workers-types/2023-07-01", "vite/client"], + }, + "include": ["api"], +} +``` + +```jsonc +// tsconfig.json + +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" }, + ], +} +``` + +#### Add to your Worker configuration + +```toml +# wrangler.toml + +name = "cloudflare-vite-tutorial" +compatibility_date = "2024-12-30" +assets = { not_found_handling = "single-page-application", binding = "ASSETS" } +main = "./api/index.ts" +``` + +The assets `binding` defined here will allow you to access the assets functionality from your Worker. + +#### Add your API Worker + +```ts +// api/index.ts + +interface Env { + ASSETS: Fetcher +} + +export default { + fetch(request, env) { + const url = new URL(request.url) + + if (url.pathname.startsWith('/api/')) { + return Response.json({ + name: 'Cloudflare', + }) + } + + return env.ASSETS.fetch(request) + }, +} satisfies ExportedHandler +``` + +The Worker above will be invoked for any request not matching a static asset. +It returns a JSON response if the `pathname` starts with `/api/` and otherwise passes the incoming request through to the assets binding. +This means that for paths that do not start with `/api/`, the `not_found_handling` behavior defined in the Worker config will be evaluated and the `index.html` file will be returned, enabling SPA navigations. + +#### Call the API from the client + +Edit `src/App.tsx` so that it includes an additional button that calls the API and sets some state. +Replace the file contents with the following code: + +```tsx +// src/App.tsx + +import { useState } from 'react' +import reactLogo from './assets/react.svg' +import viteLogo from '/vite.svg' +import './App.css' + +function App() { + const [count, setCount] = useState(0) + const [name, setName] = useState('unknown') + + return ( + <> + +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+
+ +

+ Edit api/index.ts to change the name +

+
+

+ Click on the Vite and React logos to learn more +

+ + ) +} + +export default App +``` + +Now, if you click the button, it will display 'Name from API is: Cloudflare'. + +Increment the counter to update the application state in the browser. +Next, edit `api/index.ts` by changing the `name` it returns to `'Cloudflare Workers'`. +If you click the button again, it will display the new `name` while preserving the previously set counter value. +With Vite and the Cloudflare plugin, you can iterate on the client and server parts of your app quickly without losing UI state between edits. + +#### Build your application + +Run `npm run build` to build your application. + +If you inspect the `dist` directory, you will see that it contains two subdirectories: `client` and `cloudflare-vite-tutorial`. +The `cloudflare-vite-tutorial` directory contains your Worker code and the output `wrangler.json` configuration. + +#### Preview your application + +Run `npm run preview` to validate that your application runs as expected. +This command will run your build output locally in the Workers runtime, closely matching its behaviour in production. + +#### Deploy to Cloudflare + +Run `npm exec wrangler deploy` to deploy your application to Cloudflare. +This command will automatically use the output `wrangler.json` that was included in the build output. + +### Next steps + +In this tutorial, we created an SPA that could be deployed as a Worker with Workers Assets. +We then added an API Worker that could be accessed from the front-end code and deployed to Cloudflare. +Possible next steps include: + +- Adding a binding to another Cloudflare service such as a [KV namespace](https://developers.cloudflare.com/kv/) or [D1 database](https://developers.cloudflare.com/d1/) +- Expanding the API to include additional routes +- Using a library, such as [tRPC](https://trpc.io/) or [Hono](https://hono.dev/), in your API Worker + +## API + +### `cloudflare` + +The `cloudflare` plugin should be included in the Vite `plugins` array: + +```ts +// vite.config.ts + +import { defineConfig } from 'vite' +import { cloudflare } from '@cloudflare/vite-plugin' + +export default defineConfig({ + plugins: [cloudflare()], +}) +``` + +It accepts an optional `PluginConfig` parameter. + +### `interface PluginConfig` + +- `configPath?: string` + + An optional path to your Worker config file. + By default, a `wrangler.toml`, `wrangler.json`, or `wrangler.jsonc` file in the root of your application will be used as the Worker config. + +- `viteEnvironment?: { name?: string }` + + Optional Vite environment options. + By default, the environment name is the Worker name with `-` characters replaced with `_`. + Setting the name here will override this. + +- `persistState?: boolean | { path: string }` + + An optional override for state persistence. + By default, state is persisted to `.wrangler/state` in a `v3` subdirectory. + A custom `path` can be provided or, alternatively, persistence can be disabled by setting the value to `false`. + +- `auxiliaryWorkers?: Array` + + An optional array of auxiliary workers. + You can use [service bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) to call auxiliary workers from your main (entry) Worker. + All requests are routed through your entry Worker. + During the build, each Worker is output to a separate subdirectory of `dist`. + +> [!NOTE] +> When running `wrangler deploy`, only your main (entry) Worker will be deployed. +> If using multiple Workers, each must be deployed individually. +> You can inspect the `dist` directory and then run `wrangler deploy -c path-to-worker-output-config` for each. + +### `interface AuxiliaryWorkerConfig` + +- `configPath: string` + + A required path to your Worker config file. + +- `viteEnvironment?: { name?: string }` + + Optional Vite environment options. + By default, the environment name is the Worker name with `-` characters replaced with `_`. + Setting the name here will override this. + +## Cloudflare environments + +A Worker config file may contain configuration for multiple [Cloudflare environments](https://developers.cloudflare.com/workers/wrangler/environments/). +With the Cloudflare Vite plugin, you select a Cloudflare environment at dev or build time by providing the `CLOUDFLARE_ENV` environment variable. +Consider the following example `wrangler.toml` file: + +```toml +# wrangler.toml + +name = "my-worker" +compatibility_date = "2024-12-30" +main = "./src/index.ts" + +vars = { MY_VAR = "Top-level var" } + +[env.staging] +vars = { MY_VAR = "Staging var" } + +[env.production] +vars = { MY_VAR = "Production var" } +``` + +If you run `CLOUDFLARE_ENV=production vite build` then the output `wrangler.json` file generated by the build will be a flattened configuration for the 'production' Cloudflare environment. +This combines [top-level only](https://developers.cloudflare.com/workers/wrangler/configuration/#top-level-only-keys), [inheritable](https://developers.cloudflare.com/workers/wrangler/configuration/#inheritable-keys), and [non-inheritable](https://developers.cloudflare.com/workers/wrangler/configuration/#non-inheritable-keys) keys. +The value of `MY_VAR` will therefore be `'Production var'`. +The name of the Worker will be `'my-worker-production'`. +This is because the environment name is automatically appended to the top-level Worker name. + +> [!NOTE] +> The default Vite environment name for a Worker is always the top-level Worker name. +> This enables you to reference the Worker consistently in your Vite config when using multiple Cloudflare environments. + +Cloudflare environments can also be used in development. +For example, you could run `CLOUDFLARE_ENV=development vite dev`. +It is common to use the default top-level environment as the development environment and then add additional environments as necessary. + +> [!NOTE] +> Running `vite dev` or `vite build` without providing `CLOUDFLARE_ENV` will use the default top-level Cloudflare environment. +> The value of `MY_VAR` will therefore be `'Top-level var'`. +> As Cloudflare environments are applied at dev and build time, specifying `CLOUDFLARE_ENV` when running `vite preview` or `wrangler deploy` will have no effect. + +### Combining Cloudflare environments and Vite modes + +You may wish to combine the concepts of [Cloudflare environments](https://developers.cloudflare.com/workers/wrangler/environments/) and [Vite modes](https://vite.dev/guide/env-and-mode.html#modes). +With this approach, the Vite mode can be used to select the Cloudflare environment and a single method can be used to determine environment specific configuration and code. +Consider again the previous example: + +```toml +# wrangler.toml + +name = "my-worker" +compatibility_date = "2024-12-30" +main = "./src/index.ts" + +vars = { MY_VAR = "Top-level var" } + +[env.staging] +vars = { MY_VAR = "Staging var" } + +[env.production] +vars = { MY_VAR = "Production var" } +``` + +Next, provide `.env.staging` and `.env.production` files: + +```sh +# .env.staging + +CLOUDFLARE_ENV=staging +``` + +```sh +# .env.production + +CLOUDFLARE_ENV=production +``` + +By default, `vite build` uses the 'production' Vite mode. +Vite will therefore load the `.env.production` file to get the environment variables that are used in the build. +Since the `.env.production` file contains `CLOUDFLARE_ENV=production`, the Cloudflare Vite plugin will select the 'production' Cloudflare environment. +The value of `MY_VAR` will therefore be `'Production var'`. +If you run `vite build --mode staging` then the 'staging' Vite mode will be used and the 'staging' Cloudflare environment will be selected. +The value of `MY_VAR` will therefore be `'Staging var'`. + +## Migrating from `wrangler dev` + +Migrating from `wrangler dev` is a simple process and you can follow the instructions in the [Quick start](#quick-start) to get started. +There are a few key differences to highlight: + +### Input and output Worker config files + +In the Vite integration, your Worker config file (for example, `wrangler.toml`) is the input configuration and a separate output configuration is created as part of the build. +This output file is a snapshot of your configuration at the time of the build and is modified to reference your build artifacts. +It is the configuration that is used for preview and deployment. + +### Redundant fields in the Wrangler config file + +There are various options in the Worker config file that are ignored when using Vite, as they are either no longer applicable or are replaced by Vite equivalents. +If these options are provided, then warnings will be printed to the console with suggestions for how to proceed. +Examples where the Vite configuration should be used instead include `alias` and `define`. diff --git a/packages/vite-plugin-cloudflare/package.json b/packages/vite-plugin-cloudflare/package.json new file mode 100644 index 0000000000000..638c9ff1a0990 --- /dev/null +++ b/packages/vite-plugin-cloudflare/package.json @@ -0,0 +1,70 @@ +{ + "name": "@cloudflare/vite-plugin", + "version": "0.0.0", + "description": "Cloudflare plugin for Vite", + "keywords": [ + "cloudflare", + "workers", + "cloudflare-workers", + "vite", + "vite-plugin" + ], + "homepage": "https://github.com/cloudflare/workers-sdk/tree/main/packages/vite-plugin-cloudflare#readme", + "bugs": { + "url": "https://github.com/cloudflare/workers-sdk/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/cloudflare/workers-sdk.git", + "directory": "packages/vite-plugin-cloudflare" + }, + "license": "MIT", + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsup", + "check:type": "tsc --build", + "test": "vitest", + "test:ci": "vitest run", + "watch": "tsup --watch" + }, + "dependencies": { + "@hattip/adapter-node": "^0.0.49", + "miniflare": "workspace:*", + "unenv": "catalog:vite-plugin", + "ws": "^8.18.0" + }, + "devDependencies": { + "@cloudflare/workers-shared": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "@types/node": "catalog:vite-plugin", + "@types/ws": "^8.5.13", + "magic-string": "^0.30.12", + "tsup": "^8.3.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + }, + "peerDependencies": { + "vite": "catalog:vite-plugin", + "wrangler": "workspace:^" + }, + "publishConfig": { + "access": "public" + }, + "workers-sdk": { + "prerelease": true + } +} diff --git a/packages/vite-plugin-cloudflare/playground/__test-utils__/index.ts b/packages/vite-plugin-cloudflare/playground/__test-utils__/index.ts new file mode 100644 index 0000000000000..ffc0462924a59 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/__test-utils__/index.ts @@ -0,0 +1,2 @@ +export * from "../vitest-setup"; +export * from "./responses"; diff --git a/packages/vite-plugin-cloudflare/playground/__test-utils__/responses.ts b/packages/vite-plugin-cloudflare/playground/__test-utils__/responses.ts new file mode 100644 index 0000000000000..0b3a90bda7e2a --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/__test-utils__/responses.ts @@ -0,0 +1,21 @@ +import { page, viteTestUrl } from "./index"; + +export async function getTextResponse(path = "/"): Promise { + const response = await getResponse(path); + return response.text(); +} + +export async function getJsonResponse( + path = "/" +): Promise> { + const response = await getResponse(path); + return response.json(); +} + +async function getResponse(path = "/") { + const url = `${viteTestUrl}${path}`; + + const response = page.waitForResponse(url); + await page.goto(url); + return response; +} diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/.env.custom-mode b/packages/vite-plugin-cloudflare/playground/cloudflare-env/.env.custom-mode new file mode 100644 index 0000000000000..9e7641131eab8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/.env.custom-mode @@ -0,0 +1 @@ +CLOUDFLARE_ENV=custom-env \ No newline at end of file diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/__tests__/cloudflare-env.spec.ts b/packages/vite-plugin-cloudflare/playground/cloudflare-env/__tests__/cloudflare-env.spec.ts new file mode 100644 index 0000000000000..0553967a53bd7 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/__tests__/cloudflare-env.spec.ts @@ -0,0 +1,6 @@ +import { expect, test } from "vitest"; +import { getTextResponse } from "../../__test-utils__"; + +test("returns the correct top-level var when CLOUDFLARE_ENV is undefined", async () => { + expect(await getTextResponse()).toEqual("Top level var"); +}); diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/__tests__/custom-mode/cloudflare-env.spec.ts b/packages/vite-plugin-cloudflare/playground/cloudflare-env/__tests__/custom-mode/cloudflare-env.spec.ts new file mode 100644 index 0000000000000..46d05f819194b --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/__tests__/custom-mode/cloudflare-env.spec.ts @@ -0,0 +1,6 @@ +import { expect, test } from "vitest"; +import { getTextResponse } from "../../../__test-utils__"; + +test("returns the correct var when CLOUDFLARE_ENV is provided in a .env.[mode] file", async () => { + expect(await getTextResponse()).toEqual("Custom env var"); +}); diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/package.json b/packages/vite-plugin-cloudflare/playground/cloudflare-env/package.json new file mode 100644 index 0000000000000..47ced0a2e8aac --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/package.json @@ -0,0 +1,22 @@ +{ + "name": "@playground/cloudflare-env", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "build:custom-mode": "vite build --app -c vite.config.custom-mode.ts", + "check:types": "tsc --build", + "dev": "vite dev", + "dev:custom-mode": "vite dev -c vite.config.custom-mode.ts", + "preview": "vite preview", + "preview:custom-mode": "vite preview -c vite.config.custom-mode.ts" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/src/index.ts b/packages/vite-plugin-cloudflare/playground/cloudflare-env/src/index.ts new file mode 100644 index 0000000000000..9d76da70607a3 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/src/index.ts @@ -0,0 +1,9 @@ +interface Env { + MY_VAR: string; +} + +export default { + async fetch(request, env) { + return new Response(env.MY_VAR); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.json b/packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.node.json new file mode 100644 index 0000000000000..01cb02ba3b1a6 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "vite.config.custom-mode.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.worker.json new file mode 100644 index 0000000000000..da43778b826f4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/turbo.json b/packages/vite-plugin-cloudflare/playground/cloudflare-env/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/vite.config.custom-mode.ts b/packages/vite-plugin-cloudflare/playground/cloudflare-env/vite.config.custom-mode.ts new file mode 100644 index 0000000000000..4bb74003eccdc --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/vite.config.custom-mode.ts @@ -0,0 +1,7 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + mode: "custom-mode", + plugins: [cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/vite.config.ts b/packages/vite-plugin-cloudflare/playground/cloudflare-env/vite.config.ts new file mode 100644 index 0000000000000..55c8276338d72 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/vite.config.ts @@ -0,0 +1,6 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/wrangler.toml b/packages/vite-plugin-cloudflare/playground/cloudflare-env/wrangler.toml new file mode 100644 index 0000000000000..66b03c7aa1c87 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/wrangler.toml @@ -0,0 +1,8 @@ +name = "worker" +main = "./src/index.ts" +compatibility_date = "2024-12-30" + +vars = { MY_VAR = "Top level var" } + +[env.custom-env] +vars = { MY_VAR = "Custom env var" } diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/__tests__/durable-objects.spec.ts b/packages/vite-plugin-cloudflare/playground/durable-objects/__tests__/durable-objects.spec.ts new file mode 100644 index 0000000000000..86ad1a660cb05 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/__tests__/durable-objects.spec.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from "vitest"; +import { getTextResponse } from "../../__test-utils__"; + +describe("in-worker defined durable objects", async () => { + test("can bind and use a Durable Object defined in the worker", async () => { + expect(await getTextResponse("/?name=my-do")).toEqual( + "Durable Object 'my-do' count: 0" + ); + expect(await getTextResponse("/increment?name=my-do")).toEqual( + "Durable Object 'my-do' count: 1" + ); + expect(await getTextResponse("/increment?name=my-do")).toEqual( + "Durable Object 'my-do' count: 2" + ); + expect(await getTextResponse("/decrement?name=my-do")).toEqual( + "Durable Object 'my-do' count: 1" + ); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/package.json b/packages/vite-plugin-cloudflare/playground/durable-objects/package.json new file mode 100644 index 0000000000000..36fc859b8f86b --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/package.json @@ -0,0 +1,19 @@ +{ + "name": "@playground/durable-objects", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/src/index.ts b/packages/vite-plugin-cloudflare/playground/durable-objects/src/index.ts new file mode 100644 index 0000000000000..f566ea7a4fb2f --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/src/index.ts @@ -0,0 +1,62 @@ +import { DurableObject } from "cloudflare:workers"; + +interface Env { + COUNTERS: DurableObjectNamespace; +} + +export class Counter extends DurableObject { + async getCounterValue() { + let value = ((await this.ctx.storage.get("value")) as number) || 0; + + return value; + } + + async increment(amount = 1) { + let value = ((await this.ctx.storage.get("value")) as number) || 0; + value += amount; + await this.ctx.storage.put("value", value); + + return value; + } + + async decrement(amount = 1) { + let value = ((await this.ctx.storage.get("value")) as number) || 0; + value -= amount; + await this.ctx.storage.put("value", value); + + return value; + } +} + +export default { + async fetch(request, env) { + let url = new URL(request.url); + let name = url.searchParams.get("name"); + + if (!name) { + return new Response( + "Select a Durable Object to contact by using the `name` URL query string parameter, for example, ?name=A" + ); + } + + const id = env.COUNTERS.idFromName(name); + const stub = env.COUNTERS.get(id); + let count = null; + + switch (url.pathname) { + case "/increment": + count = await stub.increment(); + break; + case "/decrement": + count = await stub.decrement(); + break; + case "/": + count = await stub.getCounterValue(); + break; + default: + return new Response("Not found", { status: 404 }); + } + + return new Response(`Durable Object '${name}' count: ${count}`); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.json b/packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.worker.json new file mode 100644 index 0000000000000..da43778b826f4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/turbo.json b/packages/vite-plugin-cloudflare/playground/durable-objects/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/vite.config.ts b/packages/vite-plugin-cloudflare/playground/durable-objects/vite.config.ts new file mode 100644 index 0000000000000..55c8276338d72 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/vite.config.ts @@ -0,0 +1,6 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/wrangler.toml b/packages/vite-plugin-cloudflare/playground/durable-objects/wrangler.toml new file mode 100644 index 0000000000000..44ec372080cb0 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/wrangler.toml @@ -0,0 +1,10 @@ +name = "worker" +main = "./src/index.ts" +compatibility_date = "2024-12-30" + +[durable_objects] +bindings = [{ name = "COUNTERS", class_name = "Counter" }] + +[[migrations]] +tag = "v1" +new_classes = ["Counter"] diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/__tests__/external-durable-object.spec.ts b/packages/vite-plugin-cloudflare/playground/external-durable-objects/__tests__/external-durable-object.spec.ts new file mode 100644 index 0000000000000..85402c13d3b97 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/__tests__/external-durable-object.spec.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from "vitest"; +import { getTextResponse } from "../../__test-utils__"; + +describe("external durable objects", async () => { + test("can use `scriptName` to bind to a Durable Object defined in another Worker", async () => { + expect(await getTextResponse("/?name=my-do")).toEqual( + 'From worker-a: {"name":"my-do","count":0}' + ); + expect(await getTextResponse("/increment?name=my-do")).toEqual( + 'From worker-a: {"name":"my-do","count":1}' + ); + expect(await getTextResponse("/increment?name=my-do")).toEqual( + 'From worker-a: {"name":"my-do","count":2}' + ); + expect(await getTextResponse("/?name=my-do")).toEqual( + 'From worker-a: {"name":"my-do","count":2}' + ); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/package.json b/packages/vite-plugin-cloudflare/playground/external-durable-objects/package.json new file mode 100644 index 0000000000000..8b0925ce9a194 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/package.json @@ -0,0 +1,19 @@ +{ + "name": "@playground/external-durable-objects", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.json b/packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.worker.json new file mode 100644 index 0000000000000..373f783bea729 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["worker-a", "worker-b"] +} diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/turbo.json b/packages/vite-plugin-cloudflare/playground/external-durable-objects/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/vite.config.ts b/packages/vite-plugin-cloudflare/playground/external-durable-objects/vite.config.ts new file mode 100644 index 0000000000000..0d3b728cc5920 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/vite.config.ts @@ -0,0 +1,12 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + cloudflare({ + configPath: "./worker-a/wrangler.toml", + auxiliaryWorkers: [{ configPath: "./worker-b/wrangler.toml" }], + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-a/index.ts b/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-a/index.ts new file mode 100644 index 0000000000000..9a41e7ee3d6eb --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-a/index.ts @@ -0,0 +1,35 @@ +import type { Counter } from "../worker-b"; + +interface Env { + COUNTERS: DurableObjectNamespace; +} + +export default { + async fetch(request, env) { + let url = new URL(request.url); + let name = url.searchParams.get("name"); + + if (!name) { + throw new Error( + "Select a Durable Object to contact by using the `name` URL query string parameter, for example, ?name=A" + ); + } + + const id = env.COUNTERS.idFromName(name); + const stub = env.COUNTERS.get(id); + let count = null; + + switch (url.pathname) { + case "/increment": + count = await stub.increment(); + break; + case "/": + count = await stub.getCounterValue(); + break; + default: + throw new Error("Unhandled route"); + } + + return new Response(`From worker-a: ${JSON.stringify({ name, count })}`); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-a/wrangler.toml b/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-a/wrangler.toml new file mode 100644 index 0000000000000..5422696b46c9e --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-a/wrangler.toml @@ -0,0 +1,12 @@ +name = "worker-a" +main = "./index.ts" +compatibility_date = "2024-12-30" + +[durable_objects] +bindings = [ + { name = "COUNTERS", class_name = "Counter", script_name = "worker-b" }, +] + +[[migrations]] +tag = "v1" +new_classes = ["Counter"] diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-b/index.ts b/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-b/index.ts new file mode 100644 index 0000000000000..4ef212bb9c420 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-b/index.ts @@ -0,0 +1,17 @@ +import { DurableObject } from "cloudflare:workers"; + +export class Counter extends DurableObject { + async getCounterValue() { + let value = ((await this.ctx.storage.get("value")) as number) || 0; + + return value; + } + + async increment(amount = 1) { + let value = ((await this.ctx.storage.get("value")) as number) || 0; + value += amount; + await this.ctx.storage.put("value", value); + + return value; + } +} diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-b/wrangler.toml b/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-b/wrangler.toml new file mode 100644 index 0000000000000..eb8703d488982 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/worker-b/wrangler.toml @@ -0,0 +1,3 @@ +name = "worker-b" +main = "./index.ts" +compatibility_date = "2024-12-30" diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/__tests__/workflows.spec.ts b/packages/vite-plugin-cloudflare/playground/external-workflows/__tests__/workflows.spec.ts new file mode 100644 index 0000000000000..5b39535f978d3 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/__tests__/workflows.spec.ts @@ -0,0 +1,40 @@ +import { expect, test, vi } from "vitest"; +import { getJsonResponse } from "../../__test-utils__"; + +test("creates a Workflow with an ID", async () => { + const instanceId = "test-id"; + + expect(await getJsonResponse(`/create?id=${instanceId}`)).toEqual({ + id: instanceId, + status: { + status: "running", + __LOCAL_DEV_STEP_OUTPUTS: [], + output: null, + }, + }); + + await vi.waitFor( + async () => { + expect(await getJsonResponse(`/get?id=${instanceId}`)).toEqual({ + status: "running", + __LOCAL_DEV_STEP_OUTPUTS: [{ output: "First step result" }], + output: null, + }); + }, + { timeout: 5000 } + ); + + await vi.waitFor( + async () => { + expect(await getJsonResponse(`/get?id=${instanceId}`)).toEqual({ + status: "complete", + __LOCAL_DEV_STEP_OUTPUTS: [ + { output: "First step result" }, + { output: "Second step result" }, + ], + output: "Workflow output", + }); + }, + { timeout: 5000 } + ); +}); diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/package.json b/packages/vite-plugin-cloudflare/playground/external-workflows/package.json new file mode 100644 index 0000000000000..fe2750ee99b89 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/package.json @@ -0,0 +1,19 @@ +{ + "name": "@playground/external-workflows", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.json b/packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.worker.json new file mode 100644 index 0000000000000..373f783bea729 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["worker-a", "worker-b"] +} diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/turbo.json b/packages/vite-plugin-cloudflare/playground/external-workflows/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/vite.config.ts b/packages/vite-plugin-cloudflare/playground/external-workflows/vite.config.ts new file mode 100644 index 0000000000000..0d3b728cc5920 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/vite.config.ts @@ -0,0 +1,12 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + cloudflare({ + configPath: "./worker-a/wrangler.toml", + auxiliaryWorkers: [{ configPath: "./worker-b/wrangler.toml" }], + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/worker-a/index.ts b/packages/vite-plugin-cloudflare/playground/external-workflows/worker-a/index.ts new file mode 100644 index 0000000000000..c86cda824140f --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/worker-a/index.ts @@ -0,0 +1,37 @@ +interface Env { + MY_WORKFLOW: Workflow; +} + +export default { + async fetch(request, env) { + const url = new URL(request.url); + const id = url.searchParams.get("id"); + + if (url.pathname === "/create") { + const instance = await env.MY_WORKFLOW.create( + id === null ? undefined : { id } + ); + + return Response.json({ + id: instance.id, + status: await instance.status(), + }); + } + + if (url.pathname === "/get") { + if (id === null) { + return new Response( + "Please provide an id (`/get?id=unique-instance-id`)" + ); + } + + const instance = await env.MY_WORKFLOW.get(id); + + return Response.json(await instance.status()); + } + + return new Response( + "Create a new Workflow instance (`/create` or `/create?id=unique-instance-id`) or inspect an existing instance (`/get?id=unique-instance-id`)." + ); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/worker-a/wrangler.toml b/packages/vite-plugin-cloudflare/playground/external-workflows/worker-a/wrangler.toml new file mode 100644 index 0000000000000..7b93a9764494d --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/worker-a/wrangler.toml @@ -0,0 +1,9 @@ +name = "worker-a" +main = "./index.ts" +compatibility_date = "2024-12-30" + +[[workflows]] +name = "workflow" +binding = "MY_WORKFLOW" +script_name = "worker-b" +class_name = "MyWorkflow" diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/worker-b/index.ts b/packages/vite-plugin-cloudflare/playground/external-workflows/worker-b/index.ts new file mode 100644 index 0000000000000..9807c30252454 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/worker-b/index.ts @@ -0,0 +1,22 @@ +import { WorkflowEntrypoint } from "cloudflare:workers"; +import type { WorkflowEvent, WorkflowStep } from "cloudflare:workers"; + +export class MyWorkflow extends WorkflowEntrypoint { + override async run(event: WorkflowEvent, step: WorkflowStep) { + await step.do("first step", async () => { + return { + output: "First step result", + }; + }); + + await step.sleep("sleep", "1 second"); + + await step.do("second step", async () => { + return { + output: "Second step result", + }; + }); + + return "Workflow output"; + } +} diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/worker-b/wrangler.toml b/packages/vite-plugin-cloudflare/playground/external-workflows/worker-b/wrangler.toml new file mode 100644 index 0000000000000..eb8703d488982 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/worker-b/wrangler.toml @@ -0,0 +1,3 @@ +name = "worker-b" +main = "./index.ts" +compatibility_date = "2024-12-30" diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/__tests__/hot-channel.spec.ts b/packages/vite-plugin-cloudflare/playground/hot-channel/__tests__/hot-channel.spec.ts new file mode 100644 index 0000000000000..cd167dabedd5a --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/__tests__/hot-channel.spec.ts @@ -0,0 +1,10 @@ +import { expect, test } from "vitest"; +import { isBuild, serverLogs } from "../../__test-utils__"; + +test.runIf(!isBuild)("client receives custom events", async () => { + expect(serverLogs.info.join()).toContain("__server-event-data-received__"); +}); + +test.runIf(!isBuild)("server receives custom events", async () => { + expect(serverLogs.info.join()).toContain("__client-event-data-received__"); +}); diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/package.json b/packages/vite-plugin-cloudflare/playground/hot-channel/package.json new file mode 100644 index 0000000000000..4c6b1b82700c0 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/package.json @@ -0,0 +1,18 @@ +{ + "name": "@playground/hot-channel", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/src/index.ts b/packages/vite-plugin-cloudflare/playground/hot-channel/src/index.ts new file mode 100644 index 0000000000000..76e2509f3c6b3 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/src/index.ts @@ -0,0 +1,15 @@ +import.meta.hot?.on("server-event", (payload) => { + console.log(`__${payload}-received__`); + import.meta.hot?.send("client-event", "client-event-data"); +}); + +// Needed to log here rather than in the test-plugin in order for it to appear in `serverLogs` +import.meta.hot?.on("client-event-received", (payload) => { + console.log(`__${payload}-received__`); +}); + +export default { + async fetch() { + return new Response("OK"); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.json b/packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.worker.json new file mode 100644 index 0000000000000..da43778b826f4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/turbo.json b/packages/vite-plugin-cloudflare/playground/hot-channel/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/vite.config.ts b/packages/vite-plugin-cloudflare/playground/hot-channel/vite.config.ts new file mode 100644 index 0000000000000..eb2c68086fba0 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/vite.config.ts @@ -0,0 +1,26 @@ +import assert from "node:assert"; +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + cloudflare({ persistState: false }), + { + name: "test-plugin", + configureServer(viteDevServer) { + const worker = viteDevServer.environments.worker; + assert(worker, `'worker' environment not found`); + + return () => { + viteDevServer.middlewares.use(async (req, res, next) => { + worker.hot.send("server-event", "server-event-data"); + worker.hot.on("client-event", (payload) => { + worker.hot.send("client-event-received", payload); + }); + next(); + }); + }; + }, + }, + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/wrangler.toml b/packages/vite-plugin-cloudflare/playground/hot-channel/wrangler.toml new file mode 100644 index 0000000000000..9a44ca3733ffb --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/wrangler.toml @@ -0,0 +1,3 @@ +name = "worker" +main = "./src/index.ts" +compatibility_date = "2024-12-30" diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/__tests__/module-resolution.spec.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/__tests__/module-resolution.spec.ts new file mode 100644 index 0000000000000..7b1d9c019701d --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/__tests__/module-resolution.spec.ts @@ -0,0 +1,121 @@ +import { afterAll, describe, expect, test } from "vitest"; +import { + getJsonResponse, + getTextResponse, + isBuild, + page, + serverLogs, + viteTestUrl, +} from "../../__test-utils__"; + +describe("module resolution", async () => { + afterAll(() => { + expect(serverLogs.errors).toEqual([]); + }); + + describe("basic module resolution", () => { + test("`require` js/cjs files with specifying their file extension", async () => { + const result = await getJsonResponse("/require-ext"); + expect(result).toEqual({ + "(requires/ext) hello.cjs (wrong-extension)": null, + "(requires/ext) helloWorld": "hello (.js) world (.cjs)", + "(requires/ext) world.js (wrong-extension)": null, + }); + }); + + test("`require` js/cjs files without specifying their file extension", async () => { + const result = await getJsonResponse("/require-no-ext"); + expect(result).toEqual({ + "(requires/no-ext) helloWorld": "hello (.js) world (.cjs)", + }); + }); + + test("`require` json files", async () => { + const result = await getJsonResponse("/require-json"); + expect(result).toEqual({ + "(requires/json) package name": + "@playground/module-resolution-requires", + "(requires/json) package version": "1.0.0", + }); + }); + }); + + describe("Cloudflare specific module resolution", () => { + test("internal imports from `cloudflare:*`", async () => { + const result = await getJsonResponse("/cloudflare-imports"); + + // Note: in some cases the DurableObject class name (erroneously) includes + // the `Base` suffix, that's a workerd bug that happens for us on builds + const durableObjectName = isBuild ? "DurableObjectBase" : "DurableObject"; + + expect(result).toEqual({ + "(cloudflare:workers) WorkerEntrypoint.name": "WorkerEntrypoint", + "(cloudflare:workers) DurableObject.name": durableObjectName, + "(cloudflare:sockets) typeof connect": "function", + }); + }); + + test("external imports from `cloudflare:*`", async () => { + const result = await getJsonResponse("/external-cloudflare-imports"); + + // Note: in some cases the DurableObject class name (erroneously) includes + // the `Base` suffix, that's a workerd bug that happens for us on builds + const durableObjectName = isBuild ? "DurableObjectBase" : "DurableObject"; + + expect(result).toEqual({ + "(EXTERNAL) (cloudflare:workers) DurableObject.name": durableObjectName, + }); + }); + }); + + /** + * These tests check that module resolution works as intended for various third party npm packages (these tests are more + * realistic but less helpful than the other ones (these can be considered integration tests whilst the other unit tests)). + * + * These are packages that involve non-trivial module resolutions (and that in the past we had issues with), they have no + * special meaning to us. + */ + describe("third party packages resolutions", () => { + test("react", async () => { + const result = await getJsonResponse("/third-party/react"); + expect(result).toEqual({ + "(react) reactVersionsMatch": true, + "(react) typeof React": "object", + "(react) typeof React.cloneElement": "function", + }); + }); + + test("@remix-run/cloudflare", async () => { + const result = await getJsonResponse("/third-party/remix"); + expect(result).toEqual({ + "(remix) remixRunCloudflareCookieName": + "my-remix-run-cloudflare-cookie", + }); + }); + + test("discord-api-types/v10", async () => { + const result = await getJsonResponse("/third-party/discord-api-types"); + expect(result).toEqual({ + "(discord-api-types/v10) RPCErrorCodes.InvalidUser": 4010, + "(discord-api-types/v10) Utils.isLinkButton({})": false, + }); + }); + + test("slash-create", async () => { + const result = await getJsonResponse("/third-party/slash-create"); + expect(result).toEqual({ + "(slash-create/web) VERSION": "6.2.1", + "(slash-create/web) myCollection.random()": 54321, + "(slash-create/web) slashCreatorInstance is instance of SlashCreator": + true, + }); + }); + }); + + describe("user aliases", () => { + test("imports from an aliased package", async () => { + const result = await getTextResponse("/@alias/test"); + expect(result).toBe("OK!"); + }); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/__tests__/no-prebundling/module-resolution-no-prebundling.spec.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/__tests__/no-prebundling/module-resolution-no-prebundling.spec.ts new file mode 100644 index 0000000000000..183397eb7e76e --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/__tests__/no-prebundling/module-resolution-no-prebundling.spec.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "vitest"; +import { + getTextResponse, + isBuild, + page, + viteTestUrl, +} from "../../../__test-utils__"; + +// TODO: test build +describe.runIf(!isBuild)("module resolution without prebundling", async () => { + test("importing a non-prebundled `@cloudflare-dev-module-resolution/requires/no-ext`", async () => { + await page.goto(`${viteTestUrl}/require-no-ext`); + const errorText = await page + .locator("vite-error-overlay pre.message") + .textContent(); + expect(errorText).toMatch( + /^\[Error\] Trying to import non-prebundled module \(only prebundled modules are allowed\):/ + ); + expect(errorText).toContain("/no-ext"); + }); + + test("importing a non-prebundled `@cloudflare-dev-module-resolution/requires/ext`", async () => { + await page.goto(`${viteTestUrl}/require-ext`); + const errorText = await page + .locator("vite-error-overlay pre.message") + .textContent(); + expect(errorText).toMatch( + /^\[Error\] Trying to import non-prebundled module \(only prebundled modules are allowed\):/ + ); + expect(errorText).toContain("/ext"); + }); + + test("importing a non-prebundled `react`", async () => { + await page.goto(`${viteTestUrl}/third-party/react`); + const errorText = await page + .locator("vite-error-overlay pre.message") + .textContent(); + expect(errorText).toMatch( + /^\[Error\] Trying to import non-prebundled module \(only prebundled modules are allowed\):/ + ); + expect(errorText).toContain("react"); + }); + + describe("user aliases", () => { + test("imports from an aliased package", async () => { + const result = await getTextResponse("/@alias/test"); + expect(result).toBe("OK!"); + }); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/package.json b/packages/vite-plugin-cloudflare/playground/module-resolution/package.json new file mode 100644 index 0000000000000..46e03c0e5719b --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/package.json @@ -0,0 +1,29 @@ +{ + "name": "@playground/module-resolution", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "build:no-prebundling": "vite build --app -c ./vite.config.no-prebundling.ts", + "check:types": "tsc --build", + "dev": "vite dev", + "dev:no-prebundling": "vite dev -c ./vite.config.no-prebundling.ts", + "preview": "vite preview", + "preview:no-prebundling": "vite preview -c ./vite.config.no-prebundling.ts" + }, + "devDependencies": { + "@cloudflare-dev-module-resolution/imports": "file:./packages/imports", + "@cloudflare-dev-module-resolution/requires": "file:./packages/requires", + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "@remix-run/cloudflare": "2.12.0", + "@types/react": "^18.3.11", + "discord-api-types": "0.37.98", + "react": "18.3.1", + "slash-create": "6.2.1", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/cloudflare-builtins.d.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/cloudflare-builtins.d.ts new file mode 100644 index 0000000000000..aff8f9f3354bf --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/cloudflare-builtins.d.ts @@ -0,0 +1 @@ +export const durableObjectName: string; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/cloudflare-builtins.js b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/cloudflare-builtins.js new file mode 100644 index 0000000000000..8c1c42dbf49e8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/cloudflare-builtins.js @@ -0,0 +1,3 @@ +import { DurableObject } from "cloudflare:workers"; + +export const durableObjectName = DurableObject.name; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/package.json b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/package.json new file mode 100644 index 0000000000000..82c3d9be06379 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports/package.json @@ -0,0 +1,12 @@ +{ + "name": "@playground/module-resolution-imports", + "version": "1.0.0", + "description": "a packages with various imports", + "type": "module", + "exports": { + "./cloudflare-builtins": { + "default": "./cloudflare-builtins.js", + "types": "./cloudflare-builtins.d.ts" + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/ext.d.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/ext.d.ts new file mode 100644 index 0000000000000..9d7f1175a5f78 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/ext.d.ts @@ -0,0 +1,4 @@ +export const helloWorldExt: string; + +export const helloCjs: null; +export const worldJs: null; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/ext.js b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/ext.js new file mode 100644 index 0000000000000..f60cb4c237696 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/ext.js @@ -0,0 +1,13 @@ +exports.helloWorldExt = `${require("./hello.js").default} ${require("./world.cjs").default}`; + +let helloCjs = null; +try { + helloCjs = require("./hello.cjs"); +} catch {} +exports.helloCjs = helloCjs; + +let worldJs = null; +try { + helloCjs = require("./world.js"); +} catch {} +exports.worldJs = worldJs; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/hello.js b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/hello.js new file mode 100644 index 0000000000000..5bc6cc7bcd5ff --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/hello.js @@ -0,0 +1 @@ +exports.default = "hello (.js)"; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/index.d.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/index.d.ts new file mode 100644 index 0000000000000..edcb8b0777c00 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/index.d.ts @@ -0,0 +1 @@ +export const helloWorldNoExt: string; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/index.js b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/index.js new file mode 100644 index 0000000000000..1622dc2414c9c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/index.js @@ -0,0 +1 @@ +exports.helloWorldNoExt = `${require("./hello").default} ${require("./world").default}`; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/json.d.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/json.d.ts new file mode 100644 index 0000000000000..d948ec6aaa019 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/json.d.ts @@ -0,0 +1,3 @@ +export const packageName: string; + +export const packageVersion: string; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/json.js b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/json.js new file mode 100644 index 0000000000000..bdfff42fa2048 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/json.js @@ -0,0 +1,3 @@ +exports.packageName = `${require("./package.json").name}`; + +exports.packageVersion = `${require("./package.json").version}`; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/no-ext.d.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/no-ext.d.ts new file mode 100644 index 0000000000000..edcb8b0777c00 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/no-ext.d.ts @@ -0,0 +1 @@ +export const helloWorldNoExt: string; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/no-ext.js b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/no-ext.js new file mode 100644 index 0000000000000..1622dc2414c9c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/no-ext.js @@ -0,0 +1 @@ +exports.helloWorldNoExt = `${require("./hello").default} ${require("./world").default}`; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/package.json b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/package.json new file mode 100644 index 0000000000000..f2ab4f78cc2af --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/package.json @@ -0,0 +1,19 @@ +{ + "name": "@playground/module-resolution-requires", + "version": "1.0.0", + "description": "a packages which has require calls of different types each used in its own entrypoint", + "exports": { + "./ext": { + "default": "./ext.js", + "types": "./ext.d.ts" + }, + "./no-ext": { + "default": "./no-ext.js", + "types": "./no-ext.d.ts" + }, + "./json": { + "default": "./json.js", + "types": "./json.d.ts" + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/world.cjs b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/world.cjs new file mode 100644 index 0000000000000..067badd1a5d30 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires/world.cjs @@ -0,0 +1 @@ +exports.default = "world (.cjs)"; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/aliasing.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/aliasing.ts new file mode 100644 index 0000000000000..48a61f4f426a2 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/aliasing.ts @@ -0,0 +1,3 @@ +export function test() { + return new Response("OK!"); +} diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/cloudflare-imports.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/cloudflare-imports.ts new file mode 100644 index 0000000000000..d843ffd817019 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/cloudflare-imports.ts @@ -0,0 +1,8 @@ +import { connect } from "cloudflare:sockets"; +import { DurableObject, WorkerEntrypoint } from "cloudflare:workers"; + +export default { + "(cloudflare:workers) WorkerEntrypoint.name": WorkerEntrypoint.name, + "(cloudflare:workers) DurableObject.name": DurableObject.name, + "(cloudflare:sockets) typeof connect": typeof connect, +}; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/external-cloudflare-imports.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/external-cloudflare-imports.ts new file mode 100644 index 0000000000000..ef1e4bd370756 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/external-cloudflare-imports.ts @@ -0,0 +1,5 @@ +import { durableObjectName } from "../packages/imports/cloudflare-builtins"; + +export default { + "(EXTERNAL) (cloudflare:workers) DurableObject.name": durableObjectName, +}; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/index.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/index.ts new file mode 100644 index 0000000000000..7a9c009977b2e --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/index.ts @@ -0,0 +1,45 @@ +const modules = import.meta.glob("../src/**/*.ts"); + +export default { + async fetch(request) { + const url = new URL(request.url); + const path = url.pathname; + + const filePath = `${path.replace(/^\//, "./")}.ts`; + + if (modules[filePath]) { + const mod = await modules[filePath](); + return Response.json((mod as { default: unknown }).default); + } + + if (path === "/@alias/test") { + const { test } = await import("@alias/test"); + return test(); + } + + const html = ` + +

Module Resolution App

+

+ This app is an example/test for dependencies module resolution being performed in the Cloudflare environment (inside the workerd runtime) +

+
+

Available Routes

+
    + ${[...Object.keys(modules), "/@alias/test"] + .map((path) => path.replace(/^\.\//, "/").replace(/\.ts$/, "")) + .map( + (route) => + `
  • ${route}
  • ` + ) + .join("\n")} +
+ `; + + return new Response(html, { + headers: { + "content-type": "text/html;charset=UTF-8", + }, + }); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/require-ext.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/require-ext.ts new file mode 100644 index 0000000000000..f307065d01c95 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/require-ext.ts @@ -0,0 +1,11 @@ +import { + helloCjs, + helloWorldExt, + worldJs, +} from "@cloudflare-dev-module-resolution/requires/ext"; + +export default { + "(requires/ext) helloWorld": helloWorldExt, + "(requires/ext) hello.cjs (wrong-extension)": helloCjs, + "(requires/ext) world.js (wrong-extension)": worldJs, +}; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/require-json.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/require-json.ts new file mode 100644 index 0000000000000..6269dfdcd228d --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/require-json.ts @@ -0,0 +1,9 @@ +import { + packageName, + packageVersion, +} from "@cloudflare-dev-module-resolution/requires/json"; + +export default { + "(requires/json) package name": packageName, + "(requires/json) package version": packageVersion, +}; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/require-no-ext.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/require-no-ext.ts new file mode 100644 index 0000000000000..729b09f954d94 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/require-no-ext.ts @@ -0,0 +1,5 @@ +import { helloWorldNoExt } from "@cloudflare-dev-module-resolution/requires/no-ext"; + +export default { + "(requires/no-ext) helloWorld": helloWorldNoExt, +}; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/discord-api-types.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/discord-api-types.ts new file mode 100644 index 0000000000000..d9d2889622ff2 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/discord-api-types.ts @@ -0,0 +1,11 @@ +import { RPCErrorCodes, Utils } from "discord-api-types/v10"; + +// resolving discord-api-types/v10 (package which uses `require()`s without extensions +// can be problematic, see: https://github.com/dario-piotrowicz/vitest-pool-workers-ext-repro) +export default { + "(discord-api-types/v10) Utils.isLinkButton({})": Utils.isLinkButton( + {} as any + ), + "(discord-api-types/v10) RPCErrorCodes.InvalidUser": + RPCErrorCodes.InvalidUser, +}; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/react.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/react.ts new file mode 100644 index 0000000000000..f2f9ed35a496c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/react.ts @@ -0,0 +1,7 @@ +import React, { version as ReactVersion } from "react"; + +export default { + "(react) typeof React": typeof React, + "(react) typeof React.cloneElement": typeof React.cloneElement, + "(react) reactVersionsMatch": React.version === ReactVersion, +}; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/remix.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/remix.ts new file mode 100644 index 0000000000000..41716e6900e6c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/remix.ts @@ -0,0 +1,7 @@ +import { createCookie } from "@remix-run/cloudflare"; + +export default { + "(remix) remixRunCloudflareCookieName": createCookie( + "my-remix-run-cloudflare-cookie" + ).name, +}; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/slash-create.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/slash-create.ts new file mode 100644 index 0000000000000..55e6df6ee6374 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/src/third-party/slash-create.ts @@ -0,0 +1,22 @@ +// Note: we can import from the /web entrypoint, if we import from the default one things don't work +// both because of missing built-in node modules and also because of other module resolution +// issues. +// Note that in the readme itself: https://github.com/Snazzah/slash-create?tab=readme-ov-file#using-webservers +// they say to use /web, so I do think that this is good enough (should the standard import also work?) +import { Collection, SlashCreator, VERSION } from "slash-create/web"; + +const slashCreatorInstance = new SlashCreator({ + applicationID: "xxx", +}); + +const myCollection = new Collection([["a number", 54321]]); + +// The slash-create package `require`s its package.json for its version +// (source: https://github.com/Snazzah/slash-create/blob/a08e8f35bc/src/constants.ts#L13) +// we need to make sure that we do support this +export default { + "(slash-create/web) VERSION": VERSION, + "(slash-create/web) slashCreatorInstance is instance of SlashCreator": + slashCreatorInstance instanceof SlashCreator, + "(slash-create/web) myCollection.random()": myCollection.random(), +}; diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.json b/packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.node.json new file mode 100644 index 0000000000000..45f006f5d0972 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "vite.config.*.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.worker.json new file mode 100644 index 0000000000000..ee09b15a87f9d --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/tsconfig.worker.json @@ -0,0 +1,9 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "compilerOptions": { + "paths": { + "@alias/test": ["./src/aliasing.ts"] + } + }, + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/turbo.json b/packages/vite-plugin-cloudflare/playground/module-resolution/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/vite.config.no-prebundling.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/vite.config.no-prebundling.ts new file mode 100644 index 0000000000000..eff8346bdf5e8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/vite.config.no-prebundling.ts @@ -0,0 +1,26 @@ +import { resolve } from "node:path"; +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + resolve: { + alias: { + "@alias/test": resolve(__dirname, "./src/aliasing.ts"), + }, + }, + environments: { + worker: { + optimizeDeps: { + // we specifically opt-out of prebundling for the following dependencies + exclude: ["@cloudflare-dev-module-resolution/requires", "react"], + }, + resolve: { + // external modules don't get prebundled + external: ["@cloudflare-dev-module-resolution/requires/ext"], + }, + }, + }, + plugins: [ + cloudflare({ viteEnvironment: { name: "worker" }, persistState: false }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/vite.config.ts b/packages/vite-plugin-cloudflare/playground/module-resolution/vite.config.ts new file mode 100644 index 0000000000000..67f2fb471591f --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/vite.config.ts @@ -0,0 +1,12 @@ +import { resolve } from "node:path"; +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + resolve: { + alias: { + "@alias/test": resolve(__dirname, "./src/aliasing.ts"), + }, + }, + plugins: [cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/wrangler.toml b/packages/vite-plugin-cloudflare/playground/module-resolution/wrangler.toml new file mode 100644 index 0000000000000..9a44ca3733ffb --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/wrangler.toml @@ -0,0 +1,3 @@ +name = "worker" +main = "./src/index.ts" +compatibility_date = "2024-12-30" diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/.gitignore b/packages/vite-plugin-cloudflare/playground/multi-worker/.gitignore new file mode 100644 index 0000000000000..672df3401348b --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/.gitignore @@ -0,0 +1,3 @@ +custom-root-output-directory +custom-environment-output-directory +custom-worker-output-directory \ No newline at end of file diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/custom-output-directories/multi-worker.spec.ts b/packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/custom-output-directories/multi-worker.spec.ts new file mode 100644 index 0000000000000..fb01aab887faa --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/custom-output-directories/multi-worker.spec.ts @@ -0,0 +1,24 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import { describe, expect, test } from "vitest"; +import { getJsonResponse, isBuild, rootDir } from "../../../__test-utils__"; + +describe.runIf(isBuild)("output directories", () => { + test("creates the correct output directories", () => { + expect( + fs.existsSync( + path.join(rootDir, "custom-root-output-directory", "worker_a") + ) + ).toBe(true); + expect( + fs.existsSync(path.join(rootDir, "custom-worker-output-directory")) + ).toBe(true); + }); +}); + +describe("multi-worker service bindings", async () => { + test("returns a response from another worker", async () => { + const result = await getJsonResponse("/fetch"); + expect(result).toEqual({ result: { name: "Worker B" } }); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/multi-worker.spec.ts b/packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/multi-worker.spec.ts new file mode 100644 index 0000000000000..61ccd1d253a03 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/multi-worker.spec.ts @@ -0,0 +1,49 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import { describe, expect, test } from "vitest"; +import { + getJsonResponse, + isBuild, + rootDir, + serverLogs, +} from "../../__test-utils__"; + +describe.runIf(isBuild)("output directories", () => { + test("creates the correct output directories", () => { + expect(fs.existsSync(path.join(rootDir, "dist", "worker_a"))).toBe(true); + expect(fs.existsSync(path.join(rootDir, "dist", "worker_b"))).toBe(true); + }); +}); + +describe("multi-worker basic functionality", async () => { + test("worker configs warnings are not present in the terminal", async () => { + expect(serverLogs.warns).toEqual([]); + }); + + test("entry worker returns a response", async () => { + const result = await getJsonResponse(); + expect(result).toEqual({ name: "Worker A" }); + }); +}); + +describe("multi-worker service bindings", async () => { + test("returns a response from another worker", async () => { + const result = await getJsonResponse("/fetch"); + expect(result).toEqual({ result: { name: "Worker B" } }); + }); + + test("calls an RPC method on another worker", async () => { + const result = await getJsonResponse("/rpc-method"); + expect(result).toEqual({ result: 9 }); + }); + + test("calls an RPC getter on another worker", async () => { + const result = await getJsonResponse("/rpc-getter"); + expect(result).toEqual({ result: "Cloudflare" }); + }); + + test("calls an RPC method on a named entrypoint", async () => { + const result = await getJsonResponse("/rpc-named-entrypoint"); + expect(result).toEqual({ result: 20 }); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/with-worker-configs-warning/multi-worker.spec.ts b/packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/with-worker-configs-warning/multi-worker.spec.ts new file mode 100644 index 0000000000000..282a407974ad0 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/__tests__/with-worker-configs-warning/multi-worker.spec.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from "vitest"; +import { getJsonResponse, serverLogs } from "../../../__test-utils__"; + +describe("multi-worker basic functionality", async () => { + test("a worker configs warning is present in the terminal", async () => { + /** + * Note: we always expect the warning once for both values of `isBuild`. + * For dev is obvious, for builds we do get the warning once because we get it when we + * build the application but not when we run its preview (since that reads the generated wrangler.json) + */ + expect(serverLogs.warns).toEqual([ + expect.stringMatching( + /your workers configs contain configuration options which are ignored[\s\S]+preserve_file_names[\s\S]+tsconfig[\s\S]+build/ + ), + ]); + }); + + test("entry worker returns a response", async () => { + const result = await getJsonResponse(); + expect(result).toEqual({ name: "Worker A" }); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/package.json b/packages/vite-plugin-cloudflare/playground/multi-worker/package.json new file mode 100644 index 0000000000000..1fe5345ec0a2f --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/package.json @@ -0,0 +1,24 @@ +{ + "name": "@playground/multi-worker", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "build:custom-output-directories": "vite build --app -c ./vite.config.custom-output-directories.ts", + "build:with-worker-configs-warning": "vite build --app -c vite.config.with-worker-configs-warning.ts", + "check:types": "tsc --build", + "dev": "vite dev", + "dev:with-worker-configs-warning": "vite dev -c vite.config.with-worker-configs-warning.ts", + "preview": "vite preview", + "preview:custom-output-directories": "vite preview -c ./vite.config.custom-output-directories.ts", + "preview:with-worker-configs-warning": "vite preview -c vite.config.with-worker-configs-warning.ts" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.json b/packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.node.json new file mode 100644 index 0000000000000..025b9cda82606 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": [ + "vite.config.ts", + "vite.config.custom-output-directories.ts", + "__tests__" + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.worker.json new file mode 100644 index 0000000000000..373f783bea729 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["worker-a", "worker-b"] +} diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/turbo.json b/packages/vite-plugin-cloudflare/playground/multi-worker/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.custom-output-directories.ts b/packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.custom-output-directories.ts new file mode 100644 index 0000000000000..a6abcef5e4853 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.custom-output-directories.ts @@ -0,0 +1,22 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "custom-root-output-directory", + }, + environments: { + worker_b: { + build: { + outDir: "custom-worker-output-directory", + }, + }, + }, + plugins: [ + cloudflare({ + configPath: "./worker-a/wrangler.toml", + auxiliaryWorkers: [{ configPath: "./worker-b/wrangler.toml" }], + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.ts b/packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.ts new file mode 100644 index 0000000000000..0d3b728cc5920 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.ts @@ -0,0 +1,12 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + cloudflare({ + configPath: "./worker-a/wrangler.toml", + auxiliaryWorkers: [{ configPath: "./worker-b/wrangler.toml" }], + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.with-worker-configs-warning.ts b/packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.with-worker-configs-warning.ts new file mode 100644 index 0000000000000..c6b25f1f51bca --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/vite.config.with-worker-configs-warning.ts @@ -0,0 +1,14 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + cloudflare({ + configPath: "./worker-a/wrangler.with-warning.toml", + auxiliaryWorkers: [ + { configPath: "./worker-b/wrangler.with-warning.toml" }, + ], + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/index.ts b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/index.ts new file mode 100644 index 0000000000000..66bc99bbb1855 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/index.ts @@ -0,0 +1,39 @@ +import type WorkerB from "../worker-b"; +import type { NamedEntrypoint } from "../worker-b"; + +interface Env { + WORKER_B: Fetcher; + NAMED_ENTRYPOINT: Fetcher; +} + +export default { + async fetch(request, env) { + const url = new URL(request.url); + + switch (url.pathname) { + case "/fetch": { + const response = await env.WORKER_B.fetch(request); + const result = await response.json(); + return Response.json({ + result, + }); + } + case "/rpc-method": { + const result = await env.WORKER_B.add(4, 5); + return Response.json({ + result, + }); + } + case "/rpc-getter": { + const result = await env.WORKER_B.name; + return Response.json({ result }); + } + case "/rpc-named-entrypoint": { + const result = await env.NAMED_ENTRYPOINT.multiply(4, 5); + return Response.json({ result }); + } + } + + return Response.json({ name: "Worker A" }); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/wrangler.toml b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/wrangler.toml new file mode 100644 index 0000000000000..6c9ff1ed511ca --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/wrangler.toml @@ -0,0 +1,8 @@ +name = "worker-a" +main = "./index.ts" +compatibility_date = "2024-12-30" + +services = [ + { binding = "WORKER_B", service = "worker-b" }, + { binding = "NAMED_ENTRYPOINT", service = "worker-b", entrypoint = "NamedEntrypoint" }, +] diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/wrangler.with-warning.toml b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/wrangler.with-warning.toml new file mode 100644 index 0000000000000..abf8e39ff46ae --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-a/wrangler.with-warning.toml @@ -0,0 +1,11 @@ +name = "worker-a" +main = "./index.ts" +compatibility_date = "2024-12-30" + +services = [ + { binding = "WORKER_B", service = "worker-b" }, + { binding = "NAMED_ENTRYPOINT", service = "worker-b", entrypoint = "NamedEntrypoint" }, +] + +preserve_file_names = true +tsconfig = "./tsconfig.custom.json" diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/index.ts b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/index.ts new file mode 100644 index 0000000000000..4ffee2cc7c7c6 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/index.ts @@ -0,0 +1,21 @@ +import { WorkerEntrypoint } from "cloudflare:workers"; + +export default class extends WorkerEntrypoint { + override fetch() { + return Response.json({ + name: "Worker B", + }); + } + add(a: number, b: number) { + return a + b; + } + get name() { + return "Cloudflare"; + } +} + +export class NamedEntrypoint extends WorkerEntrypoint { + multiply(a: number, b: number) { + return a * b; + } +} diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/wrangler.toml b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/wrangler.toml new file mode 100644 index 0000000000000..eb8703d488982 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/wrangler.toml @@ -0,0 +1,3 @@ +name = "worker-b" +main = "./index.ts" +compatibility_date = "2024-12-30" diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/wrangler.with-warning.toml b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/wrangler.with-warning.toml new file mode 100644 index 0000000000000..32a6615114861 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/worker-b/wrangler.with-warning.toml @@ -0,0 +1,6 @@ +name = "worker-b" +main = "./index.ts" +compatibility_date = "2024-12-30" + +[build] +command = "npm run build" diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-basic/basic.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-basic/basic.spec.ts new file mode 100644 index 0000000000000..fd059d27d15dc --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-basic/basic.spec.ts @@ -0,0 +1,7 @@ +import { expect, test } from "vitest"; +import { getTextResponse } from "../../../__test-utils__"; + +test("basic nodejs properties", async () => { + const result = await getTextResponse(); + expect(result).toBe(`"OK!"`); +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-cross-env/cross-env.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-cross-env/cross-env.spec.ts new file mode 100644 index 0000000000000..2d8bbcb996a23 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-cross-env/cross-env.spec.ts @@ -0,0 +1,7 @@ +import { expect, test } from "vitest"; +import { getTextResponse } from "../../../__test-utils__"; + +test("import unenv aliased 3rd party packages (e.g. cross-env)", async () => { + const result = await getTextResponse(); + expect(result).toBe(`"OK!"`); +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-crypto/crypto.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-crypto/crypto.spec.ts new file mode 100644 index 0000000000000..0746066a92390 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-crypto/crypto.spec.ts @@ -0,0 +1,24 @@ +import { expect, test } from "vitest"; +import { getTextResponse } from "../../../__test-utils__"; + +test("crypto.X509Certificate is implemented", async () => { + const result = await getTextResponse(); + expect(result).toMatchInlineSnapshot(` + ""OK!": -----BEGIN CERTIFICATE----- + MIICZjCCAc+gAwIBAgIUOsv8Y+x40C+gdNuu40N50KpGUhEwDQYJKoZIhvcNAQEL + BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA5MjAwOTA4MTNaFw0yNTA5 + MjAwOTA4MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw + HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEB + BQADgY0AMIGJAoGBALpJn3dUrNmZhZV02RbjZKTd5j3hpgTncF4lG4Y3sQA18k0l + 7pt6xpZuXYSFH7v2zTAxYy+uYyYwX2NZur48dZc76FSzIeuQdoTCkT0NacwFRTR5 + fEEqPvvB85ozYuyk8Bl3vSsonivOH3WftEDp9mjkHROQzS4wAZbIj7Cp+is/AgMB + AAGjUzBRMB0GA1UdDgQWBBSzFJSiPAw2tJOg8oUXrFBdqWI6zDAfBgNVHSMEGDAW + gBSzFJSiPAw2tJOg8oUXrFBdqWI6zDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 + DQEBCwUAA4GBACbto0+Ds40F7faRFFMwg5nPyh7gsiX+ZK3FYcrO3oxh5ejfzwow + DKOOje4Ncaw0rIkVpxacPyjg+wANuK2Nv/Z4CVAD3mneE4gwgRdn38q8IYN9AtSv + GzEf4UxiLBbUB6WRBgyVyquGfUMlKl/tnm4q0yeYQloYKSoHpGeHVJuN + -----END CERTIFICATE----- + " + `); +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-postgres/postgres.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-postgres/postgres.spec.ts new file mode 100644 index 0000000000000..e508cdfe373d9 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-postgres/postgres.spec.ts @@ -0,0 +1,17 @@ +import { expect, test } from "vitest"; +import { getJsonResponse, getTextResponse } from "../../../__test-utils__"; + +test("should be able to create a pg Client", async () => { + const result = await getTextResponse(); + expect(result).toMatchInlineSnapshot(`"hh-pgsql-public.ebi.ac.uk"`); +}); + +// Disabling actually querying the database in CI since we are getting this error: +// > too many connections for role 'reader' +test.runIf(!process.env.CI)( + "should be able to use pg library to send a query", + async () => { + const result = await getJsonResponse("/send-query"); + expect(result!.id).toEqual("1"); + } +); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process/process.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process/process.spec.ts new file mode 100644 index 0000000000000..28405eb6cc488 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process/process.spec.ts @@ -0,0 +1,7 @@ +import { expect, test } from "vitest"; +import { getTextResponse } from "../../../__test-utils__"; + +test("should support process global", async () => { + const result = await getTextResponse(); + expect(result).toBe(`OK!`); +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-random/random.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-random/random.spec.ts new file mode 100644 index 0000000000000..4e6789d2675dc --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-random/random.spec.ts @@ -0,0 +1,12 @@ +import { expect, test } from "vitest"; +import { getJsonResponse } from "../../../__test-utils__"; + +test("should be able to call `getRandomValues()` bound to any object", async () => { + const result = await getJsonResponse(); + expect(result).toEqual([ + expect.any(String), + expect.any(String), + expect.any(String), + expect.any(String), + ]); +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/package.json b/packages/vite-plugin-cloudflare/playground/node-compat/package.json new file mode 100644 index 0000000000000..9e6d0a39dd5f8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/package.json @@ -0,0 +1,47 @@ +{ + "name": "@playground/integration-node-compat", + "private": true, + "description": "", + "type": "module", + "scripts": { + "basic:build": "vite build --app -c vite.config.worker-basic.ts", + "basic:dev": "vite dev -c vite.config.worker-basic.ts", + "basic:preview": "vite preview -c vite.config.worker-basic.ts", + "basic:test": "vitest run -c ../vitest.config.e2e.ts worker-basic", + "build": "pnpm run basic:build && pnpm run cross-env:build && pnpm run crypto:build && pnpm run postgres:build && pnpm run process:build && pnpm run random:build", + "check:types": "tsc --build", + "cross-env:build": "vite build --app -c vite.config.worker-cross-env.ts", + "cross-env:dev": "vite dev -c vite.config.worker-cross-env.ts", + "cross-env:preview": "vite preview -c vite.config.worker-cross-env.ts", + "cross-env:test": "vitest run -c ../vitest.config.e2e.ts worker-cross-env", + "crypto:build": "vite build --app -c vite.config.worker-crypto.ts", + "crypto:dev": "vite dev -c vite.config.worker-crypto.ts", + "crypto:preview": "vite preview -c vite.config.worker-crypto.ts", + "crypto:test": "vitest run -c ../vitest.config.e2e.ts worker-crypto", + "postgres:build": "vite build --app -c vite.config.worker-postgres.ts", + "postgres:dev": "vite dev -c vite.config.worker-postgres.ts", + "postgres:preview": "vite preview -c vite.config.worker-postgres.ts", + "postgres:test": "vitest run -c ../vitest.config.e2e.ts worker-postgres", + "process:build": "vite build --app -c vite.config.worker-process.ts", + "process:dev": "vite dev -c vite.config.worker-process.ts", + "process:preview": "vite preview -c vite.config.worker-process.ts", + "process:test": "vitest run -c ../vitest.config.e2e.ts worker-process", + "random:build": "vite build --app -c vite.config.worker-random.ts", + "random:dev": "vite dev -c vite.config.worker-random.ts", + "random:preview": "vite preview -c vite.config.worker-random.ts", + "random:test": "vitest run -c ../vitest.config.e2e.ts worker-random" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "@types/node": "catalog:vite-plugin", + "@types/pg": "^8.11.2", + "cross-fetch": "^4.0.0", + "pg": "^8.13.0", + "pg-cloudflare": "^1.1.1", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.json b/packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.node.json new file mode 100644 index 0000000000000..9e09612c840c3 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.*.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.worker.json new file mode 100644 index 0000000000000..9e8ccfabb3f39 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/tsconfig.worker.json @@ -0,0 +1,11 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": [ + "worker-basic", + "worker-cross-env", + "worker-crypto", + "worker-postgres", + "worker-process", + "worker-random" + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/turbo.json b/packages/vite-plugin-cloudflare/playground/node-compat/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-basic.ts b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-basic.ts new file mode 100644 index 0000000000000..be6174b78f496 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-basic.ts @@ -0,0 +1,14 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "dist/worker-basic", + }, + plugins: [ + cloudflare({ + configPath: "./worker-basic/wrangler.toml", + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-cross-env.ts b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-cross-env.ts new file mode 100644 index 0000000000000..fb701adaa3079 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-cross-env.ts @@ -0,0 +1,14 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "dist/worker-cross-env", + }, + plugins: [ + cloudflare({ + configPath: "./worker-cross-env/wrangler.toml", + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-crypto.ts b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-crypto.ts new file mode 100644 index 0000000000000..4878cd48c6038 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-crypto.ts @@ -0,0 +1,14 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "dist/worker-crypto", + }, + plugins: [ + cloudflare({ + configPath: "./worker-crypto/wrangler.toml", + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-postgres.ts b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-postgres.ts new file mode 100644 index 0000000000000..749aecce701f1 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-postgres.ts @@ -0,0 +1,14 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "dist/worker-postgres", + }, + plugins: [ + cloudflare({ + configPath: "./worker-postgres/wrangler.toml", + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-process.ts b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-process.ts new file mode 100644 index 0000000000000..030f971ae7cc9 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-process.ts @@ -0,0 +1,14 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "dist/worker-process", + }, + plugins: [ + cloudflare({ + configPath: "./worker-process/wrangler.toml", + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-random.ts b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-random.ts new file mode 100644 index 0000000000000..b768b4e9597fe --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/vite.config.worker-random.ts @@ -0,0 +1,14 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "dist/worker-random", + }, + plugins: [ + cloudflare({ + configPath: "./worker-random/wrangler.toml", + persistState: false, + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/global.d.ts b/packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/global.d.ts new file mode 100644 index 0000000000000..657cb842657c2 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/global.d.ts @@ -0,0 +1,2 @@ +declare var performance: Performance; +declare var Performance: typeof Performance; diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/index.ts b/packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/index.ts new file mode 100644 index 0000000000000..c011a07481496 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/index.ts @@ -0,0 +1,41 @@ +import assert from "node:assert/strict"; +import { join } from "path"; +// Check that we can actually import unenv polyfilled modules in user source. +import "perf_hooks"; + +export default { + async fetch() { + return testBasicNodejsProperties(); + }, +} satisfies ExportedHandler; + +function testBasicNodejsProperties() { + assert(true, "the world is broken"); + + assert(join("a", "b") === "a/b", "expected posix path joining"); + + const buffer1 = Buffer.of(1); + assert(buffer1.toJSON().data[0] === 1, "Buffer is broken"); + + const buffer2 = global.Buffer.of(1); + assert(buffer2.toJSON().data[0] === 1, "global.Buffer is broken"); + + const buffer3 = globalThis.Buffer.of(1); + assert(buffer3.toJSON().data[0] === 1, "globalThis.Buffer is broken"); + + assert(performance !== undefined, "performance is missing"); + assert(global.performance !== undefined, "global.performance is missing"); + assert( + globalThis.performance !== undefined, + "globalThis.performance is missing" + ); + + assert(Performance !== undefined, "Performance is missing"); + assert(global.Performance !== undefined, "global.Performance is missing"); + assert( + globalThis.Performance !== undefined, + "globalThis.Performance is missing" + ); + + return new Response(`"OK!"`); +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/wrangler.toml b/packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/wrangler.toml new file mode 100644 index 0000000000000..cbd15cf20387c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-basic/wrangler.toml @@ -0,0 +1,4 @@ +name = "worker" +main = "./index.ts" +compatibility_date = "2024-12-30" +compatibility_flags = ["nodejs_compat"] diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-cross-env/index.ts b/packages/vite-plugin-cloudflare/playground/node-compat/worker-cross-env/index.ts new file mode 100644 index 0000000000000..ae2dd00f0834e --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-cross-env/index.ts @@ -0,0 +1,31 @@ +import crossFetch, { Headers } from "cross-fetch"; + +export default { + async fetch() { + return testImportUenvAliasedPackages(); + }, +} satisfies ExportedHandler; + +async function testImportUenvAliasedPackages() { + const errors = []; + if (typeof crossFetch !== "function") { + errors.push( + "Expected `fetch` to be a function (default export) but got " + + typeof crossFetch + ); + } + + if (typeof Headers !== "function") { + errors.push( + "Expected `Headers` to be a function (named export) but got " + + typeof Headers + ); + } + + if (errors.length > 0) { + return new Response( + "NOT OK:\n" + errors.length + errors.join("\n") + "..." + ); + } + return new Response(`"OK!"`); +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-cross-env/wrangler.toml b/packages/vite-plugin-cloudflare/playground/node-compat/worker-cross-env/wrangler.toml new file mode 100644 index 0000000000000..cbd15cf20387c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-cross-env/wrangler.toml @@ -0,0 +1,4 @@ +name = "worker" +main = "./index.ts" +compatibility_date = "2024-12-30" +compatibility_flags = ["nodejs_compat"] diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-crypto/index.ts b/packages/vite-plugin-cloudflare/playground/node-compat/worker-crypto/index.ts new file mode 100644 index 0000000000000..cfc2d2718b4a7 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-crypto/index.ts @@ -0,0 +1,30 @@ +import nodeCrypto from "crypto"; + +export default { + async fetch() { + return testX509Certificate(); + }, +} satisfies ExportedHandler; + +function testX509Certificate() { + try { + const cert = new nodeCrypto.X509Certificate(`-----BEGIN CERTIFICATE----- +MIICZjCCAc+gAwIBAgIUOsv8Y+x40C+gdNuu40N50KpGUhEwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA5MjAwOTA4MTNaFw0yNTA5 +MjAwOTA4MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEB +BQADgY0AMIGJAoGBALpJn3dUrNmZhZV02RbjZKTd5j3hpgTncF4lG4Y3sQA18k0l +7pt6xpZuXYSFH7v2zTAxYy+uYyYwX2NZur48dZc76FSzIeuQdoTCkT0NacwFRTR5 +fEEqPvvB85ozYuyk8Bl3vSsonivOH3WftEDp9mjkHROQzS4wAZbIj7Cp+is/AgMB +AAGjUzBRMB0GA1UdDgQWBBSzFJSiPAw2tJOg8oUXrFBdqWI6zDAfBgNVHSMEGDAW +gBSzFJSiPAw2tJOg8oUXrFBdqWI6zDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4GBACbto0+Ds40F7faRFFMwg5nPyh7gsiX+ZK3FYcrO3oxh5ejfzwow +DKOOje4Ncaw0rIkVpxacPyjg+wANuK2Nv/Z4CVAD3mneE4gwgRdn38q8IYN9AtSv +GzEf4UxiLBbUB6WRBgyVyquGfUMlKl/tnm4q0yeYQloYKSoHpGeHVJuN +-----END CERTIFICATE-----`); + return new Response(`"OK!": ` + cert.toString()); + } catch { + return new Response(`"KO!"`); + } +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-crypto/wrangler.toml b/packages/vite-plugin-cloudflare/playground/node-compat/worker-crypto/wrangler.toml new file mode 100644 index 0000000000000..cbd15cf20387c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-crypto/wrangler.toml @@ -0,0 +1,4 @@ +name = "worker" +main = "./index.ts" +compatibility_date = "2024-12-30" +compatibility_flags = ["nodejs_compat"] diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-postgres/index.ts b/packages/vite-plugin-cloudflare/playground/node-compat/worker-postgres/index.ts new file mode 100644 index 0000000000000..3f283a85ff2b7 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-postgres/index.ts @@ -0,0 +1,39 @@ +import { Client } from "pg"; + +interface Env { + DB_HOSTNAME: string; + DB_PORT: string; + DB_NAME: string; + DB_USERNAME: string; + DB_PASSWORD: string; +} + +export default { + async fetch(request, env, ctx) { + const client = new Client({ + user: env.DB_USERNAME, + password: env.DB_PASSWORD, + host: env.DB_HOSTNAME, + port: Number(env.DB_PORT), + database: env.DB_NAME, + }); + + const url = new URL(request.url); + if (url.pathname == "/send-query") { + return testPostgresLibrary(client, ctx); + } else { + return new Response(client.host); + } + }, +} satisfies ExportedHandler; + +async function testPostgresLibrary(client: Client, ctx: ExecutionContext) { + await client.connect(); + const result = await client.query(`SELECT * FROM rnc_database`); + // Return the first row as JSON + const resp = Response.json(result.rows[0]); + + // Clean up the client + ctx.waitUntil(client.end()); + return resp; +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-postgres/wrangler.toml b/packages/vite-plugin-cloudflare/playground/node-compat/worker-postgres/wrangler.toml new file mode 100644 index 0000000000000..0cfe231f83a11 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-postgres/wrangler.toml @@ -0,0 +1,14 @@ +name = "worker" +main = "./index.ts" +compatibility_date = "2024-12-30" +compatibility_flags = ["nodejs_compat"] + +[vars] +# These DB connection values are to a public database containing information about +# RNA, genes, etc. See https://rnacentral.org/help/public-database. +# In case GitGuardian complains in future PRs, this is public information so a false positive. +DB_HOSTNAME = "hh-pgsql-public.ebi.ac.uk" +DB_PORT = "5432" +DB_NAME = "pfmegrnargs" +DB_USERNAME = "reader" +DB_PASSWORD = "NWDMCE5xdipIjRrp" diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-process/index.ts b/packages/vite-plugin-cloudflare/playground/node-compat/worker-process/index.ts new file mode 100644 index 0000000000000..c071272dde95e --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-process/index.ts @@ -0,0 +1,86 @@ +import assert from "node:assert"; + +export default { + async fetch() { + return testProcessBehaviour(); + }, +} satisfies ExportedHandler; + +function testProcessBehaviour() { + const originalProcess = process; + try { + assert(process !== undefined, "process is missing"); + assert(globalThis.process !== undefined, "globalThis.process is missing"); + assert(global.process !== undefined, "global.process is missing"); + assert( + process === global.process, + "process is not the same as global.process" + ); + assert( + global.process === globalThis.process, + "global.process is not the same as globalThis.process" + ); + assert( + globalThis.process === process, + "globalThis.process is not the same as process" + ); + + const fakeProcess1 = {} as typeof process; + process = fakeProcess1; + assert(process === fakeProcess1, "process is not updated to fakeProcess"); + assert( + global.process === fakeProcess1, + "global.process is not updated to fakeProcess" + ); + assert( + globalThis.process === fakeProcess1, + "globalThis.process is not updated to fakeProcess" + ); + + const fakeProcess2 = {} as typeof process; + global.process = fakeProcess2; + assert(process === fakeProcess2, "process is not updated to fakeProcess"); + assert( + global.process === fakeProcess2, + "global.process is not updated to fakeProcess" + ); + assert( + globalThis.process === fakeProcess2, + "globalThis.process is not updated to fakeProcess" + ); + + const fakeProcess3 = {} as typeof process; + globalThis.process = fakeProcess3; + assert(process === fakeProcess3, "process is not updated to fakeProcess"); + assert( + global.process === fakeProcess3, + "global.process is not updated to fakeProcess" + ); + assert( + globalThis.process === fakeProcess3, + "globalThis.process is not updated to fakeProcess" + ); + + const fakeProcess4 = {} as typeof process; + globalThis["process"] = fakeProcess4; + assert(process === fakeProcess4, "process is not updated to fakeProcess"); + assert( + global.process === fakeProcess4, + "global.process is not updated to fakeProcess" + ); + assert( + globalThis.process === fakeProcess4, + "globalThis.process is not updated to fakeProcess" + ); + } catch (e) { + if (e instanceof Error) { + return new Response(`${e.stack}`, { status: 500 }); + } else { + throw e; + } + } finally { + process = originalProcess; + } + + return new Response("OK!"); +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-process/wrangler.toml b/packages/vite-plugin-cloudflare/playground/node-compat/worker-process/wrangler.toml new file mode 100644 index 0000000000000..cbd15cf20387c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-process/wrangler.toml @@ -0,0 +1,4 @@ +name = "worker" +main = "./index.ts" +compatibility_date = "2024-12-30" +compatibility_flags = ["nodejs_compat"] diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-random/global.d.ts b/packages/vite-plugin-cloudflare/playground/node-compat/worker-random/global.d.ts new file mode 100644 index 0000000000000..657cb842657c2 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-random/global.d.ts @@ -0,0 +1,2 @@ +declare var performance: Performance; +declare var Performance: typeof Performance; diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-random/index.ts b/packages/vite-plugin-cloudflare/playground/node-compat/worker-random/index.ts new file mode 100644 index 0000000000000..ba18e6f797555 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-random/index.ts @@ -0,0 +1,25 @@ +import nodeCrypto, { getRandomValues, webcrypto } from "crypto"; +import assert from "node:assert"; + +export default { + async fetch() { + return testGetRandomValues(); + }, +} satisfies ExportedHandler; + +function testGetRandomValues() { + assert( + webcrypto.getRandomValues === getRandomValues, + "Unexpected identity for webcrypto.getRandomValues" + ); + assert( + nodeCrypto.getRandomValues === getRandomValues, + "Unexpected identity for nodeCrypto.getRandomValues" + ); + return Response.json([ + crypto.getRandomValues(new Uint8Array(6)).toString(), // global + webcrypto.getRandomValues(new Uint8Array(6)).toString(), // webcrypto + nodeCrypto.getRandomValues(new Uint8Array(6)).toString(), // namespace import + getRandomValues(new Uint8Array(6)).toString(), // named import + ]); +} diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/worker-random/wrangler.toml b/packages/vite-plugin-cloudflare/playground/node-compat/worker-random/wrangler.toml new file mode 100644 index 0000000000000..0cfe231f83a11 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/node-compat/worker-random/wrangler.toml @@ -0,0 +1,14 @@ +name = "worker" +main = "./index.ts" +compatibility_date = "2024-12-30" +compatibility_flags = ["nodejs_compat"] + +[vars] +# These DB connection values are to a public database containing information about +# RNA, genes, etc. See https://rnacentral.org/help/public-database. +# In case GitGuardian complains in future PRs, this is public information so a false positive. +DB_HOSTNAME = "hh-pgsql-public.ebi.ac.uk" +DB_PORT = "5432" +DB_NAME = "pfmegrnargs" +DB_USERNAME = "reader" +DB_PASSWORD = "NWDMCE5xdipIjRrp" diff --git a/packages/vite-plugin-cloudflare/playground/package.json b/packages/vite-plugin-cloudflare/playground/package.json new file mode 100644 index 0000000000000..c651b7c51fe6a --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/package.json @@ -0,0 +1,19 @@ +{ + "name": "@vite-plugin-cloudflare/playground", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "check:types": "tsc --build", + "test:build": "cross-env VITE_TEST_BUILD=1 vitest run -c vitest.config.e2e.ts", + "pretest:ci": "pnpm exec playwright install-deps && pnpm exec playwright install", + "test:ci": "pnpm test:serve && pnpm test:build", + "test:serve": "vitest run -c vitest.config.e2e.ts" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "playwright-chromium": "^1.48.1", + "typescript": "catalog:vite-plugin" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/__tests__/assets.spec.ts b/packages/vite-plugin-cloudflare/playground/react-spa/__tests__/assets.spec.ts new file mode 100644 index 0000000000000..91a60c3d63848 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/__tests__/assets.spec.ts @@ -0,0 +1,22 @@ +import { expect, test } from "vitest"; +import { page, viteTestUrl } from "../../__test-utils__"; + +test("returns the correct home page", async () => { + const content = await page.textContent("h1"); + expect(content).toBe("Vite + React"); +}); + +test("allows updating state", async () => { + const button = page.getByRole("button", { name: "increment" }); + const contentBefore = await button.innerText(); + expect(contentBefore).toBe("count is 0"); + await button.click(); + const contentAfter = await button.innerText(); + expect(contentAfter).toBe("count is 1"); +}); + +test("returns the home page for not found routes", async () => { + await page.goto(`${viteTestUrl}/random-page`); + const content = await page.textContent("h1"); + expect(content).toBe("Vite + React"); +}); diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/index.html b/packages/vite-plugin-cloudflare/playground/react-spa/index.html new file mode 100644 index 0000000000000..ef80c79b33a59 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/package.json b/packages/vite-plugin-cloudflare/playground/react-spa/package.json new file mode 100644 index 0000000000000..9c2dc74b6520e --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/package.json @@ -0,0 +1,26 @@ +{ + "name": "@playground/react-spa", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/public/vite.svg b/packages/vite-plugin-cloudflare/playground/react-spa/public/vite.svg new file mode 100644 index 0000000000000..e7b8dfb1b2a60 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/src/App.css b/packages/vite-plugin-cloudflare/playground/react-spa/src/App.css new file mode 100644 index 0000000000000..df674c0d89587 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/src/App.tsx b/packages/vite-plugin-cloudflare/playground/react-spa/src/App.tsx new file mode 100644 index 0000000000000..7c51938085417 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/src/App.tsx @@ -0,0 +1,38 @@ +import viteLogo from "/vite.svg"; +import { useState } from "react"; +import reactLogo from "./assets/react.svg"; +import "./App.css"; + +function App() { + const [count, setCount] = useState(0); + + return ( + <> + +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+ + ); +} + +export default App; diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/src/assets/react.svg b/packages/vite-plugin-cloudflare/playground/react-spa/src/assets/react.svg new file mode 100644 index 0000000000000..6c87de9bb3358 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/src/index.css b/packages/vite-plugin-cloudflare/playground/react-spa/src/index.css new file mode 100644 index 0000000000000..9cfcb00f249ad --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/src/main.tsx b/packages/vite-plugin-cloudflare/playground/react-spa/src/main.tsx new file mode 100644 index 0000000000000..b310b355463c3 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import App from "./App.tsx"; + +createRoot(document.getElementById("root")!).render( + + + +); diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/src/vite-env.d.ts b/packages/vite-plugin-cloudflare/playground/react-spa/src/vite-env.d.ts new file mode 100644 index 0000000000000..11f02fe2a0061 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.client.json b/packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.client.json new file mode 100644 index 0000000000000..719bc3b2504bf --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.client.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/react.json"], + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.json b/packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.json new file mode 100644 index 0000000000000..a83bc9d6d2f8a --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.client.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/turbo.json b/packages/vite-plugin-cloudflare/playground/react-spa/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/vite.config.ts b/packages/vite-plugin-cloudflare/playground/react-spa/vite.config.ts new file mode 100644 index 0000000000000..1e498add4f511 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/vite.config.ts @@ -0,0 +1,7 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [react(), cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/wrangler.toml b/packages/vite-plugin-cloudflare/playground/react-spa/wrangler.toml new file mode 100644 index 0000000000000..9f66d48c747a9 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/react-spa/wrangler.toml @@ -0,0 +1,3 @@ +name = "react-spa" +compatibility_date = "2024-12-30" +assets = { not_found_handling = "single-page-application" } diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/.gitignore b/packages/vite-plugin-cloudflare/playground/spa-with-api/.gitignore new file mode 100644 index 0000000000000..a4c84af48423a --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/.gitignore @@ -0,0 +1,2 @@ +custom-root-output-directory +custom-client-output-directory diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/__tests__/custom-output-directories/spa-with-api.spec.ts b/packages/vite-plugin-cloudflare/playground/spa-with-api/__tests__/custom-output-directories/spa-with-api.spec.ts new file mode 100644 index 0000000000000..7db4cb5e340bb --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/__tests__/custom-output-directories/spa-with-api.spec.ts @@ -0,0 +1,33 @@ +import * as fs from "node:fs"; +import * as path from "node:path"; +import { describe, expect, test } from "vitest"; +import { isBuild, page, rootDir } from "../../../__test-utils__"; + +describe.runIf(isBuild)("output directories", () => { + test("creates the correct output directories", () => { + expect( + fs.existsSync(path.join(rootDir, "custom-root-output-directory", "api")) + ).toBe(true); + expect( + fs.existsSync(path.join(rootDir, "custom-client-output-directory")) + ).toBe(true); + }); +}); + +test("returns the correct home page", async () => { + const content = await page.textContent("h1"); + expect(content).toBe("Vite + React"); +}); + +test("returns the response from the API", async () => { + const button = page.getByRole("button", { name: "get-name" }); + const contentBefore = await button.innerText(); + expect(contentBefore).toBe("Name from API is: unknown"); + const responsePromise = page.waitForResponse((response) => + response.url().endsWith("/api/") + ); + await button.click(); + await responsePromise; + const contentAfter = await button.innerText(); + expect(contentAfter).toBe("Name from API is: Cloudflare"); +}); diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/__tests__/spa-with-api.spec.ts b/packages/vite-plugin-cloudflare/playground/spa-with-api/__tests__/spa-with-api.spec.ts new file mode 100644 index 0000000000000..0e19884e1a1b4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/__tests__/spa-with-api.spec.ts @@ -0,0 +1,20 @@ +import { expect, test } from "vitest"; +import { page } from "../../__test-utils__"; + +test("returns the correct home page", async () => { + const content = await page.textContent("h1"); + expect(content).toBe("Vite + React"); +}); + +test("returns the response from the API", async () => { + const button = page.getByRole("button", { name: "get-name" }); + const contentBefore = await button.innerText(); + expect(contentBefore).toBe("Name from API is: unknown"); + const responsePromise = page.waitForResponse((response) => + response.url().endsWith("/api/") + ); + await button.click(); + await responsePromise; + const contentAfter = await button.innerText(); + expect(contentAfter).toBe("Name from API is: Cloudflare"); +}); diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/api/index.ts b/packages/vite-plugin-cloudflare/playground/spa-with-api/api/index.ts new file mode 100644 index 0000000000000..b134cbf32cc04 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/api/index.ts @@ -0,0 +1,17 @@ +interface Env { + ASSETS: Fetcher; +} + +export default { + fetch(request, env) { + const url = new URL(request.url); + + if (url.pathname.startsWith("/api/")) { + return Response.json({ + name: "Cloudflare", + }); + } + + return env.ASSETS.fetch(request); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/index.html b/packages/vite-plugin-cloudflare/playground/spa-with-api/index.html new file mode 100644 index 0000000000000..ef80c79b33a59 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/package.json b/packages/vite-plugin-cloudflare/playground/spa-with-api/package.json new file mode 100644 index 0000000000000..2341ec95b17cb --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/package.json @@ -0,0 +1,28 @@ +{ + "name": "@playground/spa-with-api", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "build:custom-output-directories": "vite build --app -c ./vite.config.custom-output-directories.ts", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview", + "preview:custom-output-directories": "vite preview -c ./vite.config.custom-output-directories.ts" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.4", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/public/vite.svg b/packages/vite-plugin-cloudflare/playground/spa-with-api/public/vite.svg new file mode 100644 index 0000000000000..e7b8dfb1b2a60 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/src/App.css b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/App.css new file mode 100644 index 0000000000000..df674c0d89587 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/src/App.tsx b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/App.tsx new file mode 100644 index 0000000000000..1fa5abd000158 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/App.tsx @@ -0,0 +1,54 @@ +import viteLogo from "/vite.svg"; +import { useState } from "react"; +import reactLogo from "./assets/react.svg"; +import "./App.css"; + +function App() { + const [count, setCount] = useState(0); + const [name, setName] = useState("unknown"); + + return ( + <> + +

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+
+ +

+ Edit api/index.ts to change the name +

+
+

+ Click on the Vite and React logos to learn more +

+ + ); +} + +export default App; diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/src/assets/react.svg b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/assets/react.svg new file mode 100644 index 0000000000000..6c87de9bb3358 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/src/index.css b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/index.css new file mode 100644 index 0000000000000..9cfcb00f249ad --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/src/main.tsx b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/main.tsx new file mode 100644 index 0000000000000..b310b355463c3 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import App from "./App.tsx"; + +createRoot(document.getElementById("root")!).render( + + + +); diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/src/vite-env.d.ts b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/vite-env.d.ts new file mode 100644 index 0000000000000..11f02fe2a0061 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.client.json b/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.client.json new file mode 100644 index 0000000000000..719bc3b2504bf --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.client.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/react.json"], + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.json b/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.json new file mode 100644 index 0000000000000..f4ac20b7cbcf1 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.json @@ -0,0 +1,8 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.client.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.worker.json new file mode 100644 index 0000000000000..5dfc94c35640e --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["api"] +} diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/turbo.json b/packages/vite-plugin-cloudflare/playground/spa-with-api/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/vite.config.custom-output-directories.ts b/packages/vite-plugin-cloudflare/playground/spa-with-api/vite.config.custom-output-directories.ts new file mode 100644 index 0000000000000..4a2685c4016fd --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/vite.config.custom-output-directories.ts @@ -0,0 +1,17 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "custom-root-output-directory", + }, + environments: { + client: { + build: { + outDir: "custom-client-output-directory", + }, + }, + }, + plugins: [react(), cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/vite.config.ts b/packages/vite-plugin-cloudflare/playground/spa-with-api/vite.config.ts new file mode 100644 index 0000000000000..1e498add4f511 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/vite.config.ts @@ -0,0 +1,7 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [react(), cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/wrangler.toml b/packages/vite-plugin-cloudflare/playground/spa-with-api/wrangler.toml new file mode 100644 index 0000000000000..20bff697d6459 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/wrangler.toml @@ -0,0 +1,4 @@ +name = "api" +main = "./api/index.ts" +compatibility_date = "2024-12-30" +assets = { not_found_handling = "single-page-application", binding = "ASSETS" } diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/404.html b/packages/vite-plugin-cloudflare/playground/static-mpa/404.html new file mode 100644 index 0000000000000..e7fe9d0740215 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/404.html @@ -0,0 +1,12 @@ + + + + + + + Static MPA - Root 404 + + +

Root 404

+ + diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/__tests__/assets.spec.ts b/packages/vite-plugin-cloudflare/playground/static-mpa/__tests__/assets.spec.ts new file mode 100644 index 0000000000000..230fad4ad2f2c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/__tests__/assets.spec.ts @@ -0,0 +1,41 @@ +import { expect, test } from "vitest"; +import { page, serverLogs, viteTestUrl } from "../../__test-utils__"; + +test("returns the correct home page", async () => { + const content = await page.textContent("h1"); + expect(content).toBe("Home"); +}); + +test("returns the correct contact page", async () => { + await page.goto(`${viteTestUrl}/contact`); + const content = await page.textContent("h1"); + expect(content).toBe("Contact"); +}); + +test("returns the correct about page", async () => { + await page.goto(`${viteTestUrl}/about`); + const content = await page.textContent("h1"); + expect(content).toBe("About"); +}); + +test("returns the correct canonical URL", async () => { + await page.goto(`${viteTestUrl}/about`); + const url = page.url(); + expect(url).toBe(`${viteTestUrl}/about/`); +}); + +test("returns the correct root 404 page", async () => { + await page.goto(`${viteTestUrl}/random-page`); + const content = await page.textContent("h1"); + expect(content).toBe("Root 404"); +}); + +test("returns the correct nested 404 page", async () => { + await page.goto(`${viteTestUrl}/about/random-page`); + const content = await page.textContent("h1"); + expect(content).toBe("About 404"); +}); + +test("worker configs warnings are not present in the terminal", async () => { + expect(serverLogs.warns).toEqual([]); +}); diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/__tests__/with-worker-configs-warning/assets.spec.ts b/packages/vite-plugin-cloudflare/playground/static-mpa/__tests__/with-worker-configs-warning/assets.spec.ts new file mode 100644 index 0000000000000..acad8379bf4ed --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/__tests__/with-worker-configs-warning/assets.spec.ts @@ -0,0 +1,15 @@ +import { expect, test } from "vitest"; +import { serverLogs } from "../../../__test-utils__"; + +test("a worker configs warning is present in the terminal", async () => { + /** + * Note: we always expect the warning once for both values of `isBuild`. + * For dev is obvious, for builds we do get the warning once because we get it when we + * build the application but not when we run its preview (since that reads the generated wrangler.json) + */ + expect(serverLogs.warns).toEqual([ + expect.stringMatching( + /your worker config \(at `.*?wrangler.with-warning.toml`\) contains the following configuration options which are ignored since they are not applicable when using Vite:[\s\S]+preserve_file_names/ + ), + ]); +}); diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/about/404.html b/packages/vite-plugin-cloudflare/playground/static-mpa/about/404.html new file mode 100644 index 0000000000000..ec191335918df --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/about/404.html @@ -0,0 +1,12 @@ + + + + + + + Static MPA - About 404 + + +

About 404

+ + diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/about/index.html b/packages/vite-plugin-cloudflare/playground/static-mpa/about/index.html new file mode 100644 index 0000000000000..0187212020104 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/about/index.html @@ -0,0 +1,12 @@ + + + + + + + Static MPA - About + + +

About

+ + diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/contact.html b/packages/vite-plugin-cloudflare/playground/static-mpa/contact.html new file mode 100644 index 0000000000000..03b632eb3886f --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/contact.html @@ -0,0 +1,12 @@ + + + + + + + Static MPA - Contact + + +

Contact

+ + diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/index.html b/packages/vite-plugin-cloudflare/playground/static-mpa/index.html new file mode 100644 index 0000000000000..ea21bb77ab93f --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/index.html @@ -0,0 +1,12 @@ + + + + + + + Static MPA - Home + + +

Home

+ + diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/package.json b/packages/vite-plugin-cloudflare/playground/static-mpa/package.json new file mode 100644 index 0000000000000..adc85541cb249 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/package.json @@ -0,0 +1,22 @@ +{ + "name": "@playground/static-mpa", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "build:with-worker-configs-warning": "vite build --app -c vite.config.with-worker-configs-warning.ts", + "check:types": "tsc --build", + "dev": "vite dev", + "dev:with-worker-configs-warning": "vite dev -c vite.config.with-worker-configs-warning.ts", + "preview": "vite preview", + "preview:with-worker-configs-warning": "vite preview -c vite.config.with-worker-configs-warning.ts" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/public/vite.svg b/packages/vite-plugin-cloudflare/playground/static-mpa/public/vite.svg new file mode 100644 index 0000000000000..e7b8dfb1b2a60 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/tsconfig.json b/packages/vite-plugin-cloudflare/playground/static-mpa/tsconfig.json new file mode 100644 index 0000000000000..e0ed880f2c050 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/static-mpa/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/turbo.json b/packages/vite-plugin-cloudflare/playground/static-mpa/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/vite.config.ts b/packages/vite-plugin-cloudflare/playground/static-mpa/vite.config.ts new file mode 100644 index 0000000000000..ab80b6146ebae --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/vite.config.ts @@ -0,0 +1,22 @@ +import * as path from "node:path"; +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + environments: { + client: { + build: { + rollupOptions: { + input: { + main: path.resolve(__dirname, "index.html"), + contact: path.resolve(__dirname, "contact.html"), + "404": path.resolve(__dirname, "404.html"), + about: path.resolve(__dirname, "about/index.html"), + "about-404": path.resolve(__dirname, "about/404.html"), + }, + }, + }, + }, + }, + plugins: [cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/vite.config.with-worker-configs-warning.ts b/packages/vite-plugin-cloudflare/playground/static-mpa/vite.config.with-worker-configs-warning.ts new file mode 100644 index 0000000000000..78088401603a7 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/vite.config.with-worker-configs-warning.ts @@ -0,0 +1,27 @@ +import * as path from "node:path"; +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + environments: { + client: { + build: { + rollupOptions: { + input: { + main: path.resolve(__dirname, "index.html"), + contact: path.resolve(__dirname, "contact.html"), + "404": path.resolve(__dirname, "404.html"), + about: path.resolve(__dirname, "about/index.html"), + "about-404": path.resolve(__dirname, "about/404.html"), + }, + }, + }, + }, + }, + plugins: [ + cloudflare({ + persistState: false, + configPath: "./wrangler.with-warning.toml", + }), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/wrangler.toml b/packages/vite-plugin-cloudflare/playground/static-mpa/wrangler.toml new file mode 100644 index 0000000000000..998fcaee3a49c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/wrangler.toml @@ -0,0 +1,3 @@ +name = "static-mpa" +compatibility_date = "2024-12-30" +assets = { html_handling = "auto-trailing-slash", not_found_handling = "404-page" } diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/wrangler.with-warning.toml b/packages/vite-plugin-cloudflare/playground/static-mpa/wrangler.with-warning.toml new file mode 100644 index 0000000000000..315e16ddeba4a --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/wrangler.with-warning.toml @@ -0,0 +1,5 @@ +name = "static-mpa" +compatibility_date = "2024-12-30" +assets = { html_handling = "auto-trailing-slash", not_found_handling = "404-page" } + +preserve_file_names = true diff --git a/packages/vite-plugin-cloudflare/playground/tsconfig.json b/packages/vite-plugin-cloudflare/playground/tsconfig.json new file mode 100644 index 0000000000000..bcd29a6078114 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@cloudflare/workers-tsconfig/base.json", + "include": ["vitest-global-setup.ts", "vitest-setup.ts", "__test-utils__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/__tests__/basic-functionality.spec.ts b/packages/vite-plugin-cloudflare/playground/virtual-modules/__tests__/basic-functionality.spec.ts new file mode 100644 index 0000000000000..a5f9584fc80f9 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/__tests__/basic-functionality.spec.ts @@ -0,0 +1,6 @@ +import { expect, test } from "vitest"; +import { getTextResponse } from "../../__test-utils__"; + +test("returns value from virtual module", async () => { + expect(await getTextResponse()).toEqual("virtual module"); +}); diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/package.json b/packages/vite-plugin-cloudflare/playground/virtual-modules/package.json new file mode 100644 index 0000000000000..502af7ac8ab1a --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/package.json @@ -0,0 +1,19 @@ +{ + "name": "@playground/virtual-modules", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/src/index.ts b/packages/vite-plugin-cloudflare/playground/virtual-modules/src/index.ts new file mode 100644 index 0000000000000..6d71b8346c5db --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/src/index.ts @@ -0,0 +1,8 @@ +export default { + async fetch() { + // @ts-ignore + const virtualModule = await import("virtual:module"); + + return new Response(virtualModule.default); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.json b/packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.worker.json new file mode 100644 index 0000000000000..da43778b826f4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/turbo.json b/packages/vite-plugin-cloudflare/playground/virtual-modules/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/vite.config.ts b/packages/vite-plugin-cloudflare/playground/virtual-modules/vite.config.ts new file mode 100644 index 0000000000000..9700ee883f0a3 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/vite.config.ts @@ -0,0 +1,21 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [ + { + name: "virtual-module-plugin", + resolveId(id) { + if (id === "virtual:module") { + return `\0virtual:module`; + } + }, + load(id) { + if (id === "\0virtual:module") { + return `export default 'virtual module'`; + } + }, + }, + cloudflare(), + ], +}); diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/wrangler.toml b/packages/vite-plugin-cloudflare/playground/virtual-modules/wrangler.toml new file mode 100644 index 0000000000000..9a44ca3733ffb --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/wrangler.toml @@ -0,0 +1,3 @@ +name = "worker" +main = "./src/index.ts" +compatibility_date = "2024-12-30" diff --git a/packages/vite-plugin-cloudflare/playground/vitest-global-setup.ts b/packages/vite-plugin-cloudflare/playground/vitest-global-setup.ts new file mode 100644 index 0000000000000..1993b43eb268a --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/vitest-global-setup.ts @@ -0,0 +1,24 @@ +import { chromium } from "playwright-chromium"; +import type { BrowserServer } from "playwright-chromium"; +import type { GlobalSetupContext } from "vitest/node"; + +let browserServer: BrowserServer | undefined; + +export async function setup({ provide }: GlobalSetupContext): Promise { + process.env.NODE_ENV = process.env.VITE_TEST_BUILD + ? "production" + : "development"; + + browserServer = await chromium.launchServer({ + headless: !process.env.VITE_DEBUG_SERVE, + args: process.env.CI + ? ["--no-sandbox", "--disable-setuid-sandbox"] + : undefined, + }); + + provide("wsEndpoint", browserServer.wsEndpoint()); +} + +export async function teardown(): Promise { + await browserServer?.close(); +} diff --git a/packages/vite-plugin-cloudflare/playground/vitest-setup.ts b/packages/vite-plugin-cloudflare/playground/vitest-setup.ts new file mode 100644 index 0000000000000..f3ff68fb71839 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/vitest-setup.ts @@ -0,0 +1,373 @@ +import fs from "node:fs"; +import path from "node:path"; +import { chromium } from "playwright-chromium"; +import { + createBuilder, + createServer, + loadConfigFromFile, + mergeConfig, + preview, + Rollup, +} from "vite"; +import { beforeAll, beforeEach, inject } from "vitest"; +import type * as http from "node:http"; +import type { Browser, Page } from "playwright-chromium"; +import type { + ConfigEnv, + InlineConfig, + Logger, + PluginOption, + ResolvedConfig, + UserConfig, + ViteDevServer, +} from "vite"; +import type { RunnerTestFile } from "vitest"; + +export const workspaceRoot = path.resolve(__dirname, "../"); + +export const isBuild = !!process.env.VITE_TEST_BUILD; +export const isWindows = process.platform === "win32"; + +let server: ViteDevServer | http.Server; + +/** + * Vite Dev Server when testing serve + */ +export let viteServer: ViteDevServer; +/** + * Root of the Vite fixture + */ +export let rootDir: string; +/** + * Path to the current test file + */ +export let testPath: string; +/** + * Path to the test folder + */ +export let testDir: string; +/** + * Test folder name + */ +export let testName: string; + +export const serverLogs: { + info: string[]; + warns: string[]; + errors: string[]; +} = { + info: [], + warns: [], + errors: [], +}; +export const browserLogs: string[] = []; +export const browserErrors: Error[] = []; + +export let resolvedConfig: ResolvedConfig = undefined!; + +export let page: Page = undefined!; +export let browser: Browser = undefined!; +export let viteTestUrl: string = ""; +export let watcher: Rollup.RollupWatcher | undefined = undefined; + +export function setViteUrl(url: string): void { + viteTestUrl = url; +} + +export function resetServerLogs() { + serverLogs.info = []; + serverLogs.warns = []; + serverLogs.errors = []; +} + +beforeAll(async (s) => { + const suite = s as RunnerTestFile; + + testPath = suite.filepath!; + testName = slash(testPath).match(/playground\/([\w-]+)\//)?.[1]!; + testDir = path.dirname(testPath); + if (testName) { + testDir = path.resolve(workspaceRoot, "playground", testName); + } + + const wsEndpoint = inject("wsEndpoint"); + if (!wsEndpoint) { + throw new Error("wsEndpoint not found"); + } + + browser = await chromium.connect(wsEndpoint); + page = await browser.newPage(); + + const globalConsole = console; + const warn = globalConsole.warn; + globalConsole.warn = (msg: string, ...args: unknown[]) => { + if (msg.includes("Generated an empty chunk")) return; + warn.call(globalConsole, msg, ...args); + }; + + try { + page.on("console", (msg) => { + // ignore favicon requests in headed browser + if ( + process.env.VITE_DEBUG_SERVE && + msg.text().includes("Failed to load resource:") && + msg.location().url.includes("favicon.ico") + ) { + return; + } + browserLogs.push(msg.text()); + }); + page.on("pageerror", (error) => { + browserErrors.push(error); + }); + + // if this is a test placed under playground/xxx/__tests__ + // start a vite server in that directory. + if (testName) { + // when `root` dir is present, use it as vite's root + const testCustomRoot = path.resolve(testDir, "root"); + rootDir = fs.existsSync(testCustomRoot) ? testCustomRoot : testDir; + + // separate rootDir for variant + const variantName = path.basename(path.dirname(testPath)); + if (variantName !== "__tests__") { + const variantTestDir = testDir + "__" + variantName; + if (fs.existsSync(variantTestDir)) { + rootDir = testDir = variantTestDir; + } + } + + const testCustomServe = [ + path.resolve(path.dirname(testPath), "serve.ts"), + path.resolve(path.dirname(testPath), "serve.js"), + ].find((i) => fs.existsSync(i)); + + if (testCustomServe) { + // test has custom server configuration. + const mod = await import(testCustomServe); + const serve = mod.serve || mod.default?.serve; + const preServe = mod.preServe || mod.default?.preServe; + if (preServe) { + await preServe(); + } + if (serve) { + server = await serve(); + viteServer = mod.viteServer; + } + } else { + await startDefaultServe(); + } + } + } catch (e) { + // Closing the page since an error in the setup, for example a runtime error + // when building the playground should skip further tests. + // If the page remains open, a command like `await page.click(...)` produces + // a timeout with an exception that hides the real error in the console. + await page.close(); + await server?.close(); + throw e; + } + + return async () => { + resetServerLogs(); + + await page?.close(); + await server?.close(); + await watcher?.close(); + if (browser) { + await browser.close(); + } + }; +}); + +beforeEach(async () => { + await page.goto(viteTestUrl); +}); + +async function loadConfig(configEnv: ConfigEnv) { + let config: UserConfig | null = null; + let cacheDir = "node_modules/.vite"; + + // config file named by convention as the *.spec.ts folder + const variantName = path.basename(path.dirname(testPath)); + if (variantName !== "__tests__") { + cacheDir += "/" + variantName; + for (const extension of ["js", "ts", "mjs", "cjs", "mts", "cts"]) { + const configVariantPath = path.resolve( + rootDir, + `vite.config.${variantName}.${extension}` + ); + if (fs.existsSync(configVariantPath)) { + const res = await loadConfigFromFile(configEnv, configVariantPath); + if (res) { + config = res.config; + break; + } + } + } + } + // config file from test root dir + if (!config) { + const res = await loadConfigFromFile(configEnv, undefined, rootDir); + if (res) { + config = res.config; + } + } + + const options: InlineConfig = { + cacheDir, + root: rootDir, + logLevel: "silent", + configFile: false, + server: { + watch: { + // During tests we edit the files too fast and sometimes chokidar + // misses change events, so enforce polling for consistency + usePolling: true, + interval: 100, + }, + fs: { + strict: !isBuild, + }, + }, + build: { + // esbuild do not minify ES lib output since that would remove pure annotations and break tree-shaking + // skip transpilation during tests to make it faster + target: "esnext", + // tests are flaky when `emptyOutDir` is `true` + emptyOutDir: false, + }, + customLogger: createInMemoryLogger( + serverLogs.info, + serverLogs.warns, + serverLogs.errors + ), + }; + return mergeConfig(options, config || {}); +} + +export async function startDefaultServe(): Promise { + setupConsoleWarnCollector(serverLogs.warns); + + if (!isBuild) { + process.env.VITE_INLINE = "inline-serve"; + const config = await loadConfig({ command: "serve", mode: "development" }); + viteServer = server = await (await createServer(config)).listen(); + viteTestUrl = server!.resolvedUrls!.local[0]!; + if (server.config.base === "/") { + viteTestUrl = viteTestUrl.replace(/\/$/, ""); + } + await page.goto(viteTestUrl); + } else { + process.env.VITE_INLINE = "inline-build"; + // determine build watch + const resolvedPlugin: () => PluginOption = () => ({ + name: "vite-plugin-watcher", + configResolved(config) { + resolvedConfig = config; + }, + }); + const buildConfig = mergeConfig( + await loadConfig({ + command: "build", + mode: "production", + }), + { + plugins: [resolvedPlugin()], + } + ); + const builder = await createBuilder(buildConfig); + await builder.buildApp(); + + const previewConfig = await loadConfig({ + command: "serve", + mode: "development", + isPreview: true, + }); + const _nodeEnv = process.env.NODE_ENV; + // Make sure we are running from within the playground. + // Otherwise workerd will error with messages about not being allowed to escape the starting directory with `..`. + process.chdir(previewConfig.root); + const previewServer = await preview(previewConfig); + // prevent preview change NODE_ENV + process.env.NODE_ENV = _nodeEnv; + viteTestUrl = previewServer!.resolvedUrls!.local[0]!; + if (previewServer.config.base === "/") { + viteTestUrl = viteTestUrl.replace(/\/$/, ""); + } + await page.goto(viteTestUrl); + } +} + +/** + * Send the rebuild complete message in build watch + */ +export async function notifyRebuildComplete( + watcher: Rollup.RollupWatcher +): Promise { + let resolveFn: undefined | (() => void); + const callback = (event: Rollup.RollupWatcherEvent): void => { + if (event.code === "END") { + resolveFn?.(); + } + }; + watcher.on("event", callback); + await new Promise((resolve) => { + resolveFn = resolve; + }); + return watcher.off("event", callback); +} + +export function createInMemoryLogger( + info: string[], + warns: string[], + errors: string[] +): Logger { + const loggedErrors = new WeakSet(); + const warnedMessages = new Set(); + + const logger: Logger = { + hasWarned: false, + hasErrorLogged: (err) => loggedErrors.has(err), + clearScreen: () => {}, + info(msg) { + info.push(msg); + }, + warn(msg) { + warns.push(msg); + logger.hasWarned = true; + }, + warnOnce(msg) { + if (warnedMessages.has(msg)) return; + warns.push(msg); + logger.hasWarned = true; + warnedMessages.add(msg); + }, + error(msg, opts) { + errors.push(msg); + if (opts?.error) { + loggedErrors.add(opts.error); + } + }, + }; + + return logger; +} + +function setupConsoleWarnCollector(logs: string[]) { + const warn = console.warn; + console.warn = (...args: unknown[]) => { + logs.push(args.join(" ")); + return warn.call(console, ...args); + }; +} + +export function slash(p: string): string { + return p.replace(/\\/g, "/"); +} + +declare module "vitest" { + export interface ProvidedContext { + wsEndpoint: string; + } +} diff --git a/packages/vite-plugin-cloudflare/playground/vitest.config.e2e.ts b/packages/vite-plugin-cloudflare/playground/vitest.config.e2e.ts new file mode 100644 index 0000000000000..c1e9e0cd02794 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/vitest.config.e2e.ts @@ -0,0 +1,22 @@ +import { resolve } from "node:path"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + resolve: { + alias: { + "~utils": resolve(__dirname, "__test-utils__"), + }, + }, + test: { + // We run these tests in a single fork to avoid them running in parallel. + // Otherwise we occasionally get flakes where two tests are overwriting + // the same output files. + poolOptions: { forks: { singleFork: true } }, + include: ["./**/__tests__/**/*.spec.[tj]s"], + setupFiles: ["./vitest-setup.ts"], + globalSetup: ["./vitest-global-setup.ts"], + reporters: "dot", + onConsoleLog: () => false, + }, + publicDir: false, +}); diff --git a/packages/vite-plugin-cloudflare/playground/websockets/__tests__/websockets.spec.ts b/packages/vite-plugin-cloudflare/playground/websockets/__tests__/websockets.spec.ts new file mode 100644 index 0000000000000..4d023dfc772bc --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/__tests__/websockets.spec.ts @@ -0,0 +1,41 @@ +import { expect, test, vi } from "vitest"; +import { page } from "../../__test-utils__"; + +async function openWebSocket() { + const openButton = page.getByRole("button", { name: "Open WebSocket" }); + const statusTextBefore = await page.textContent("h2"); + expect(statusTextBefore).toBe("WebSocket closed"); + await openButton.click(); + await vi.waitFor(async () => { + const statusTextAfter = await page.textContent("h2"); + expect(statusTextAfter).toBe("WebSocket open"); + }); +} + +test("opens WebSocket connection", openWebSocket); + +test("closes WebSocket connection", async () => { + await openWebSocket(); + const closeButton = page.getByRole("button", { name: "Close WebSocket" }); + const statusTextBefore = await page.textContent("h2"); + expect(statusTextBefore).toBe("WebSocket open"); + await closeButton.click(); + await vi.waitFor(async () => { + const statusTextAfter = await page.textContent("h2"); + expect(statusTextAfter).toBe("WebSocket closed"); + }); +}); + +test("sends and receives WebSocket messages", async () => { + await openWebSocket(); + const sendButton = page.getByRole("button", { name: "Send message" }); + const messageTextBefore = await page.textContent("p"); + expect(messageTextBefore).toBe(""); + await sendButton.click(); + await vi.waitFor(async () => { + const messageTextAfter = await page.textContent("p"); + expect(messageTextAfter).toBe( + `Durable Object received client message: 'Client event'.` + ); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/websockets/package.json b/packages/vite-plugin-cloudflare/playground/websockets/package.json new file mode 100644 index 0000000000000..d5ae9c3339fb4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/package.json @@ -0,0 +1,19 @@ +{ + "name": "@playground/websockets", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/websockets/src/index.html b/packages/vite-plugin-cloudflare/playground/websockets/src/index.html new file mode 100644 index 0000000000000..45ab99cd10fff --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/src/index.html @@ -0,0 +1,69 @@ + + + + + + + +
+

WebSockets playground

+ + + +

WebSocket closed

+

+
+ + + diff --git a/packages/vite-plugin-cloudflare/playground/websockets/src/index.ts b/packages/vite-plugin-cloudflare/playground/websockets/src/index.ts new file mode 100644 index 0000000000000..65c46f9436b43 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/src/index.ts @@ -0,0 +1,44 @@ +import html from "./index.html?raw"; +import { DurableObject } from "cloudflare:workers"; + +interface Env { + WEBSOCKET_SERVER: DurableObjectNamespace; +} + +export class WebSocketServer extends DurableObject { + override fetch() { + const { 0: client, 1: server } = new WebSocketPair(); + + this.ctx.acceptWebSocket(server); + + return new Response(null, { status: 101, webSocket: client }); + } + + override async webSocketMessage(ws: WebSocket, data: string | ArrayBuffer) { + const decoder = new TextDecoder(); + const message = typeof data === "string" ? data : decoder.decode(data); + + ws.send(`Durable Object received client message: '${message}'.`); + } +} + +export default { + async fetch(request, env) { + if (request.url.endsWith("/websocket")) { + const upgradeHeader = request.headers.get("Upgrade"); + + if (!upgradeHeader || upgradeHeader !== "websocket") { + return new Response("Durable Object expected Upgrade: websocket", { + status: 426, + }); + } + + const id = env.WEBSOCKET_SERVER.idFromName("id"); + const stub = env.WEBSOCKET_SERVER.get(id); + + return stub.fetch(request); + } + + return new Response(html, { headers: { "content-type": "text/html" } }); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/websockets/tsconfig.json b/packages/vite-plugin-cloudflare/playground/websockets/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/websockets/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/websockets/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/websockets/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/websockets/tsconfig.worker.json new file mode 100644 index 0000000000000..da43778b826f4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/websockets/turbo.json b/packages/vite-plugin-cloudflare/playground/websockets/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/websockets/vite.config.ts b/packages/vite-plugin-cloudflare/playground/websockets/vite.config.ts new file mode 100644 index 0000000000000..55c8276338d72 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/vite.config.ts @@ -0,0 +1,6 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/websockets/wrangler.toml b/packages/vite-plugin-cloudflare/playground/websockets/wrangler.toml new file mode 100644 index 0000000000000..d3d793e0d924a --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/websockets/wrangler.toml @@ -0,0 +1,10 @@ +name = "worker" +main = "./src/index.ts" +compatibility_date = "2024-09-09" + +[durable_objects] +bindings = [{ name = "WEBSOCKET_SERVER", class_name = "WebSocketServer" }] + +[[migrations]] +tag = "v1" +new_classes = ["WebSocketServer"] diff --git a/packages/vite-plugin-cloudflare/playground/worker/__tests__/basic-functionality.spec.ts b/packages/vite-plugin-cloudflare/playground/worker/__tests__/basic-functionality.spec.ts new file mode 100644 index 0000000000000..659c89fcb4740 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/worker/__tests__/basic-functionality.spec.ts @@ -0,0 +1,12 @@ +import { expect, test } from "vitest"; +import { getTextResponse, serverLogs } from "../../__test-utils__"; + +test("basic hello-world functionality", async () => { + expect(await getTextResponse()).toEqual("Hello World!"); +}); + +test("basic dev logging", async () => { + expect(serverLogs.info.join()).toContain("__console log__"); + expect(serverLogs.errors.join()).toContain("__console error__"); + expect(serverLogs.errors.join()).toContain("__console warn__"); +}); diff --git a/packages/vite-plugin-cloudflare/playground/worker/package.json b/packages/vite-plugin-cloudflare/playground/worker/package.json new file mode 100644 index 0000000000000..78bc7cd70c8b8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/worker/package.json @@ -0,0 +1,19 @@ +{ + "name": "@playground/worker", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/worker/src/index.ts b/packages/vite-plugin-cloudflare/playground/worker/src/index.ts new file mode 100644 index 0000000000000..1eb1d77d2ead8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/worker/src/index.ts @@ -0,0 +1,9 @@ +export default { + async fetch() { + console.log("__console log__"); + console.warn("__console warn__"); + console.error("__console error__"); + + return new Response("Hello World!"); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/worker/tsconfig.json b/packages/vite-plugin-cloudflare/playground/worker/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/worker/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/worker/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/worker/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/worker/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/worker/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/worker/tsconfig.worker.json new file mode 100644 index 0000000000000..da43778b826f4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/worker/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/worker/turbo.json b/packages/vite-plugin-cloudflare/playground/worker/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/worker/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/worker/vite.config.ts b/packages/vite-plugin-cloudflare/playground/worker/vite.config.ts new file mode 100644 index 0000000000000..55c8276338d72 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/worker/vite.config.ts @@ -0,0 +1,6 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/worker/wrangler.toml b/packages/vite-plugin-cloudflare/playground/worker/wrangler.toml new file mode 100644 index 0000000000000..9a44ca3733ffb --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/worker/wrangler.toml @@ -0,0 +1,3 @@ +name = "worker" +main = "./src/index.ts" +compatibility_date = "2024-12-30" diff --git a/packages/vite-plugin-cloudflare/playground/workflows/__tests__/workflows.spec.ts b/packages/vite-plugin-cloudflare/playground/workflows/__tests__/workflows.spec.ts new file mode 100644 index 0000000000000..5b39535f978d3 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/workflows/__tests__/workflows.spec.ts @@ -0,0 +1,40 @@ +import { expect, test, vi } from "vitest"; +import { getJsonResponse } from "../../__test-utils__"; + +test("creates a Workflow with an ID", async () => { + const instanceId = "test-id"; + + expect(await getJsonResponse(`/create?id=${instanceId}`)).toEqual({ + id: instanceId, + status: { + status: "running", + __LOCAL_DEV_STEP_OUTPUTS: [], + output: null, + }, + }); + + await vi.waitFor( + async () => { + expect(await getJsonResponse(`/get?id=${instanceId}`)).toEqual({ + status: "running", + __LOCAL_DEV_STEP_OUTPUTS: [{ output: "First step result" }], + output: null, + }); + }, + { timeout: 5000 } + ); + + await vi.waitFor( + async () => { + expect(await getJsonResponse(`/get?id=${instanceId}`)).toEqual({ + status: "complete", + __LOCAL_DEV_STEP_OUTPUTS: [ + { output: "First step result" }, + { output: "Second step result" }, + ], + output: "Workflow output", + }); + }, + { timeout: 5000 } + ); +}); diff --git a/packages/vite-plugin-cloudflare/playground/workflows/package.json b/packages/vite-plugin-cloudflare/playground/workflows/package.json new file mode 100644 index 0000000000000..0b273fc15f224 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/workflows/package.json @@ -0,0 +1,19 @@ +{ + "name": "@playground/workflows", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20241230.0", + "typescript": "catalog:vite-plugin", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/workflows/src/index.ts b/packages/vite-plugin-cloudflare/playground/workflows/src/index.ts new file mode 100644 index 0000000000000..b895122305643 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/workflows/src/index.ts @@ -0,0 +1,60 @@ +import { WorkflowEntrypoint } from "cloudflare:workers"; +import type { WorkflowEvent, WorkflowStep } from "cloudflare:workers"; + +interface Env { + MY_WORKFLOW: Workflow; +} + +export class MyWorkflow extends WorkflowEntrypoint { + override async run(event: WorkflowEvent, step: WorkflowStep) { + await step.do("first step", async () => { + return { + output: "First step result", + }; + }); + + await step.sleep("sleep", "1 second"); + + await step.do("second step", async () => { + return { + output: "Second step result", + }; + }); + + return "Workflow output"; + } +} + +export default { + async fetch(request, env) { + const url = new URL(request.url); + const id = url.searchParams.get("id"); + + if (url.pathname === "/create") { + const instance = await env.MY_WORKFLOW.create( + id === null ? undefined : { id } + ); + + return Response.json({ + id: instance.id, + status: await instance.status(), + }); + } + + if (url.pathname === "/get") { + if (id === null) { + return new Response( + "Please provide an id (`/get?id=unique-instance-id`)" + ); + } + + const instance = await env.MY_WORKFLOW.get(id); + + return Response.json(await instance.status()); + } + + return new Response( + "Create a new Workflow instance (`/create` or `/create?id=unique-instance-id`) or inspect an existing instance (`/get?id=unique-instance-id`)." + ); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/workflows/tsconfig.json b/packages/vite-plugin-cloudflare/playground/workflows/tsconfig.json new file mode 100644 index 0000000000000..b52af703bdc29 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/workflows/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/workflows/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/workflows/tsconfig.node.json new file mode 100644 index 0000000000000..773be9834af5c --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/workflows/tsconfig.node.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": ["vite.config.ts", "__tests__"] +} diff --git a/packages/vite-plugin-cloudflare/playground/workflows/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/workflows/tsconfig.worker.json new file mode 100644 index 0000000000000..da43778b826f4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/workflows/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/playground/workflows/turbo.json b/packages/vite-plugin-cloudflare/playground/workflows/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/workflows/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/workflows/vite.config.ts b/packages/vite-plugin-cloudflare/playground/workflows/vite.config.ts new file mode 100644 index 0000000000000..55c8276338d72 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/workflows/vite.config.ts @@ -0,0 +1,6 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [cloudflare({ persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/workflows/wrangler.toml b/packages/vite-plugin-cloudflare/playground/workflows/wrangler.toml new file mode 100644 index 0000000000000..e0790f8455516 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/workflows/wrangler.toml @@ -0,0 +1,8 @@ +name = "worker" +main = "./src/index.ts" +compatibility_date = "2024-12-30" + +[[workflows]] +name = "workflow" +binding = "MY_WORKFLOW" +class_name = "MyWorkflow" diff --git a/packages/vite-plugin-cloudflare/src/__tests__/fixtures/simple-wrangler.toml b/packages/vite-plugin-cloudflare/src/__tests__/fixtures/simple-wrangler.toml new file mode 100644 index 0000000000000..ac4f9592941b8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/__tests__/fixtures/simple-wrangler.toml @@ -0,0 +1,3 @@ +name = "my-worker" +main = "./index.ts" +compatibility_date = "2024-12-30" diff --git a/packages/vite-plugin-cloudflare/src/__tests__/fixtures/wrangler-with-fields-to-ignore.toml b/packages/vite-plugin-cloudflare/src/__tests__/fixtures/wrangler-with-fields-to-ignore.toml new file mode 100644 index 0000000000000..2cf9e1378bec1 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/__tests__/fixtures/wrangler-with-fields-to-ignore.toml @@ -0,0 +1,37 @@ +name = "my-worker" +main = "./index.ts" +compatibility_date = "2024-12-30" + +base_dir = "./src" + +define = { define-a = "a", define-b = "b" } + +find_additional_modules = false + +minify = true + +no_bundle = false + +node_compat = false + +preserve_file_names = true + +rules = [{ type = "Text", globs = ["**/*.md"], fallthrough = true }] + +tsconfig = "./tsconfig.custom.json" + +upload_source_maps = false + +[alias] +"my-test" = "./my-test.ts" +"my-test-a" = "./my-test-a.ts" + +[build] +command = "npm run build" +cwd = "build_cwd" +watch_dir = "build_watch_dir" + +[site] +bucket = "./public" +include = ["upload_dir"] +exclude = ["ignore_dir"] diff --git a/packages/vite-plugin-cloudflare/src/__tests__/get-warning-for-workers-resolved-configs.spec.ts b/packages/vite-plugin-cloudflare/src/__tests__/get-warning-for-workers-resolved-configs.spec.ts new file mode 100644 index 0000000000000..ffe84a07be886 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/__tests__/get-warning-for-workers-resolved-configs.spec.ts @@ -0,0 +1,166 @@ +import { describe, expect, test } from "vitest"; +import { getWarningForWorkersConfigs } from "../workers-configs"; +import type { WorkerConfig } from "../plugin-config"; +import type { Unstable_Config as RawWorkerConfig } from "wrangler"; + +describe("getWarningForWorkersConfigs", () => { + describe("no warning needed", () => { + test("entry worker only", () => { + const warning = getWarningForWorkersConfigs({ + entryWorker: { + type: "worker", + config: { + name: "entry-worker", + configPath: "./wrangler.json", + } as Partial as WorkerConfig, + nonApplicable: getEmptyNotApplicableMap(), + raw: getEmptyRawConfig(), + }, + auxiliaryWorkers: [], + }); + expect(warning).toBeUndefined(); + }); + + test("multi workers", () => { + const warning = getWarningForWorkersConfigs({ + entryWorker: { + type: "worker", + config: { + name: "entry-worker", + configPath: "./wrangler.json", + } as Partial as WorkerConfig, + nonApplicable: getEmptyNotApplicableMap(), + raw: getEmptyRawConfig(), + }, + auxiliaryWorkers: [ + { + type: "worker", + config: { + name: "worker-a", + configPath: "./a/wrangler.json", + } as Partial as WorkerConfig, + nonApplicable: getEmptyNotApplicableMap(), + raw: getEmptyRawConfig(), + }, + { + type: "worker", + config: { + configPath: "./b/wrangler.json", + } as Partial as WorkerConfig, + nonApplicable: getEmptyNotApplicableMap(), + raw: getEmptyRawConfig(), + }, + ], + }); + expect(warning).toBeUndefined(); + }); + }); + + test("entry worker only", () => { + const warning = getWarningForWorkersConfigs({ + entryWorker: { + type: "worker", + config: { + name: "entry-worker", + configPath: "./wrangler.json", + } as Partial as WorkerConfig, + nonApplicable: { + replacedByVite: new Set(["alias", "minify"]), + notRelevant: new Set([ + "build", + "find_additional_modules", + "no_bundle", + ]), + overridden: new Set(["rules"]), + }, + raw: getEmptyRawConfig(), + }, + + auxiliaryWorkers: [], + }); + expect(warning).toMatchInlineSnapshot(` + " + + WARNING: your worker config (at \`wrangler.json\`) contains the following configuration options which are ignored since they are not applicable when using Vite: + - \`alias\` which is replaced by Vite's \`resolve.alias\` (docs: https://vite.dev/config/shared-options.html#resolve-alias) + - \`minify\` which is replaced by Vite's \`build.minify\` (docs: https://vite.dev/config/build-options.html#build-minify) + - \`build\`, \`find_additional_modules\`, \`no_bundle\` which are not relevant in the context of a Vite project + - \`rules\` which is overridden by \`@cloudflare/vite-plugin\` + " + `); + }); + + test("multi workers", () => { + const warning = getWarningForWorkersConfigs({ + entryWorker: { + type: "worker", + config: { + name: "entry-worker", + configPath: "./wrangler.json", + } as Partial as WorkerConfig, + nonApplicable: { + replacedByVite: new Set(["alias"]), + notRelevant: new Set(["build"]), + overridden: new Set(), + }, + raw: getEmptyRawConfig(), + }, + auxiliaryWorkers: [ + { + type: "worker", + config: { + name: "worker-a", + configPath: "./a/wrangler.json", + } as Partial as WorkerConfig, + nonApplicable: { + replacedByVite: new Set([]), + notRelevant: new Set(["find_additional_modules", "no_bundle"]), + overridden: new Set(), + }, + raw: getEmptyRawConfig(), + }, + { + type: "worker", + config: { + configPath: "./b/wrangler.json", + } as Partial as WorkerConfig, + nonApplicable: { + replacedByVite: new Set([]), + notRelevant: new Set(["site"]), + overridden: new Set(), + }, + raw: getEmptyRawConfig(), + }, + ], + }); + // Note: to make the snapshot work on windows we need to replace path backslashes into normal forward ones + const normalizedWarning = warning?.replaceAll( + "\\wrangler.json", + "/wrangler.json" + ); + expect(normalizedWarning).toMatchInlineSnapshot(` + " + WARNING: your workers configs contain configuration options which are ignored since they are not applicable when using Vite: + - (entry) worker "entry-worker" (config at \`wrangler.json\`) + - \`alias\` which is replaced by Vite's \`resolve.alias\` (docs: https://vite.dev/config/shared-options.html#resolve-alias) + - \`build\` which is not relevant in the context of a Vite project + - (auxiliary) worker "worker-a" (config at \`a/wrangler.json\`) + - \`find_additional_modules\`, \`no_bundle\` which are not relevant in the context of a Vite project + - (auxiliary) worker (config at \`b/wrangler.json\`) + - \`site\` which is not relevant in the context of a Vite project + " + `); + }); +}); + +function getEmptyNotApplicableMap() { + return { + replacedByVite: new Set([]), + notRelevant: new Set([]), + overridden: new Set([]), + }; +} + +function getEmptyRawConfig() { + return {} as RawWorkerConfig; +} diff --git a/packages/vite-plugin-cloudflare/src/__tests__/get-worker-config.spec.ts b/packages/vite-plugin-cloudflare/src/__tests__/get-worker-config.spec.ts new file mode 100644 index 0000000000000..c7490124417f6 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/__tests__/get-worker-config.spec.ts @@ -0,0 +1,165 @@ +import { fileURLToPath } from "node:url"; +import { describe, expect, test } from "vitest"; +import { getWorkerConfig } from "../workers-configs"; + +describe("getWorkerConfig", () => { + test("should return a simple raw config", () => { + const { raw } = getWorkerConfig( + fileURLToPath(new URL("fixtures/simple-wrangler.toml", import.meta.url)), + undefined + ); + expect(typeof raw).toEqual("object"); + + expect(raw.ai).toBeUndefined(); + expect(raw.alias).toBeUndefined(); + expect(raw.base_dir).toBeUndefined(); + expect(raw.build).toEqual({ + command: undefined, + cwd: undefined, + watch_dir: "./src", + }); + expect(raw.compatibility_flags).toEqual([]); + expect(raw.define).toEqual({}); + expect(raw.find_additional_modules).toBeUndefined(); + expect(raw.main).toMatch(/index\.ts$/); + expect(raw.minify).toBeUndefined(); + expect(raw.name).toEqual("my-worker"); + expect(raw.node_compat).toBeUndefined(); + expect(raw.no_bundle).toBeUndefined(); + expect(raw.preserve_file_names).toBeUndefined(); + expect(raw.rules).toEqual([]); + expect(raw.site).toBeUndefined(); + expect(raw.tsconfig).toBeUndefined(); + expect(raw.upload_source_maps).toBeUndefined(); + }); + + test("should return a simple config without non-applicable fields", () => { + const { config } = getWorkerConfig( + fileURLToPath(new URL("fixtures/simple-wrangler.toml", import.meta.url)), + undefined + ); + expect(typeof config).toEqual("object"); + + expect("preserve_file_names" in config).toBeFalsy(); + }); + + test("should not return any non-applicable config when there isn't any", () => { + const { nonApplicable } = getWorkerConfig( + fileURLToPath(new URL("fixtures/simple-wrangler.toml", import.meta.url)), + undefined + ); + expect(nonApplicable).toEqual({ + replacedByVite: new Set(), + notRelevant: new Set(), + overridden: new Set(), + }); + }); + + test("should read a simple wrangler.toml file", () => { + const { config, raw, nonApplicable } = getWorkerConfig( + fileURLToPath(new URL("fixtures/simple-wrangler.toml", import.meta.url)), + undefined + ); + expect(typeof config).toEqual("object"); + + expect(config.ai).toBeUndefined(); + expect(config.alias).toBeUndefined(); + expect(config.base_dir).toBeUndefined(); + expect("build" in config).toBeFalsy(); + expect(config.compatibility_flags).toEqual([]); + expect("define" in config).toBeFalsy(); + expect(config.find_additional_modules).toBeUndefined(); + expect(config.main).toMatch(/index\.ts$/); + expect(config.minify).toBeUndefined(); + expect(config.name).toEqual("my-worker"); + expect(config.node_compat).toBeUndefined(); + expect(config.no_bundle).toBeUndefined(); + expect(config.preserve_file_names).toBeUndefined(); + expect(config.rules).toEqual([]); + expect(config.site).toBeUndefined(); + expect(config.tsconfig).toBeUndefined(); + expect(config.upload_source_maps).toBeUndefined(); + + expect(nonApplicable).toEqual({ + replacedByVite: new Set(), + notRelevant: new Set(), + overridden: new Set(), + }); + }); + + test("should collect non applicable configs", () => { + const { config, raw, nonApplicable } = getWorkerConfig( + fileURLToPath( + new URL("fixtures/wrangler-with-fields-to-ignore.toml", import.meta.url) + ), + undefined + ); + + expect(typeof config).toEqual("object"); + + expect(config.ai).toBeUndefined(); + expect("alias" in config).toBeFalsy(); + expect(raw.alias).toEqual({ + "my-test": "./my-test.ts", + "my-test-a": "./my-test-a.ts", + }); + expect("base_dir" in config).toBeFalsy(); + expect(raw.base_dir).toMatch(/src$/); + expect("build" in config).toBeFalsy(); + expect(raw.build).toEqual({ + command: "npm run build", + cwd: "build_cwd", + watch_dir: expect.stringMatching(/build_watch_dir/), + }); + expect(config.compatibility_flags).toEqual([]); + expect("define" in config).toBeFalsy(); + expect(raw.define).toEqual({ + "define-a": "a", + "define-b": "b", + }); + expect("find_additional_modules" in config).toBeFalsy(); + expect(raw.find_additional_modules).toBe(false); + expect(config.main).toMatch(/index\.ts$/); + expect("minify" in config).toBeFalsy(); + expect(raw.minify).toBe(true); + expect(config.name).toEqual("my-worker"); + expect("node_compat" in config).toBeFalsy(); + expect(raw.node_compat).toEqual(false); + expect("no_bundle" in config).toBeFalsy(); + expect(raw.no_bundle).toEqual(false); + expect("preserve_file_names" in config).toBeFalsy(); + expect(raw.preserve_file_names).toBe(true); + expect(config.rules).toEqual([ + { type: "Text", globs: ["**/*.md"], fallthrough: true }, + ]); + expect("site" in config).toBeFalsy(); + expect(raw.site).toEqual({ + bucket: "./public", + "entry-point": undefined, + exclude: ["ignore_dir"], + include: ["upload_dir"], + }); + expect("tsconfig" in config).toBeFalsy(); + expect(raw.tsconfig).toMatch(/tsconfig\.custom\.json$/); + expect("upload_source_maps" in config).toBeFalsy(); + expect(raw.upload_source_maps).toBe(false); + + expect(nonApplicable.replacedByVite).toEqual( + new Set(["define", "alias", "minify"]) + ); + expect(nonApplicable.notRelevant).toEqual( + new Set([ + "base_dir", + "build", + "find_additional_modules", + "no_bundle", + "node_compat", + "preserve_file_names", + "site", + "tsconfig", + "upload_source_maps", + ]) + ); + expect(nonApplicable.overridden).toEqual(new Set(["rules"])); + }); +}); diff --git a/packages/vite-plugin-cloudflare/src/__tests__/utils.spec.ts b/packages/vite-plugin-cloudflare/src/__tests__/utils.spec.ts new file mode 100644 index 0000000000000..8fd64960b80a1 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/__tests__/utils.spec.ts @@ -0,0 +1,35 @@ +import * as path from "node:path"; +import { describe, expect, test } from "vitest"; +import { getOutputDirectory } from "../utils"; + +describe("getOutputDirectory", () => { + test("returns the correct output if `environments[environmentName].build.outDir` is defined", () => { + expect( + getOutputDirectory( + { + environments: { + worker: { + build: { outDir: "custom-environment-output-directory" }, + }, + }, + }, + "worker" + ) + ).toBe("custom-environment-output-directory"); + }); + + test("returns the correct output if `environments[environmentName].build.outDir` is not defined and `build.outDir` is defined", () => { + expect( + getOutputDirectory( + { build: { outDir: "custom-root-output-directory" } }, + "environment-name" + ) + ).toBe(path.join("custom-root-output-directory", "environment-name")); + }); + + test("returns the correct output if `environments[environmentName].build.outDir` and `build.outDir` are not defined", () => { + expect(getOutputDirectory({}, "environment-name")).toBe( + path.join("dist", "environment-name") + ); + }); +}); diff --git a/packages/vite-plugin-cloudflare/src/asset-workers/asset-worker.ts b/packages/vite-plugin-cloudflare/src/asset-workers/asset-worker.ts new file mode 100644 index 0000000000000..039e6aeedac58 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/asset-workers/asset-worker.ts @@ -0,0 +1,40 @@ +// @ts-ignore +import AssetWorker from "@cloudflare/workers-shared/dist/asset-worker.mjs"; +import { UNKNOWN_HOST } from "../shared"; +import type { WorkerEntrypoint } from "cloudflare:workers"; + +interface Env { + __VITE_ASSET_EXISTS__: Fetcher; + __VITE_FETCH_ASSET__: Fetcher; +} + +export default class CustomAssetWorker extends (AssetWorker as typeof WorkerEntrypoint) { + override async fetch(request: Request): Promise { + const response = await super.fetch!(request); + const modifiedResponse = new Response(response.body, response); + modifiedResponse.headers.delete("ETag"); + modifiedResponse.headers.delete("Cache-Control"); + + return modifiedResponse; + } + async unstable_getByETag( + eTag: string + ): Promise<{ readableStream: ReadableStream; contentType: string }> { + const url = new URL(eTag, UNKNOWN_HOST); + const response = await this.env.__VITE_FETCH_ASSET__.fetch(url); + + if (!response.body) { + throw new Error(`Unexpected error. No HTML found for ${eTag}.`); + } + + return { readableStream: response.body, contentType: "text/html" }; + } + async unstable_exists(pathname: string): Promise { + // We need this regex to avoid getting `//` as a pathname, which results in an invalid URL. Should this be fixed upstream? + const url = new URL(pathname.replace(/^\/{2,}/, "/"), UNKNOWN_HOST); + const response = await this.env.__VITE_ASSET_EXISTS__.fetch(url); + const exists = await response.json(); + + return exists ? pathname : null; + } +} diff --git a/packages/vite-plugin-cloudflare/src/asset-workers/router-worker.ts b/packages/vite-plugin-cloudflare/src/asset-workers/router-worker.ts new file mode 100644 index 0000000000000..b47dc64b74fa4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/asset-workers/router-worker.ts @@ -0,0 +1,4 @@ +// @ts-ignore +import routerWorker from "@cloudflare/workers-shared/dist/router-worker.mjs"; + +export default routerWorker; diff --git a/packages/vite-plugin-cloudflare/src/cloudflare-environment.ts b/packages/vite-plugin-cloudflare/src/cloudflare-environment.ts new file mode 100644 index 0000000000000..8abf3a7bb52ee --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/cloudflare-environment.ts @@ -0,0 +1,203 @@ +import assert from "node:assert"; +import { builtinModules } from "node:module"; +import * as vite from "vite"; +import { getNodeCompatExternals } from "./node-js-compat"; +import { INIT_PATH, UNKNOWN_HOST } from "./shared"; +import { getOutputDirectory } from "./utils"; +import type { ResolvedPluginConfig, WorkerConfig } from "./plugin-config"; +import type { Fetcher } from "@cloudflare/workers-types/experimental"; +import type { + MessageEvent, + Miniflare, + ReplaceWorkersTypes, + WebSocket, +} from "miniflare"; + +interface WebSocketContainer { + webSocket?: WebSocket; +} + +const webSocketUndefinedError = "The WebSocket is undefined"; + +function createHotChannel( + webSocketContainer: WebSocketContainer +): vite.HotChannel { + const listenersMap = new Map>(); + + const client: vite.HotChannelClient = { + send(payload) { + const webSocket = webSocketContainer.webSocket; + assert(webSocket, webSocketUndefinedError); + + webSocket.send(JSON.stringify(payload)); + }, + }; + + function onMessage(event: MessageEvent) { + const payload = JSON.parse(event.data.toString()) as vite.CustomPayload; + const listeners = listenersMap.get(payload.event) ?? new Set(); + + for (const listener of listeners) { + listener(payload.data, client); + } + } + + return { + send(payload) { + const webSocket = webSocketContainer.webSocket; + assert(webSocket, webSocketUndefinedError); + + webSocket.send(JSON.stringify(payload)); + }, + on(event: string, listener: vite.HotChannelListener) { + const listeners = listenersMap.get(event) ?? new Set(); + + listeners.add(listener); + listenersMap.set(event, listeners); + }, + off(event: string, listener: vite.HotChannelListener) { + listenersMap.get(event)?.delete(listener); + }, + listen() { + const webSocket = webSocketContainer.webSocket; + assert(webSocket, webSocketUndefinedError); + + webSocket.addEventListener("message", onMessage); + }, + close() { + const webSocket = webSocketContainer.webSocket; + assert(webSocket, webSocketUndefinedError); + + webSocket.removeEventListener("message", onMessage); + }, + }; +} + +export class CloudflareDevEnvironment extends vite.DevEnvironment { + #webSocketContainer: { webSocket?: WebSocket }; + #worker?: ReplaceWorkersTypes; + + constructor(name: string, config: vite.ResolvedConfig) { + // It would be good if we could avoid passing this object around and mutating it + const webSocketContainer = {}; + super(name, config, { + hot: true, + transport: createHotChannel(webSocketContainer), + }); + this.#webSocketContainer = webSocketContainer; + } + + async initRunner(worker: ReplaceWorkersTypes) { + this.#worker = worker; + + const response = await this.#worker.fetch( + new URL(INIT_PATH, UNKNOWN_HOST), + { + headers: { + upgrade: "websocket", + }, + } + ); + + assert(response.ok, "Failed to initialize module runner"); + + const webSocket = response.webSocket; + assert(webSocket, "Failed to establish WebSocket"); + + webSocket.accept(); + + this.#webSocketContainer.webSocket = webSocket; + } +} + +const cloudflareBuiltInModules = [ + "cloudflare:email", + "cloudflare:sockets", + "cloudflare:workers", + "cloudflare:workflows", +]; + +export function createCloudflareEnvironmentOptions( + workerConfig: WorkerConfig, + userConfig: vite.UserConfig, + environmentName: string +): vite.EnvironmentOptions { + return { + resolve: { + // Note: in order for ssr pre-bundling to take effect we need to ask vite to treat all + // dependencies as not external + noExternal: true, + // We want to use `workerd` package exports if available (e.g. for postgres). + conditions: ["workerd", "module", "browser", "development|production"], + }, + dev: { + createEnvironment(name, config) { + return new CloudflareDevEnvironment(name, config); + }, + }, + build: { + createEnvironment(name, config) { + return new vite.BuildEnvironment(name, config); + }, + outDir: getOutputDirectory(userConfig, environmentName), + ssr: true, + rollupOptions: { + // Note: vite starts dev pre-bundling crawling from either optimizeDeps.entries or rollupOptions.input + // so the input value here serves both as the build input as well as the starting point for + // dev pre-bundling crawling (were we not to set this input field we'd have to appropriately set + // optimizeDeps.entries in the dev config) + input: workerConfig.main, + external: [...cloudflareBuiltInModules, ...getNodeCompatExternals()], + }, + }, + optimizeDeps: { + // Note: ssr pre-bundling is opt-in and we need to enable it by setting `noDiscovery` to false + noDiscovery: false, + exclude: [ + ...cloudflareBuiltInModules, + // we have to exclude all node modules to work in dev-mode not just the unenv externals... + ...builtinModules.concat(builtinModules.map((m) => `node:${m}`)), + ], + esbuildOptions: { + platform: "neutral", + resolveExtensions: [ + ".mjs", + ".js", + ".mts", + ".ts", + ".jsx", + ".tsx", + ".json", + ".cjs", + ".cts", + ".ctx", + ], + }, + }, + keepProcessEnv: false, + }; +} + +export function initRunners( + resolvedPluginConfig: ResolvedPluginConfig, + viteDevServer: vite.ViteDevServer, + miniflare: Miniflare +): Promise | undefined { + if (resolvedPluginConfig.type === "assets-only") { + return; + } + + return Promise.all( + Object.entries(resolvedPluginConfig.workers).map( + async ([environmentName, workerConfig]) => { + const worker = await miniflare.getWorker(workerConfig.name); + + return ( + viteDevServer.environments[ + environmentName + ] as CloudflareDevEnvironment + ).initRunner(worker); + } + ) + ); +} diff --git a/packages/vite-plugin-cloudflare/src/constants.ts b/packages/vite-plugin-cloudflare/src/constants.ts new file mode 100644 index 0000000000000..aed9b5f4c382d --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/constants.ts @@ -0,0 +1,3 @@ +export const ROUTER_WORKER_NAME = "__router-worker__"; +export const ASSET_WORKER_NAME = "__asset-worker__"; +export const ASSET_WORKERS_COMPATIBILITY_DATE = "2024-10-04"; diff --git a/packages/vite-plugin-cloudflare/src/deploy-config.ts b/packages/vite-plugin-cloudflare/src/deploy-config.ts new file mode 100644 index 0000000000000..a01ce03694cab --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/deploy-config.ts @@ -0,0 +1,109 @@ +import assert from "node:assert"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as vite from "vite"; +import type { ResolvedPluginConfig } from "./plugin-config"; + +interface DeployConfig { + configPath: string; + auxiliaryWorkers: Array<{ configPath: string }>; +} + +function getDeployConfigPath(root: string) { + return path.resolve(root, ".wrangler", "deploy", "config.json"); +} + +export function getWorkerConfigPaths(root: string) { + const deployConfigPath = getDeployConfigPath(root); + const deployConfig = JSON.parse( + fs.readFileSync(deployConfigPath, "utf-8") + ) as DeployConfig; + + return [ + { configPath: deployConfig.configPath }, + ...deployConfig.auxiliaryWorkers, + ].map(({ configPath }) => + path.resolve(path.dirname(deployConfigPath), configPath) + ); +} + +function getRelativePathToWorkerConfig( + deployConfigDirectory: string, + root: string, + outputDirectory: string +) { + return path.relative( + deployConfigDirectory, + path.resolve(root, outputDirectory, "wrangler.json") + ); +} + +export function writeDeployConfig( + resolvedPluginConfig: ResolvedPluginConfig, + resolvedViteConfig: vite.ResolvedConfig +) { + const deployConfigPath = getDeployConfigPath(resolvedViteConfig.root); + const deployConfigDirectory = path.dirname(deployConfigPath); + + fs.mkdirSync(deployConfigDirectory, { recursive: true }); + + if (resolvedPluginConfig.type === "assets-only") { + const clientOutputDirectory = + resolvedViteConfig.environments.client?.build.outDir; + + assert( + clientOutputDirectory, + "Unexpected error: client environment output directory is undefined" + ); + + const deployConfig: DeployConfig = { + configPath: getRelativePathToWorkerConfig( + deployConfigDirectory, + resolvedViteConfig.root, + clientOutputDirectory + ), + auxiliaryWorkers: [], + }; + + fs.writeFileSync(deployConfigPath, JSON.stringify(deployConfig)); + } else { + const workerConfigPaths = Object.fromEntries( + Object.keys(resolvedPluginConfig.workers).map((environmentName) => { + const outputDirectory = + resolvedViteConfig.environments[environmentName]?.build.outDir; + + assert( + outputDirectory, + `Unexpected error: ${environmentName} environment output directory is undefined` + ); + + return [ + environmentName, + getRelativePathToWorkerConfig( + deployConfigDirectory, + resolvedViteConfig.root, + outputDirectory + ), + ]; + }) + ); + + const { entryWorkerEnvironmentName } = resolvedPluginConfig; + const configPath = workerConfigPaths[entryWorkerEnvironmentName]; + + assert( + configPath, + `Unexpected error: ${entryWorkerEnvironmentName} environment output directory is undefined` + ); + + const auxiliaryWorkers = Object.entries(workerConfigPaths) + .filter( + ([environmentName]) => environmentName !== entryWorkerEnvironmentName + ) + .map(([_, configPath]) => ({ configPath })); + + const deployConfig: DeployConfig = { configPath, auxiliaryWorkers }; + + fs.writeFileSync(deployConfigPath, JSON.stringify(deployConfig)); + } +} diff --git a/packages/vite-plugin-cloudflare/src/dev.ts b/packages/vite-plugin-cloudflare/src/dev.ts new file mode 100644 index 0000000000000..d8b5040de88d8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/dev.ts @@ -0,0 +1,22 @@ +import assert from "node:assert"; +import { ROUTER_WORKER_NAME } from "./constants"; +import type { ResolvedPluginConfig } from "./plugin-config"; +import type { Miniflare } from "miniflare"; + +export function getDevEntryWorker( + resolvedPluginConfig: ResolvedPluginConfig, + miniflare: Miniflare +) { + const entryWorkerConfig = + resolvedPluginConfig.type === "assets-only" + ? resolvedPluginConfig.config + : resolvedPluginConfig.workers[ + resolvedPluginConfig.entryWorkerEnvironmentName + ]; + + assert(entryWorkerConfig, "Unexpected error: No entry worker configuration"); + + return entryWorkerConfig.assets + ? miniflare.getWorker(ROUTER_WORKER_NAME) + : miniflare.getWorker(entryWorkerConfig.name); +} diff --git a/packages/vite-plugin-cloudflare/src/index.ts b/packages/vite-plugin-cloudflare/src/index.ts new file mode 100644 index 0000000000000..f75bebeacdca4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/index.ts @@ -0,0 +1,292 @@ +import assert from "node:assert"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import { createMiddleware } from "@hattip/adapter-node"; +import { Miniflare } from "miniflare"; +import * as vite from "vite"; +import { + createCloudflareEnvironmentOptions, + initRunners, +} from "./cloudflare-environment"; +import { writeDeployConfig } from "./deploy-config"; +import { getDevEntryWorker } from "./dev"; +import { + getDevMiniflareOptions, + getPreviewMiniflareOptions, +} from "./miniflare-options"; +import { + getNodeCompatAliases, + injectGlobalCode, + resolveNodeCompatId, +} from "./node-js-compat"; +import { resolvePluginConfig } from "./plugin-config"; +import { getOutputDirectory, toMiniflareRequest } from "./utils"; +import { handleWebSocket } from "./websockets"; +import { getWarningForWorkersConfigs } from "./workers-configs"; +import type { PluginConfig, ResolvedPluginConfig } from "./plugin-config"; +import type { Unstable_RawConfig } from "wrangler"; + +export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin { + let resolvedPluginConfig: ResolvedPluginConfig; + let resolvedViteConfig: vite.ResolvedConfig; + let miniflare: Miniflare | undefined; + + // this flag is used to shown the workers configs warning only once + let workersConfigsWarningShown = false; + + return { + name: "vite-plugin-cloudflare", + config(userConfig, env) { + if (env.isPreview) { + return { appType: "custom" }; + } + + resolvedPluginConfig = resolvePluginConfig(pluginConfig, userConfig, env); + + if (!workersConfigsWarningShown) { + workersConfigsWarningShown = true; + const workersConfigsWarning = getWarningForWorkersConfigs( + resolvedPluginConfig.rawConfigs + ); + if (workersConfigsWarning) { + console.warn(workersConfigsWarning); + } + } + + return { + appType: "custom", + resolve: { + alias: getNodeCompatAliases(), + }, + environments: + resolvedPluginConfig.type === "workers" + ? { + ...Object.fromEntries( + Object.entries(resolvedPluginConfig.workers).map( + ([environmentName, workerConfig]) => { + return [ + environmentName, + createCloudflareEnvironmentOptions( + workerConfig, + userConfig, + environmentName + ), + ]; + } + ) + ), + client: { + build: { + outDir: getOutputDirectory(userConfig, "client"), + }, + }, + } + : undefined, + builder: { + async buildApp(builder) { + const clientEnvironment = builder.environments.client; + const defaultHtmlPath = path.resolve( + builder.config.root, + "index.html" + ); + + if ( + clientEnvironment && + (clientEnvironment.config.build.rollupOptions.input || + fs.existsSync(defaultHtmlPath)) + ) { + await builder.build(clientEnvironment); + } + + if (resolvedPluginConfig.type === "workers") { + const workerEnvironments = Object.keys( + resolvedPluginConfig.workers + ).map((environmentName) => { + const environment = builder.environments[environmentName]; + + assert(environment, `${environmentName} environment not found`); + + return environment; + }); + + await Promise.all( + workerEnvironments.map((environment) => + builder.build(environment) + ) + ); + } + + writeDeployConfig(resolvedPluginConfig, resolvedViteConfig); + }, + }, + }; + }, + configResolved(config) { + resolvedViteConfig = config; + }, + async resolveId(source) { + if (resolvedPluginConfig.type === "assets-only") { + return; + } + + const workerConfig = resolvedPluginConfig.workers[this.environment.name]; + if (!workerConfig) { + return; + } + + return resolveNodeCompatId(this.environment, workerConfig, source); + }, + async transform(code, id) { + if (resolvedPluginConfig.type === "assets-only") { + return; + } + + const workerConfig = resolvedPluginConfig.workers[this.environment.name]; + + if (!workerConfig) { + return; + } + + const resolvedId = await this.resolve(workerConfig.main); + + if (id === resolvedId?.id) { + return injectGlobalCode(id, code, workerConfig); + } + }, + generateBundle(_, bundle) { + let config: Unstable_RawConfig | undefined; + + if (resolvedPluginConfig.type === "workers") { + const workerConfig = + resolvedPluginConfig.workers[this.environment.name]; + + const entryChunk = Object.entries(bundle).find( + ([_, chunk]) => chunk.type === "chunk" && chunk.isEntry + ); + + if (!workerConfig || !entryChunk) { + return; + } + + workerConfig.main = entryChunk[0]; + + const isEntryWorker = + this.environment.name === + resolvedPluginConfig.entryWorkerEnvironmentName; + + if (isEntryWorker && workerConfig.assets) { + const workerOutputDirectory = this.environment.config.build.outDir; + const clientOutputDirectory = + resolvedViteConfig.environments.client?.build.outDir; + + assert( + clientOutputDirectory, + "Unexpected error: client output directory is undefined" + ); + + workerConfig.assets.directory = path.relative( + path.resolve(resolvedViteConfig.root, workerOutputDirectory), + path.resolve(resolvedViteConfig.root, clientOutputDirectory) + ); + } + + config = workerConfig; + } else if (this.environment.name === "client") { + const assetsOnlyConfig = resolvedPluginConfig.config; + + assetsOnlyConfig.assets.directory = "."; + + this.emitFile({ + type: "asset", + fileName: ".assetsignore", + source: "wrangler.json", + }); + + config = assetsOnlyConfig; + } + + if (!config) { + return; + } + + config.no_bundle = true; + config.rules = [{ type: "ESModule", globs: ["**/*.js"] }]; + // Setting this to `undefined` for now because `readConfig` will error when reading the output file if it's set to an empty object. This needs to be fixed in Wrangler. + config.unsafe = undefined; + + this.emitFile({ + type: "asset", + fileName: "wrangler.json", + source: JSON.stringify(config), + }); + }, + handleHotUpdate(options) { + if (resolvedPluginConfig.configPaths.has(options.file)) { + options.server.restart(); + } + }, + async buildEnd() { + if (miniflare) { + await miniflare.dispose(); + miniflare = undefined; + } + }, + async configureServer(viteDevServer) { + assert(viteDevServer.httpServer, "Unexpected error: No Vite HTTP server"); + + miniflare = new Miniflare( + getDevMiniflareOptions(resolvedPluginConfig, viteDevServer) + ); + + await initRunners(resolvedPluginConfig, viteDevServer, miniflare); + const entryWorker = await getDevEntryWorker( + resolvedPluginConfig, + miniflare + ); + + const middleware = createMiddleware(({ request }) => { + return entryWorker.fetch(toMiniflareRequest(request), { + redirect: "manual", + }) as any; + }); + + handleWebSocket( + viteDevServer.httpServer, + entryWorker.fetch, + viteDevServer.config.logger + ); + + return () => { + viteDevServer.middlewares.use((req, res, next) => { + middleware(req, res, next); + }); + }; + }, + configurePreviewServer(vitePreviewServer) { + const miniflare = new Miniflare( + getPreviewMiniflareOptions( + vitePreviewServer, + pluginConfig.persistState ?? true + ) + ); + + const middleware = createMiddleware(({ request }) => { + return miniflare.dispatchFetch(toMiniflareRequest(request), { + redirect: "manual", + }) as any; + }); + + handleWebSocket( + vitePreviewServer.httpServer, + miniflare.dispatchFetch, + vitePreviewServer.config.logger + ); + + return () => { + vitePreviewServer.middlewares.use((req, res, next) => { + middleware(req, res, next); + }); + }; + }, + }; +} diff --git a/packages/vite-plugin-cloudflare/src/miniflare-options.ts b/packages/vite-plugin-cloudflare/src/miniflare-options.ts new file mode 100644 index 0000000000000..a43fb59c2d15e --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/miniflare-options.ts @@ -0,0 +1,525 @@ +import assert from "node:assert"; +import * as fs from "node:fs"; +import * as fsp from "node:fs/promises"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; +import { Log, LogLevel, Response as MiniflareResponse } from "miniflare"; +import * as vite from "vite"; +import { + unstable_getMiniflareWorkerOptions, + unstable_readConfig, +} from "wrangler"; +import { + ASSET_WORKER_NAME, + ASSET_WORKERS_COMPATIBILITY_DATE, + ROUTER_WORKER_NAME, +} from "./constants"; +import { getWorkerConfigPaths } from "./deploy-config"; +import type { CloudflareDevEnvironment } from "./cloudflare-environment"; +import type { + PersistState, + ResolvedPluginConfig, + WorkerConfig, +} from "./plugin-config"; +import type { MiniflareOptions, SharedOptions, WorkerOptions } from "miniflare"; +import type { FetchFunctionOptions } from "vite/module-runner"; + +type PersistOptions = Pick< + SharedOptions, + | "cachePersist" + | "d1Persist" + | "durableObjectsPersist" + | "kvPersist" + | "r2Persist" + | "workflowsPersist" +>; + +function getPersistence( + root: string, + persistState: PersistState +): PersistOptions { + if (persistState === false) { + return {}; + } + + const defaultPersistPath = ".wrangler/state"; + const persistPath = path.resolve( + root, + typeof persistState === "object" ? persistState.path : defaultPersistPath, + "v3" + ); + + return { + cachePersist: path.join(persistPath, "cache"), + d1Persist: path.join(persistPath, "d1"), + durableObjectsPersist: path.join(persistPath, "do"), + kvPersist: path.join(persistPath, "kv"), + r2Persist: path.join(persistPath, "r2"), + workflowsPersist: path.join(persistPath, "workflows"), + }; +} + +function missingWorkerErrorMessage(workerName: string) { + return `${workerName} does not match a worker name.`; +} + +function getWorkerToWorkerEntrypointNamesMap( + workers: Array & { name: string }> +) { + const workerToWorkerEntrypointNamesMap = new Map( + workers.map((workerOptions) => [workerOptions.name, new Set()]) + ); + + for (const worker of workers) { + for (const value of Object.values(worker.serviceBindings ?? {})) { + if ( + typeof value === "object" && + "name" in value && + typeof value.name === "string" && + value.entrypoint !== undefined && + value.entrypoint !== "default" + ) { + const entrypointNames = workerToWorkerEntrypointNamesMap.get( + value.name + ); + assert(entrypointNames, missingWorkerErrorMessage(value.name)); + + entrypointNames.add(value.entrypoint); + } + } + } + + return workerToWorkerEntrypointNamesMap; +} + +function getWorkerToDurableObjectClassNamesMap( + workers: Array & { name: string }> +) { + const workerToDurableObjectClassNamesMap = new Map( + workers.map((workerOptions) => [workerOptions.name, new Set()]) + ); + + for (const worker of workers) { + for (const value of Object.values(worker.durableObjects ?? {})) { + if (typeof value === "string") { + const classNames = workerToDurableObjectClassNamesMap.get(worker.name); + assert(classNames, missingWorkerErrorMessage(worker.name)); + + classNames.add(value); + } else if (typeof value === "object") { + if (value.scriptName) { + const classNames = workerToDurableObjectClassNamesMap.get( + value.scriptName + ); + assert(classNames, missingWorkerErrorMessage(value.scriptName)); + + classNames.add(value.className); + } else { + const classNames = workerToDurableObjectClassNamesMap.get( + worker.name + ); + assert(classNames, missingWorkerErrorMessage(worker.name)); + + classNames.add(value.className); + } + } + } + } + + return workerToDurableObjectClassNamesMap; +} + +function getWorkerToWorkflowEntrypointClassNamesMap( + workers: Array & { name: string }> +) { + const workerToWorkflowEntrypointClassNamesMap = new Map( + workers.map((workerOptions) => [workerOptions.name, new Set()]) + ); + + for (const worker of workers) { + for (const value of Object.values(worker.workflows ?? {})) { + if (value.scriptName) { + const classNames = workerToWorkflowEntrypointClassNamesMap.get( + value.scriptName + ); + assert(classNames, missingWorkerErrorMessage(value.scriptName)); + + classNames.add(value.className); + } else { + const classNames = workerToWorkflowEntrypointClassNamesMap.get( + worker.name + ); + assert(classNames, missingWorkerErrorMessage(worker.name)); + + classNames.add(value.className); + } + } + } + + return workerToWorkflowEntrypointClassNamesMap; +} + +// We want module names to be their absolute path without the leading slash +// (i.e. the modules root should be the root directory). On Windows, we need +// paths to include the drive letter (i.e. `C:/a/b/c/index.mjs`). +// Internally, Miniflare uses `path.relative(modulesRoot, path)` to compute +// module names. Setting `modulesRoot` to a drive letter and prepending this +// to paths ensures correct names. This requires us to specify `contents` in +// the miniflare module definitions though, as the new paths don't exist. +const miniflareModulesRoot = process.platform === "win32" ? "Z:\\" : "/"; +const ROUTER_WORKER_PATH = "./asset-workers/router-worker.js"; +const ASSET_WORKER_PATH = "./asset-workers/asset-worker.js"; +const WRAPPER_PATH = "__VITE_WORKER_ENTRY__"; +const RUNNER_PATH = "./runner-worker/index.js"; + +function getEntryWorkerConfig( + resolvedPluginConfig: ResolvedPluginConfig +): WorkerConfig | undefined { + if (resolvedPluginConfig.type === "assets-only") { + return; + } + + return resolvedPluginConfig.workers[ + resolvedPluginConfig.entryWorkerEnvironmentName + ]; +} + +export function getDevMiniflareOptions( + resolvedPluginConfig: ResolvedPluginConfig, + viteDevServer: vite.ViteDevServer +): MiniflareOptions { + const resolvedViteConfig = viteDevServer.config; + const entryWorkerConfig = getEntryWorkerConfig(resolvedPluginConfig); + const assetsConfig = + resolvedPluginConfig.type === "assets-only" + ? resolvedPluginConfig.config.assets + : entryWorkerConfig?.assets; + + const assetWorkers: Array = [ + { + name: ROUTER_WORKER_NAME, + compatibilityDate: ASSET_WORKERS_COMPATIBILITY_DATE, + modulesRoot: miniflareModulesRoot, + modules: [ + { + type: "ESModule", + path: path.join(miniflareModulesRoot, ROUTER_WORKER_PATH), + contents: fs.readFileSync( + fileURLToPath(new URL(ROUTER_WORKER_PATH, import.meta.url)) + ), + }, + ], + bindings: { + CONFIG: { + has_user_worker: resolvedPluginConfig.type === "workers", + }, + }, + serviceBindings: { + ASSET_WORKER: ASSET_WORKER_NAME, + ...(entryWorkerConfig ? { USER_WORKER: entryWorkerConfig.name } : {}), + }, + }, + { + name: ASSET_WORKER_NAME, + compatibilityDate: ASSET_WORKERS_COMPATIBILITY_DATE, + modulesRoot: miniflareModulesRoot, + modules: [ + { + type: "ESModule", + path: path.join(miniflareModulesRoot, ASSET_WORKER_PATH), + contents: fs.readFileSync( + fileURLToPath(new URL(ASSET_WORKER_PATH, import.meta.url)) + ), + }, + ], + bindings: { + CONFIG: { + ...(assetsConfig?.html_handling + ? { html_handling: assetsConfig.html_handling } + : {}), + ...(assetsConfig?.not_found_handling + ? { not_found_handling: assetsConfig.not_found_handling } + : {}), + }, + }, + serviceBindings: { + __VITE_ASSET_EXISTS__: async (request) => { + const { pathname } = new URL(request.url); + const filePath = path.join(resolvedViteConfig.root, pathname); + + let exists: boolean; + + try { + exists = fs.statSync(filePath).isFile(); + } catch (error) { + exists = false; + } + + return MiniflareResponse.json(exists); + }, + __VITE_FETCH_ASSET__: async (request) => { + const { pathname } = new URL(request.url); + const filePath = path.join(resolvedViteConfig.root, pathname); + + try { + let html = await fsp.readFile(filePath, "utf-8"); + html = await viteDevServer.transformIndexHtml(pathname, html); + + return new MiniflareResponse(html, { + headers: { "Content-Type": "text/html" }, + }); + } catch (error) { + throw new Error(`Unexpected error. Failed to load ${pathname}`); + } + }, + }, + }, + ]; + + const userWorkers = + resolvedPluginConfig.type === "workers" + ? Object.entries(resolvedPluginConfig.workers).map( + ([environmentName, workerConfig]) => { + const miniflareWorkerOptions = unstable_getMiniflareWorkerOptions({ + ...workerConfig, + assets: undefined, + }); + + const { ratelimits, ...workerOptions } = + miniflareWorkerOptions.workerOptions; + + return { + ...workerOptions, + // We have to add the name again because `unstable_getMiniflareWorkerOptions` sets it to `undefined` + name: workerConfig.name, + modulesRoot: miniflareModulesRoot, + unsafeEvalBinding: "__VITE_UNSAFE_EVAL__", + bindings: { + ...workerOptions.bindings, + __VITE_ROOT__: resolvedViteConfig.root, + __VITE_ENTRY_PATH__: workerConfig.main, + }, + serviceBindings: { + ...workerOptions.serviceBindings, + ...(environmentName === + resolvedPluginConfig.entryWorkerEnvironmentName && + workerConfig.assets?.binding + ? { + [workerConfig.assets.binding]: ASSET_WORKER_NAME, + } + : {}), + __VITE_INVOKE_MODULE__: async (request) => { + const payload = (await request.json()) as vite.CustomPayload; + const invokePayloadData = payload.data as { + id: string; + name: string; + data: [string, string, FetchFunctionOptions]; + }; + + assert( + invokePayloadData.name === "fetchModule", + `Invalid invoke event: ${invokePayloadData.name}` + ); + + const [moduleId] = invokePayloadData.data; + + // For some reason we need this here for cloudflare built-ins (e.g. `cloudflare:workers`) but not for node built-ins (e.g. `node:path`) + // See https://github.com/flarelabs-net/vite-plugin-cloudflare/issues/46 + if (moduleId.startsWith("cloudflare:")) { + const result = { + externalize: moduleId, + type: "builtin", + } satisfies vite.FetchResult; + + return new MiniflareResponse(JSON.stringify({ result })); + } + + const devEnvironment = viteDevServer.environments[ + environmentName + ] as CloudflareDevEnvironment; + + const result = await devEnvironment.hot.handleInvoke(payload); + + return new MiniflareResponse(JSON.stringify(result)); + }, + }, + } satisfies Partial; + } + ) + : []; + + const workerToWorkerEntrypointNamesMap = + getWorkerToWorkerEntrypointNamesMap(userWorkers); + const workerToDurableObjectClassNamesMap = + getWorkerToDurableObjectClassNamesMap(userWorkers); + const workerToWorkflowEntrypointClassNamesMap = + getWorkerToWorkflowEntrypointClassNamesMap(userWorkers); + + const logger = new ViteMiniflareLogger(resolvedViteConfig); + + return { + log: logger, + handleRuntimeStdio(stdout, stderr) { + const decoder = new TextDecoder(); + stdout.forEach((data) => logger.info(decoder.decode(data))); + stderr.forEach((error) => + logger.logWithLevel(LogLevel.ERROR, decoder.decode(error)) + ); + }, + ...getPersistence( + resolvedViteConfig.root, + resolvedPluginConfig.persistState + ), + workers: [ + ...assetWorkers, + ...userWorkers.map((workerOptions) => { + const wrappers = [ + `import { createWorkerEntrypointWrapper, createDurableObjectWrapper, createWorkflowEntrypointWrapper } from '${RUNNER_PATH}';`, + `export default createWorkerEntrypointWrapper('default');`, + ]; + + const workerEntrypointNames = workerToWorkerEntrypointNamesMap.get( + workerOptions.name + ); + assert( + workerEntrypointNames, + `WorkerEntrypoint names not found for worker ${workerOptions.name}` + ); + + for (const entrypointName of [...workerEntrypointNames].sort()) { + wrappers.push( + `export const ${entrypointName} = createWorkerEntrypointWrapper('${entrypointName}');` + ); + } + + const durableObjectClassNames = workerToDurableObjectClassNamesMap.get( + workerOptions.name + ); + assert( + durableObjectClassNames, + `DurableObject class names not found for worker ${workerOptions.name}` + ); + + for (const className of [...durableObjectClassNames].sort()) { + wrappers.push( + `export const ${className} = createDurableObjectWrapper('${className}');` + ); + } + + const workflowEntrypointClassNames = + workerToWorkflowEntrypointClassNamesMap.get(workerOptions.name); + assert( + workflowEntrypointClassNames, + `WorkflowEntrypoint class names not found for worker ${workerOptions.name}` + ); + + for (const className of [...workflowEntrypointClassNames].sort()) { + wrappers.push( + `export const ${className} = createWorkflowEntrypointWrapper('${className}');` + ); + } + + return { + ...workerOptions, + modules: [ + { + type: "ESModule", + path: path.join(miniflareModulesRoot, WRAPPER_PATH), + contents: wrappers.join("\n"), + }, + { + type: "ESModule", + path: path.join(miniflareModulesRoot, RUNNER_PATH), + contents: fs.readFileSync( + fileURLToPath(new URL(RUNNER_PATH, import.meta.url)) + ), + }, + ], + } satisfies WorkerOptions; + }), + ], + }; +} + +export function getPreviewMiniflareOptions( + vitePreviewServer: vite.PreviewServer, + persistState: PersistState +): MiniflareOptions { + const resolvedViteConfig = vitePreviewServer.config; + const configPaths = getWorkerConfigPaths(resolvedViteConfig.root); + const workerConfigs = configPaths.map((configPath) => + unstable_readConfig({ config: configPath }) + ); + + const workers: Array = workerConfigs.map((config) => { + const miniflareWorkerOptions = unstable_getMiniflareWorkerOptions(config); + + const { ratelimits, ...workerOptions } = + miniflareWorkerOptions.workerOptions; + + return { + ...workerOptions, + // We have to add the name again because `unstable_getMiniflareWorkerOptions` sets it to `undefined` + name: config.name, + modules: true, + ...(miniflareWorkerOptions.main + ? { scriptPath: miniflareWorkerOptions.main } + : { script: "" }), + }; + }); + + const logger = new ViteMiniflareLogger(resolvedViteConfig); + + return { + log: logger, + handleRuntimeStdio(stdout, stderr) { + const decoder = new TextDecoder(); + stdout.forEach((data) => logger.info(decoder.decode(data))); + stderr.forEach((error) => + logger.logWithLevel(LogLevel.ERROR, decoder.decode(error)) + ); + }, + ...getPersistence(resolvedViteConfig.root, persistState), + workers, + }; +} + +/** + * A Miniflare logger that forwards messages onto a Vite logger. + */ +class ViteMiniflareLogger extends Log { + private logger: vite.Logger; + constructor(config: vite.ResolvedConfig) { + super(miniflareLogLevelFromViteLogLevel(config.logLevel)); + this.logger = config.logger; + } + + override logWithLevel(level: LogLevel, message: string) { + if (/^Ready on http/.test(message)) { + level = LogLevel.DEBUG; + } + switch (level) { + case LogLevel.ERROR: + return this.logger.error(message); + case LogLevel.WARN: + return this.logger.warn(message); + case LogLevel.INFO: + return this.logger.info(message); + } + } +} + +function miniflareLogLevelFromViteLogLevel( + level: vite.LogLevel = "info" +): LogLevel { + switch (level) { + case "error": + return LogLevel.ERROR; + case "warn": + return LogLevel.WARN; + case "info": + return LogLevel.INFO; + case "silent": + return LogLevel.NONE; + } +} diff --git a/packages/vite-plugin-cloudflare/src/node-js-compat.ts b/packages/vite-plugin-cloudflare/src/node-js-compat.ts new file mode 100644 index 0000000000000..10a1e2a3492dd --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/node-js-compat.ts @@ -0,0 +1,140 @@ +import { createRequire } from "node:module"; +import MagicString from "magic-string"; +import { getNodeCompat } from "miniflare"; +import * as unenv from "unenv"; +import type { WorkerConfig } from "./plugin-config"; +import type { Environment } from "vite"; + +const require = createRequire(import.meta.url); +const preset = unenv.env(unenv.nodeless, unenv.cloudflare); +const CLOUDFLARE_VIRTUAL_PREFIX = "\0cloudflare-"; + +/** + * Returns true if the given combination of compat dates and flags means that we need Node.js compatibility. + */ +export function isNodeCompat({ + compatibility_date, + compatibility_flags, +}: WorkerConfig): boolean { + const nodeCompatMode = getNodeCompat( + compatibility_date, + compatibility_flags ?? [] + ).mode; + if (nodeCompatMode === "v2") { + return true; + } + if (nodeCompatMode === "legacy") { + throw new Error( + "Unsupported Node.js compat mode (legacy). Remove the `node_compat` setting and add the `nodejs_compat` flag instead." + ); + } + if (nodeCompatMode === "v1") { + throw new Error( + `Unsupported Node.js compat mode (v1). Only the v2 mode is supported, either change your compat date to "2024-09-23" or later, or set the "nodejs_compat_v2" compatibility flag` + ); + } + return false; +} + +/** + * If the current environment needs Node.js compatibility, + * then inject the necessary global polyfills into the code. + */ +export function injectGlobalCode( + id: string, + code: string, + workerConfig: WorkerConfig +) { + if (!isNodeCompat(workerConfig)) { + return; + } + + const injectedCode = Object.entries(preset.inject) + .map(([globalName, globalInject]) => { + if (typeof globalInject === "string") { + const moduleSpecifier = globalInject; + // the mapping is a simple string, indicating a default export, so the string is just the module specifier. + return `import var_${globalName} from "${moduleSpecifier}";\nglobalThis.${globalName} = var_${globalName};\n`; + } + + // the mapping is a 2 item tuple, indicating a named export, made up of a module specifier and an export name. + const [moduleSpecifier, exportName] = globalInject; + return `import var_${globalName} from "${moduleSpecifier}";\nglobalThis.${globalName} = var_${globalName}.${exportName};\n`; + }) + .join("\n"); + + const modified = new MagicString(code); + modified.prepend(injectedCode); + return { + code: modified.toString(), + map: modified.generateMap({ hires: "boundary", source: id }), + }; +} + +/** + * We only want to alias Node.js built-ins if the environment has Node.js compatibility turned on. + * But Vite only allows us to configure aliases at the shared options level, not per environment. + * So instead we alias these to a virtual module, which are then handled with environment specific code in the `resolveId` handler + */ +export function getNodeCompatAliases() { + const aliases: Record = {}; + Object.keys(preset.alias).forEach((key) => { + // Don't create aliases for modules that are already marked as external + if (!preset.external.includes(key)) { + aliases[key] = CLOUDFLARE_VIRTUAL_PREFIX + key; + } + }); + return aliases; +} + +/** + * Attempt to resolve the `id` to an unenv alias or polyfill. + */ +export function resolveNodeCompatId( + environment: Environment, + workerConfig: WorkerConfig, + id: string +) { + const aliased = resolveNodeAliases(id, workerConfig) ?? id; + + if (aliased.startsWith("unenv/")) { + const resolvedDep = require.resolve(aliased).replace(/\.cjs$/, ".mjs"); + if (environment.mode === "dev" && environment.depsOptimizer) { + const dep = environment.depsOptimizer.registerMissingImport( + aliased, + resolvedDep + ); + return dep.id; + } else { + return resolvedDep; + } + } +} + +/** + * Get an array of modules that should be considered external. + */ +export function getNodeCompatExternals(): string[] { + return preset.external; +} + +/** + * Convert any virtual module Id that was generated by the aliases returned from `getNodeCompatAliases()` + * back to real a module Id and whether it is an external (built-in) package or not. + */ +function resolveNodeAliases(source: string, workerConfig: WorkerConfig) { + if ( + !source.startsWith(CLOUDFLARE_VIRTUAL_PREFIX) || + !isNodeCompat(workerConfig) + ) { + return; + } + + const from = source.slice(CLOUDFLARE_VIRTUAL_PREFIX.length); + const alias = preset.alias[from]; + + if (alias && preset.external.includes(alias)) { + throw new Error(`Alias to external: ${source} -> ${alias}`); + } + return alias; +} diff --git a/packages/vite-plugin-cloudflare/src/plugin-config.ts b/packages/vite-plugin-cloudflare/src/plugin-config.ts new file mode 100644 index 0000000000000..f1e6235a79f5b --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/plugin-config.ts @@ -0,0 +1,171 @@ +import assert from "node:assert"; +import * as path from "node:path"; +import * as vite from "vite"; +import { findWranglerConfig, getWorkerConfig } from "./workers-configs"; +import type { + AssetsOnlyWorkerResolvedConfig, + SanitizedWorkerConfig, + WorkerResolvedConfig, + WorkerWithServerLogicResolvedConfig, +} from "./workers-configs"; + +export type PersistState = boolean | { path: string }; + +interface BaseWorkerConfig { + viteEnvironment?: { name?: string }; +} + +interface EntryWorkerConfig extends BaseWorkerConfig { + configPath?: string; +} + +interface AuxiliaryWorkerConfig extends BaseWorkerConfig { + configPath: string; +} + +export interface PluginConfig extends EntryWorkerConfig { + auxiliaryWorkers?: AuxiliaryWorkerConfig[]; + persistState?: PersistState; +} + +type Defined = Exclude; + +interface BaseConfig extends SanitizedWorkerConfig { + topLevelName: Defined; + name: Defined; + compatibility_date: Defined; +} + +export interface AssetsOnlyConfig extends BaseConfig { + assets: Defined; +} + +export interface WorkerConfig extends BaseConfig { + main: Defined; +} + +interface BasePluginConfig { + configPaths: Set; + persistState: PersistState; +} + +interface AssetsOnlyPluginConfig extends BasePluginConfig { + type: "assets-only"; + config: AssetsOnlyConfig; + rawConfigs: { + entryWorker: AssetsOnlyWorkerResolvedConfig; + }; +} + +interface WorkerPluginConfig extends BasePluginConfig { + type: "workers"; + workers: Record; + entryWorkerEnvironmentName: string; + rawConfigs: { + entryWorker: WorkerWithServerLogicResolvedConfig; + auxiliaryWorkers: WorkerResolvedConfig[]; + }; +} + +export type ResolvedPluginConfig = AssetsOnlyPluginConfig | WorkerPluginConfig; + +// Worker names can only contain alphanumeric characters and '-' whereas environment names can only contain alphanumeric characters and '$', '_' +function workerNameToEnvironmentName(workerName: string) { + return workerName.replaceAll("-", "_"); +} + +export function resolvePluginConfig( + pluginConfig: PluginConfig, + userConfig: vite.UserConfig, + viteEnv: vite.ConfigEnv +): ResolvedPluginConfig { + const configPaths = new Set(); + const persistState = pluginConfig.persistState ?? true; + const root = userConfig.root ? path.resolve(userConfig.root) : process.cwd(); + const { CLOUDFLARE_ENV } = vite.loadEnv(viteEnv.mode, root, ""); + + const configPath = pluginConfig.configPath + ? path.resolve(root, pluginConfig.configPath) + : findWranglerConfig(root); + + assert( + configPath, + `Config not found. Have you created a wrangler.json(c) or wrangler.toml file?` + ); + + const entryWorkerResolvedConfig = getWorkerConfig( + configPath, + CLOUDFLARE_ENV, + { + visitedConfigPaths: configPaths, + isEntryWorker: true, + } + ); + + if (entryWorkerResolvedConfig.type === "assets-only") { + return { + type: "assets-only", + config: entryWorkerResolvedConfig.config, + configPaths, + persistState, + rawConfigs: { + entryWorker: entryWorkerResolvedConfig, + }, + }; + } + + const entryWorkerConfig = entryWorkerResolvedConfig.config; + + const entryWorkerEnvironmentName = + pluginConfig.viteEnvironment?.name ?? + workerNameToEnvironmentName(entryWorkerConfig.topLevelName); + + const workers = { + [entryWorkerEnvironmentName]: entryWorkerConfig, + }; + + const auxiliaryWorkersResolvedConfigs: WorkerResolvedConfig[] = []; + + for (const auxiliaryWorker of pluginConfig.auxiliaryWorkers ?? []) { + const workerResolvedConfig = getWorkerConfig( + path.resolve(root, auxiliaryWorker.configPath), + CLOUDFLARE_ENV, + { + visitedConfigPaths: configPaths, + } + ); + + auxiliaryWorkersResolvedConfigs.push(workerResolvedConfig); + + assert( + workerResolvedConfig.type === "worker", + "Unexpected error: received AssetsOnlyResult with auxiliary workers." + ); + + const workerConfig = workerResolvedConfig.config; + + const workerEnvironmentName = + auxiliaryWorker.viteEnvironment?.name ?? + workerNameToEnvironmentName(workerConfig.topLevelName); + + if (workers[workerEnvironmentName]) { + throw new Error( + `Duplicate Vite environment name found: ${workerEnvironmentName}` + ); + } + + workers[workerEnvironmentName] = workerConfig; + } + + return { + type: "workers", + configPaths, + persistState, + workers, + entryWorkerEnvironmentName, + rawConfigs: { + entryWorker: entryWorkerResolvedConfig, + auxiliaryWorkers: auxiliaryWorkersResolvedConfigs, + }, + }; +} diff --git a/packages/vite-plugin-cloudflare/src/runner-worker/__tests__/env.spec.ts b/packages/vite-plugin-cloudflare/src/runner-worker/__tests__/env.spec.ts new file mode 100644 index 0000000000000..66d4833c1433a --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/runner-worker/__tests__/env.spec.ts @@ -0,0 +1,65 @@ +import { describe, expect, test } from "vitest"; +import { stripInternalEnv } from "../env"; + +describe("stripInternalEnv", () => { + test("only __VITE internal fields", () => { + const env = { + __VITE_ROOT__: "", + __VITE_ENTRY_PATH__: "", + __VITE_INVOKE_MODULE__: { + fetch: async () => new Response(), + }, + __VITE_UNSAFE_EVAL__: { + eval: () => () => {}, + }, + }; + const result = stripInternalEnv(env); + expect(result).toEqual({}); + }); + + test("with extra env fields", () => { + const env = { + __VITE_ROOT__: "", + __VITE_ENTRY_PATH__: "", + __VITE_INVOKE_MODULE__: { + fetch: async () => new Response(), + }, + __VITE_UNSAFE_EVAL__: { + eval: () => () => {}, + }, + test: "this is a test", + test1: "this is a test (1)", + MY_KV: {}, + }; + const result = stripInternalEnv(env); + expect(result).toEqual({ + MY_KV: {}, + test: "this is a test", + test1: "this is a test (1)", + }); + }); + + test("with nested fields that share the same name as (top level) internal ones", () => { + const env = { + __VITE_ROOT__: "", + __VITE_ENTRY_PATH__: "", + __VITE_INVOKE_MODULE__: { + fetch: async () => new Response(), + }, + __VITE_UNSAFE_EVAL__: { + eval: () => () => {}, + }, + myJson: { + __VITE_ROOT__: "", + __VITE_ENTRY_PATH__: "", + }, + }; + const result = stripInternalEnv(env); + expect(result).toEqual({ + myJson: { + __VITE_ENTRY_PATH__: "", + __VITE_ROOT__: "", + }, + }); + }); +}); diff --git a/packages/vite-plugin-cloudflare/src/runner-worker/env.ts b/packages/vite-plugin-cloudflare/src/runner-worker/env.ts new file mode 100644 index 0000000000000..32eea0047b4e7 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/runner-worker/env.ts @@ -0,0 +1,23 @@ +export interface WrapperEnv { + __VITE_ROOT__: string; + __VITE_ENTRY_PATH__: string; + __VITE_INVOKE_MODULE__: { + fetch: (request: Request) => Promise; + }; + __VITE_UNSAFE_EVAL__: { + eval: (code: string, filename: string) => Function; + }; + [key: string]: unknown; +} + +export function stripInternalEnv(internalEnv: WrapperEnv) { + const { + __VITE_ROOT__, + __VITE_ENTRY_PATH__, + __VITE_INVOKE_MODULE__, + __VITE_UNSAFE_EVAL__, + ...userEnv + } = internalEnv; + + return userEnv; +} diff --git a/packages/vite-plugin-cloudflare/src/runner-worker/index.ts b/packages/vite-plugin-cloudflare/src/runner-worker/index.ts new file mode 100644 index 0000000000000..8ae08fe0fd82e --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/runner-worker/index.ts @@ -0,0 +1,382 @@ +import { + DurableObject, + WorkerEntrypoint, + WorkflowEntrypoint, +} from "cloudflare:workers"; +import { INIT_PATH } from "../shared"; +import { stripInternalEnv } from "./env"; +import { createModuleRunner, getWorkerEntryExport } from "./module-runner"; +import type { WrapperEnv } from "./env"; + +interface WorkerEntrypointConstructor { + new ( + ...args: ConstructorParameters> + ): WorkerEntrypoint; +} + +interface DurableObjectConstructor { + new ( + ...args: ConstructorParameters> + ): DurableObject; +} + +interface WorkflowEntrypointConstructor { + new ( + // Constructor type to be added in https://github.com/cloudflare/workerd/pull/3239 + // ...args: ConstructorParameters> + ctx: ExecutionContext, + env: T + ): WorkflowEntrypoint; +} + +const WORKER_ENTRYPOINT_KEYS = [ + "fetch", + "tail", + "trace", + "scheduled", + "queue", + "test", +] as const; + +const DURABLE_OBJECT_KEYS = [ + "fetch", + "alarm", + "webSocketMessage", + "webSocketClose", + "webSocketError", +] as const; + +const WORKFLOW_ENTRYPOINT_KEYS = ["run"] as const; + +function getRpcProperty( + ctor: WorkerEntrypointConstructor | DurableObjectConstructor, + instance: WorkerEntrypoint | DurableObject, + key: string +): unknown { + const prototypeHasKey = Reflect.has(ctor.prototype, key); + + if (!prototypeHasKey) { + const instanceHasKey = Reflect.has(instance, key); + + if (instanceHasKey) { + throw new TypeError( + [ + `The RPC receiver's prototype does not implement '${key}', but the receiver instance does.`, + "Only properties and methods defined on the prototype can be accessed over RPC.", + `Ensure properties are declared as \`get ${key}() { ... }\` instead of \`${key} = ...\`,`, + `and methods are declared as \`${key}() { ... }\` instead of \`${key} = () => { ... }\`.`, + ].join("\n") + ); + } + + throw new TypeError(`The RPC receiver does not implement '${key}'.`); + } + + return Reflect.get(ctor.prototype, key, instance); +} + +function getRpcPropertyCallableThenable( + key: string, + property: Promise +): Promise & ((...args: unknown[]) => Promise) { + const fn = async function (...args: unknown[]) { + const maybeFn = await property; + + if (typeof maybeFn !== "function") { + throw new TypeError(`'${key}' is not a function.`); + } + + return maybeFn(...args); + } as Promise & ((...args: unknown[]) => Promise); + + fn.then = (onFulfilled, onRejected) => property.then(onFulfilled, onRejected); + fn.catch = (onRejected) => property.catch(onRejected); + fn.finally = (onFinally) => property.finally(onFinally); + + return fn; +} + +async function getWorkerEntrypointRpcProperty( + this: WorkerEntrypoint, + entrypoint: string, + key: string +): Promise { + const entryPath = this.env.__VITE_ENTRY_PATH__; + const ctor = (await getWorkerEntryExport( + entryPath, + entrypoint + )) as WorkerEntrypointConstructor; + const userEnv = stripInternalEnv(this.env); + const expectedWorkerEntrypointMessage = `Expected ${entrypoint} export of ${entryPath} to be a subclass of \`WorkerEntrypoint\` for RPC.`; + + if (typeof ctor !== "function") { + throw new TypeError(expectedWorkerEntrypointMessage); + } + + const instance = new ctor(this.ctx, userEnv); + + if (!(instance instanceof WorkerEntrypoint)) { + throw new TypeError(expectedWorkerEntrypointMessage); + } + + const value = getRpcProperty(ctor, instance, key); + + if (typeof value === "function") { + return value.bind(instance); + } + + return value; +} + +export function createWorkerEntrypointWrapper( + entrypoint: string +): WorkerEntrypointConstructor { + class Wrapper extends WorkerEntrypoint { + constructor(ctx: ExecutionContext, env: WrapperEnv) { + super(ctx, env); + + return new Proxy(this, { + get(target, key, receiver) { + const value = Reflect.get(target, key, receiver); + + if (value !== undefined) { + return value; + } + + if ( + key === "self" || + typeof key === "symbol" || + (DURABLE_OBJECT_KEYS as readonly string[]).includes(key) + ) { + return; + } + + const property = getWorkerEntrypointRpcProperty.call( + receiver, + entrypoint, + key + ); + + return getRpcPropertyCallableThenable(key, property); + }, + }); + } + } + + for (const key of WORKER_ENTRYPOINT_KEYS) { + Wrapper.prototype[key] = async function (arg) { + const entryPath = this.env.__VITE_ENTRY_PATH__; + + if (key === "fetch") { + const request = arg as Request; + const url = new URL(request.url); + + if (url.pathname === INIT_PATH) { + const { 0: client, 1: server } = new WebSocketPair(); + createModuleRunner(this.env, server); + + return new Response(null, { status: 101, webSocket: client }); + } + } + + const entrypointValue = await getWorkerEntryExport(entryPath, entrypoint); + const userEnv = stripInternalEnv(this.env); + + if (typeof entrypointValue === "object" && entrypointValue !== null) { + // ExportedHandler + const maybeFn = (entrypointValue as Record)[key]; + + if (typeof maybeFn !== "function") { + throw new TypeError( + `Expected ${entrypoint} export of ${entryPath} to define a \`${key}()\` function.` + ); + } + + return maybeFn.call(entrypointValue, arg, userEnv, this.ctx); + } else if (typeof entrypointValue === "function") { + // WorkerEntrypoint + const ctor = entrypointValue as WorkerEntrypointConstructor; + const instance = new ctor(this.ctx, userEnv); + + if (!(instance instanceof WorkerEntrypoint)) { + throw new TypeError( + `Expected ${entrypoint} export of ${entryPath} to be a subclass of \`WorkerEntrypoint\`.` + ); + } + + const maybeFn = instance[key]; + + if (typeof maybeFn !== "function") { + throw new TypeError( + `Expected ${entrypoint} export of ${entryPath} to define a \`${key}()\` method.` + ); + } + + return (maybeFn as (arg: unknown) => unknown).call(instance, arg); + } else { + return new TypeError( + `Expected ${entrypoint} export of ${entryPath} to be an object or a class. Got ${entrypointValue}.` + ); + } + }; + } + + return Wrapper; +} + +const kInstance = Symbol("kInstance"); +const kEnsureInstance = Symbol("kEnsureInstance"); + +interface DurableObjectInstance { + ctor: DurableObjectConstructor; + instance: DurableObject; +} + +interface DurableObjectWrapper extends DurableObject { + [kInstance]?: DurableObjectInstance; + [kEnsureInstance](): Promise; +} + +async function getDurableObjectRpcProperty( + this: DurableObjectWrapper, + className: string, + key: string +): Promise { + const entryPath = this.env.__VITE_ENTRY_PATH__; + const { ctor, instance } = await this[kEnsureInstance](); + + if (!(instance instanceof DurableObject)) { + throw new TypeError( + `Expected ${className} export of ${entryPath} to be a subclass of \`DurableObject\` for RPC.` + ); + } + + const value = getRpcProperty(ctor, instance, key); + + if (typeof value === "function") { + return value.bind(instance); + } + + return value; +} + +export function createDurableObjectWrapper( + className: string +): DurableObjectConstructor { + class Wrapper + extends DurableObject + implements DurableObjectWrapper + { + [kInstance]?: DurableObjectInstance; + + constructor(ctx: DurableObjectState, env: WrapperEnv) { + super(ctx, env); + + return new Proxy(this, { + get(target, key, receiver) { + const value = Reflect.get(target, key, receiver); + + if (value !== undefined) { + return value; + } + + if ( + key === "self" || + typeof key === "symbol" || + (WORKER_ENTRYPOINT_KEYS as readonly string[]).includes(key) + ) { + return; + } + + const property = getDurableObjectRpcProperty.call( + receiver, + className, + key + ); + + return getRpcPropertyCallableThenable(key, property); + }, + }); + } + + async [kEnsureInstance]() { + const entryPath = this.env.__VITE_ENTRY_PATH__; + const ctor = (await getWorkerEntryExport( + entryPath, + className + )) as DurableObjectConstructor; + + if (typeof ctor !== "function") { + throw new TypeError( + `${entryPath} does not export a ${className} Durable Object.` + ); + } + + if (!this[kInstance] || this[kInstance].ctor !== ctor) { + const userEnv = stripInternalEnv(this.env); + const instance = new ctor(this.ctx, userEnv); + + this[kInstance] = { ctor, instance }; + + // Wait for `blockConcurrencyWhile()`s in the constructor to complete + await this.ctx.blockConcurrencyWhile(async () => {}); + } + + return this[kInstance]; + } + } + + for (const key of DURABLE_OBJECT_KEYS) { + Wrapper.prototype[key] = async function (...args: unknown[]) { + const entryPath = this.env.__VITE_ENTRY_PATH__; + const { instance } = await this[kEnsureInstance](); + const maybeFn = instance[key]; + + if (typeof maybeFn !== "function") { + throw new TypeError( + `Expected ${className} export of ${entryPath} to define a \`${key}()\` function.` + ); + } + + return (maybeFn as (...args: unknown[]) => any).apply(instance, args); + }; + } + + return Wrapper; +} + +export function createWorkflowEntrypointWrapper( + className: string +): WorkflowEntrypointConstructor { + class Wrapper extends WorkflowEntrypoint {} + + for (const key of WORKFLOW_ENTRYPOINT_KEYS) { + Wrapper.prototype[key] = async function (...args: unknown[]) { + const entryPath = this.env.__VITE_ENTRY_PATH__; + const ctor = (await getWorkerEntryExport( + entryPath, + className + )) as WorkflowEntrypointConstructor; + const userEnv = stripInternalEnv(this.env); + const instance = new ctor(this.ctx, userEnv); + + if (!(instance instanceof WorkflowEntrypoint)) { + throw new TypeError( + `Expected ${className} export of ${entryPath} to be a subclass of \`WorkflowEntrypoint\`.` + ); + } + + const maybeFn = instance[key]; + + if (typeof maybeFn !== "function") { + throw new TypeError( + `Expected ${className} export of ${entryPath} to define a \`${key}()\` function.` + ); + } + + return (maybeFn as (...args: unknown[]) => any).apply(instance, args); + }; + } + + return Wrapper; +} diff --git a/packages/vite-plugin-cloudflare/src/runner-worker/module-runner.ts b/packages/vite-plugin-cloudflare/src/runner-worker/module-runner.ts new file mode 100644 index 0000000000000..6637fa8af9fe2 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/runner-worker/module-runner.ts @@ -0,0 +1,106 @@ +import { + createWebSocketModuleRunnerTransport, + ModuleRunner, +} from "vite/module-runner"; +import { UNKNOWN_HOST } from "../shared"; +import type { WrapperEnv } from "./env"; + +let moduleRunner: ModuleRunner; + +export async function createModuleRunner( + env: WrapperEnv, + webSocket: WebSocket +) { + if (moduleRunner) { + throw new Error("Runner already initialized"); + } + + const transport = createWebSocketModuleRunnerTransport({ + createConnection() { + webSocket.accept(); + + return webSocket; + }, + }); + + moduleRunner = new ModuleRunner( + { + root: env.__VITE_ROOT__, + sourcemapInterceptor: "prepareStackTrace", + transport: { + ...transport, + async invoke(data) { + const response = await env.__VITE_INVOKE_MODULE__.fetch( + new Request(UNKNOWN_HOST, { + method: "POST", + body: JSON.stringify(data), + }) + ); + + if (!response.ok) { + throw new Error(await response.text()); + } + + const result = await response.json(); + + return result as { result: any } | { error: any }; + }, + }, + hmr: true, + }, + { + async runInlinedModule(context, transformed, module) { + if ( + module.file.includes("/node_modules") && + !module.file.includes("/node_modules/.vite") + ) { + throw new Error( + `[Error] Trying to import non-prebundled module (only prebundled modules are allowed): ${module.id}` + + "\n\n(have you excluded the module via `optimizeDeps.exclude`?)" + ); + } + const codeDefinition = `'use strict';async (${Object.keys(context).join( + "," + )})=>{{`; + const code = `${codeDefinition}${transformed}\n}}`; + try { + const fn = env.__VITE_UNSAFE_EVAL__.eval(code, module.id); + await fn(...Object.values(context)); + Object.freeze(context.__vite_ssr_exports__); + } catch (e) { + console.error("error running", module.id); + console.error(e instanceof Error ? e.stack : e); + throw e; + } + }, + async runExternalModule(filepath) { + if ( + filepath.includes("/node_modules") && + !filepath.includes("/node_modules/.vite") + ) { + throw new Error( + `[Error] Trying to import non-prebundled module (only prebundled modules are allowed): ${filepath}` + + "\n\n(have you externalized the module via `resolve.external`?)" + ); + } + filepath = filepath.replace(/^file:\/\//, ""); + return import(filepath); + }, + } + ); +} + +export async function getWorkerEntryExport(path: string, entrypoint: string) { + const module = await moduleRunner.import(path); + const entrypointValue = + typeof module === "object" && + module !== null && + entrypoint in module && + module[entrypoint]; + + if (!entrypointValue) { + throw new Error(`${path} does not export a ${entrypoint} entrypoint.`); + } + + return entrypointValue; +} diff --git a/packages/vite-plugin-cloudflare/src/shared.ts b/packages/vite-plugin-cloudflare/src/shared.ts new file mode 100644 index 0000000000000..720f66bb7059c --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/shared.ts @@ -0,0 +1,2 @@ +export const UNKNOWN_HOST = "http://localhost"; +export const INIT_PATH = "/__vite_plugin_cloudflare_init__"; diff --git a/packages/vite-plugin-cloudflare/src/utils.ts b/packages/vite-plugin-cloudflare/src/utils.ts new file mode 100644 index 0000000000000..61b7382237f01 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/utils.ts @@ -0,0 +1,45 @@ +import * as path from "node:path"; +import { Request as MiniflareRequest } from "miniflare"; +import * as vite from "vite"; +import type { IncomingHttpHeaders } from "node:http"; + +export function getOutputDirectory( + userConfig: vite.UserConfig, + environmentName: string +) { + const rootOutputDirectory = userConfig.build?.outDir ?? "dist"; + + return ( + userConfig.environments?.[environmentName]?.build?.outDir ?? + path.join(rootOutputDirectory, environmentName) + ); +} + +export function toMiniflareRequest(request: Request): MiniflareRequest { + return new MiniflareRequest(request.url, { + method: request.method, + headers: [["accept-encoding", "identity"], ...request.headers], + body: request.body, + duplex: "half", + }); +} + +export function nodeHeadersToWebHeaders( + nodeHeaders: IncomingHttpHeaders +): Headers { + const headers = new Headers(); + + for (const [key, value] of Object.entries(nodeHeaders)) { + if (typeof value === "string") { + headers.append(key, value); + } else if (Array.isArray(value)) { + for (const item of value) { + headers.append(key, item); + } + } + } + + return headers; +} + +export type Optional = Omit & Pick, K>; diff --git a/packages/vite-plugin-cloudflare/src/websockets.ts b/packages/vite-plugin-cloudflare/src/websockets.ts new file mode 100644 index 0000000000000..f3d9c3a8a85bd --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/websockets.ts @@ -0,0 +1,81 @@ +import ws from "ws"; +import { UNKNOWN_HOST } from "./shared"; +import { nodeHeadersToWebHeaders } from "./utils"; +import type { Fetcher } from "@cloudflare/workers-types/experimental"; +import type { ReplaceWorkersTypes } from "miniflare"; +import type { IncomingMessage } from "node:http"; +import type { Duplex } from "node:stream"; +import type * as vite from "vite"; + +/** + * This function handles 'upgrade' requests to the Vite HTTP server and forwards WebSocket events between the client and Worker environments. + */ +export function handleWebSocket( + httpServer: vite.HttpServer, + fetcher: ReplaceWorkersTypes["fetch"], + logger: vite.Logger +) { + const nodeWebSocket = new ws.Server({ noServer: true }); + + httpServer.on( + "upgrade", + async (request: IncomingMessage, socket: Duplex, head: Buffer) => { + const url = new URL(request.url ?? "", UNKNOWN_HOST); + + // Ignore Vite HMR WebSockets + if (request.headers["sec-websocket-protocol"]?.startsWith("vite")) { + return; + } + + const headers = nodeHeadersToWebHeaders(request.headers); + const response = await fetcher(url, { + headers, + method: request.method, + }); + const workerWebSocket = response.webSocket; + + if (!workerWebSocket) { + socket.destroy(); + return; + } + + nodeWebSocket.handleUpgrade( + request, + socket, + head, + async (clientWebSocket) => { + workerWebSocket.accept(); + + // Forward Worker events to client + workerWebSocket.addEventListener("message", (event) => { + clientWebSocket.send(event.data); + }); + workerWebSocket.addEventListener("error", (event) => { + logger.error( + `WebSocket error:\n${event.error?.stack || event.error?.message}`, + { error: event.error } + ); + }); + workerWebSocket.addEventListener("close", () => { + clientWebSocket.close(); + }); + + // Forward client events to Worker + clientWebSocket.on("message", (event: ArrayBuffer | string) => { + workerWebSocket.send(event); + }); + clientWebSocket.on("error", (error) => { + logger.error(`WebSocket error:\n${error.stack || error.message}`, { + error, + }); + }); + clientWebSocket.on("close", () => { + workerWebSocket.close(); + }); + + nodeWebSocket.emit("connection", clientWebSocket, request); + } + ); + } + ); +} diff --git a/packages/vite-plugin-cloudflare/src/workers-configs.ts b/packages/vite-plugin-cloudflare/src/workers-configs.ts new file mode 100644 index 0000000000000..62953b60474a9 --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/workers-configs.ts @@ -0,0 +1,382 @@ +import assert from "node:assert"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import { unstable_readConfig } from "wrangler"; +import { name as packageName } from "../package.json"; +import type { AssetsOnlyConfig, WorkerConfig } from "./plugin-config"; +import type { Optional } from "./utils"; +import type { Unstable_Config as RawWorkerConfig } from "wrangler"; + +export type WorkerResolvedConfig = + | AssetsOnlyWorkerResolvedConfig + | WorkerWithServerLogicResolvedConfig; + +export interface AssetsOnlyWorkerResolvedConfig + extends WorkerBaseResolvedConfig { + type: "assets-only"; + config: AssetsOnlyConfig; +} + +export interface WorkerWithServerLogicResolvedConfig + extends WorkerBaseResolvedConfig { + type: "worker"; + config: WorkerConfig; +} + +interface WorkerBaseResolvedConfig { + raw: RawWorkerConfig; + nonApplicable: NonApplicableConfigMap; +} + +export type SanitizedWorkerConfig = Omit< + RawWorkerConfig, + keyof NonApplicableConfig +>; + +type NonApplicableConfigMap = { + replacedByVite: Set< + Extract< + keyof RawWorkerConfig, + keyof NonApplicableWorkerConfigsInfo["replacedByVite"] + > + >; + notRelevant: Set< + Extract< + keyof RawWorkerConfig, + NonApplicableWorkerConfigsInfo["notRelevant"][number] + > + >; + overridden: Set< + Extract< + keyof RawWorkerConfig, + NonApplicableWorkerConfigsInfo["overridden"][number] + > + >; +}; + +type NonApplicableWorkerConfigsInfo = typeof nonApplicableWorkerConfigs; + +type NonApplicableConfig = + | NonApplicableConfigReplacedByVite + | NonApplicableConfigNotRelevant + | NonApplicableConfigOverridden; + +type NonApplicableConfigReplacedByVite = + keyof NonApplicableWorkerConfigsInfo["replacedByVite"]; + +type NonApplicableConfigNotRelevant = + NonApplicableWorkerConfigsInfo["notRelevant"][number]; + +type NonApplicableConfigOverridden = + NonApplicableWorkerConfigsInfo["overridden"][number]; + +/** + * Set of worker config options that are not applicable when using Vite + */ +export const nonApplicableWorkerConfigs = { + /** + * Object containing configs that have a vite replacement, the object's field contain details about the config's replacement + */ + replacedByVite: { + alias: { + viteReplacement: "resolve.alias", + viteDocs: "https://vite.dev/config/shared-options.html#resolve-alias", + }, + define: { + viteReplacement: "define", + viteDocs: "https://vite.dev/config/shared-options.html#define", + }, + minify: { + viteReplacement: "build.minify", + viteDocs: "https://vite.dev/config/build-options.html#build-minify", + }, + }, + /** + * All the configs that are not relevant when using Vite (meaning that in the context of a Vite + * application they lose their purpose/meaning) + */ + notRelevant: [ + "base_dir", + "build", + "find_additional_modules", + "no_bundle", + "node_compat", + "preserve_file_names", + "site", + "tsconfig", + "upload_source_maps", + ], + /** + * All the configs that get overridden by our plugin + */ + overridden: ["rules"], +} as const; + +/** + * The non applicable configs that can be and default to `undefined` + */ +const nullableNonApplicable = [ + "alias", + "base_dir", + "find_additional_modules", + "minify", + "no_bundle", + "node_compat", + "preserve_file_names", + "site", + "tsconfig", + "upload_source_maps", +] as const; + +function readWorkerConfig( + configPath: string, + env: string | undefined +): { + raw: RawWorkerConfig; + config: SanitizedWorkerConfig; + nonApplicable: NonApplicableConfigMap; +} { + const nonApplicable: NonApplicableConfigMap = { + replacedByVite: new Set(), + notRelevant: new Set(), + overridden: new Set(), + }; + const config: Optional = + unstable_readConfig({ config: configPath, env }, {}); + const raw = structuredClone(config) as RawWorkerConfig; + + nullableNonApplicable.forEach((prop) => { + if (config[prop] !== undefined) { + if (isReplacedByVite(prop)) { + nonApplicable.replacedByVite.add(prop); + } + + if (isNotRelevant(prop)) { + nonApplicable.notRelevant.add(prop); + } + + if (isOverridden(prop)) { + nonApplicable.overridden.add(prop); + } + } + delete config[prop]; + }); + + // config.build is always defined as an object and by default it has the `command` and `cwd` fields + // set to `undefined` but the `watch_dir` field set to `"./src"`, so to check if the user set it + // we need to check `command` and `cwd` + if (config.build && (config.build.command || config.build.cwd)) { + nonApplicable.notRelevant.add("build"); + } + delete config["build"]; + + if (config.define && Object.keys(config.define).length > 0) { + nonApplicable.replacedByVite.add("define"); + } + delete config["define"]; + + if (config.rules.length > 0) { + nonApplicable.overridden.add("rules"); + } + // Note: differently from above here we cannot delete `config['rules']` since Miniflare relies on this config being there + + return { + raw, + nonApplicable, + config: config as SanitizedWorkerConfig, + }; +} + +export function getWarningForWorkersConfigs( + configs: + | { + entryWorker: AssetsOnlyWorkerResolvedConfig; + } + | { + entryWorker: WorkerWithServerLogicResolvedConfig; + auxiliaryWorkers: WorkerResolvedConfig[]; + } +): string | undefined { + if ( + !("auxiliaryWorkers" in configs) || + configs.auxiliaryWorkers.length === 0 + ) { + const nonApplicableLines = getWorkerNonApplicableWarnLines( + configs.entryWorker, + ` - ` + ); + + if (nonApplicableLines.length === 0) { + return; + } + + const lines = [ + `\n\n\x1b[43mWARNING\x1b[0m: your worker config${configs.entryWorker.config.configPath ? ` (at \`${path.relative("", configs.entryWorker.config.configPath)}\`)` : ""} contains` + + " the following configuration options which are ignored since they are not applicable when using Vite:", + ]; + + nonApplicableLines.forEach((line) => lines.push(line)); + + lines.push(""); + return lines.join("\n"); + } + + const lines: string[] = []; + + const processWorkerConfig = ( + workerConfig: WorkerResolvedConfig, + isEntryWorker = false + ) => { + const nonApplicableLines = getWorkerNonApplicableWarnLines( + workerConfig, + ` - ` + ); + + if (nonApplicableLines.length > 0) { + lines.push( + ` - (${isEntryWorker ? "entry" : "auxiliary"}) worker${workerConfig.config.name ? ` "${workerConfig.config.name}"` : ""}${workerConfig.config.configPath ? ` (config at \`${path.relative("", workerConfig.config.configPath)}\`)` : ""}` + ); + nonApplicableLines.forEach((line) => lines.push(line)); + } + }; + + processWorkerConfig(configs.entryWorker, true); + configs.auxiliaryWorkers.forEach((config) => processWorkerConfig(config)); + + if (lines.length === 0) { + return; + } + + return [ + "\n\x1b[43mWARNING\x1b[0m: your workers configs contain configuration options which are ignored since they are not applicable when using Vite:", + ...lines, + "", + ].join("\n"); +} + +function getWorkerNonApplicableWarnLines( + workerConfig: WorkerResolvedConfig, + linePrefix: string +): string[] { + const lines: string[] = []; + + const { replacedByVite, notRelevant, overridden } = + workerConfig.nonApplicable; + + for (const config of replacedByVite) { + lines.push( + `${linePrefix}\`${config}\` which is replaced by Vite's \`${nonApplicableWorkerConfigs.replacedByVite[config].viteReplacement}\` (docs: ${nonApplicableWorkerConfigs.replacedByVite[config].viteDocs})` + ); + } + + if (notRelevant.size > 0) + lines.push( + `${linePrefix}${[...notRelevant].map((config) => `\`${config}\``).join(", ")} which ${notRelevant.size > 1 ? "are" : "is"} not relevant in the context of a Vite project` + ); + + if (overridden.size > 0) + lines.push( + `${linePrefix}${[...overridden].map((config) => `\`${config}\``).join(", ")} which ${overridden.size > 1 ? "are" : "is"} overridden by \`${packageName}\`` + ); + + return lines; +} + +function isReplacedByVite( + configName: string +): configName is NonApplicableConfigReplacedByVite { + return configName in nonApplicableWorkerConfigs["replacedByVite"]; +} + +function isNotRelevant( + configName: string +): configName is NonApplicableConfigNotRelevant { + return nonApplicableWorkerConfigs.notRelevant.includes(configName as any); +} + +function isOverridden( + configName: string +): configName is NonApplicableConfigOverridden { + return nonApplicableWorkerConfigs.overridden.includes(configName as any); +} + +function missingFieldErrorMessage( + field: string, + configPath: string, + env: string | undefined +) { + return `No ${field} field provided in '${configPath}'${env ? ` for '${env}' environment` : ""}`; +} + +export function getWorkerConfig( + configPath: string, + env: string | undefined, + opts?: { + visitedConfigPaths?: Set; + isEntryWorker?: boolean; + } +): WorkerResolvedConfig { + if (opts?.visitedConfigPaths?.has(configPath)) { + throw new Error(`Duplicate Wrangler config path found: ${configPath}`); + } + + const { raw, config, nonApplicable } = readWorkerConfig(configPath, env); + + opts?.visitedConfigPaths?.add(configPath); + + assert( + config.topLevelName, + missingFieldErrorMessage(`top-level 'name'`, configPath, env) + ); + assert(config.name, missingFieldErrorMessage(`'name'`, configPath, env)); + assert( + config.compatibility_date, + missingFieldErrorMessage(`'compatibility_date'`, configPath, env) + ); + + if (opts?.isEntryWorker && !config.main) { + assert( + config.assets, + missingFieldErrorMessage(`'main' or 'assets'`, configPath, env) + ); + + return { + type: "assets-only", + raw, + config: { + ...config, + topLevelName: config.topLevelName, + name: config.name, + compatibility_date: config.compatibility_date, + assets: config.assets, + }, + nonApplicable, + }; + } + + assert(config.main, missingFieldErrorMessage(`'main'`, configPath, env)); + + return { + type: "worker", + raw, + config: { + ...config, + topLevelName: config.topLevelName, + name: config.name, + compatibility_date: config.compatibility_date, + main: config.main, + }, + nonApplicable, + }; +} + +// We can't rely on `readConfig` from Wrangler to find the config as it may be relative to a different root that's set by the user. +export function findWranglerConfig(root: string): string | undefined { + for (const extension of ["json", "jsonc", "toml"]) { + const configPath = path.join(root, `wrangler.${extension}`); + + if (fs.existsSync(configPath)) { + return configPath; + } + } +} diff --git a/packages/vite-plugin-cloudflare/tsconfig.json b/packages/vite-plugin-cloudflare/tsconfig.json new file mode 100644 index 0000000000000..01afc3500432d --- /dev/null +++ b/packages/vite-plugin-cloudflare/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.plugin.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/tsconfig.plugin.json b/packages/vite-plugin-cloudflare/tsconfig.plugin.json new file mode 100644 index 0000000000000..fe547e7a873dd --- /dev/null +++ b/packages/vite-plugin-cloudflare/tsconfig.plugin.json @@ -0,0 +1,9 @@ +{ + "extends": "@cloudflare/workers-tsconfig/base.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true + }, + "include": ["src"], + "exclude": ["src/asset-workers", "src/runner-worker"] +} diff --git a/packages/vite-plugin-cloudflare/tsconfig.worker.json b/packages/vite-plugin-cloudflare/tsconfig.worker.json new file mode 100644 index 0000000000000..ae475b258f97d --- /dev/null +++ b/packages/vite-plugin-cloudflare/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": "@cloudflare/workers-tsconfig/worker.json", + "include": ["src/asset-workers", "src/runner-worker"] +} diff --git a/packages/vite-plugin-cloudflare/tsup.config.ts b/packages/vite-plugin-cloudflare/tsup.config.ts new file mode 100644 index 0000000000000..84b48e87f4f6c --- /dev/null +++ b/packages/vite-plugin-cloudflare/tsup.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from "tsup"; + +export default defineConfig([ + { + entry: ["src/index.ts"], + format: "esm", + platform: "node", + dts: true, + outDir: "dist", + tsconfig: "tsconfig.plugin.json", + }, + { + entry: [ + "src/asset-workers/router-worker.ts", + "src/asset-workers/asset-worker.ts", + ], + format: "esm", + platform: "neutral", + outDir: "dist/asset-workers", + external: ["cloudflare:workers"], + tsconfig: "tsconfig.worker.json", + }, + { + entry: ["src/runner-worker/index.ts"], + format: "esm", + platform: "neutral", + outDir: "dist/runner-worker", + external: ["cloudflare:workers"], + noExternal: ["vite/module-runner"], + tsconfig: "tsconfig.worker.json", + }, +]); diff --git a/packages/vite-plugin-cloudflare/turbo.json b/packages/vite-plugin-cloudflare/turbo.json new file mode 100644 index 0000000000000..6556dcf3e5e54 --- /dev/null +++ b/packages/vite-plugin-cloudflare/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/vitest.config.ts b/packages/vite-plugin-cloudflare/vitest.config.ts new file mode 100644 index 0000000000000..383052838f1cd --- /dev/null +++ b/packages/vite-plugin-cloudflare/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["**/__tests__/**/*.spec.[tj]s"], + exclude: ["**/node_modules/**", "**/dist/**", "./playground/**/*.*"], + }, + publicDir: false, +}); diff --git a/packages/workers-playground/package.json b/packages/workers-playground/package.json index d7820bb76614b..5b6a0fafb684b 100644 --- a/packages/workers-playground/package.json +++ b/packages/workers-playground/package.json @@ -53,6 +53,7 @@ "eslint": "^8.49.0", "tsx": "^3.12.8", "undici": "catalog:default", + "vite": "catalog:default", "wrangler": "workspace:^" }, "volta": { diff --git a/packages/workers-tsconfig/base.json b/packages/workers-tsconfig/base.json new file mode 100644 index 0000000000000..57f942a7e5903 --- /dev/null +++ b/packages/workers-tsconfig/base.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "esModuleInterop": false, + "skipLibCheck": true, + "target": "ES2022", + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "module": "Preserve", + "lib": ["ES2022"], + "noEmit": true + } +} diff --git a/packages/workers-tsconfig/client.json b/packages/workers-tsconfig/client.json new file mode 100644 index 0000000000000..175a30be6bf56 --- /dev/null +++ b/packages/workers-tsconfig/client.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"] + } +} diff --git a/packages/workers-tsconfig/react.json b/packages/workers-tsconfig/react.json new file mode 100644 index 0000000000000..7c9454c34c281 --- /dev/null +++ b/packages/workers-tsconfig/react.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./client.json", + "compilerOptions": { + "allowImportingTsExtensions": true, + "jsx": "react-jsx" + } +} diff --git a/packages/workers-tsconfig/worker-node-compat.json b/packages/workers-tsconfig/worker-node-compat.json new file mode 100644 index 0000000000000..481b4253e0afc --- /dev/null +++ b/packages/workers-tsconfig/worker-node-compat.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "types": [ + "@cloudflare/workers-types/experimental", + "vite/client", + "@types/node" + ] + } +} diff --git a/packages/workers-tsconfig/worker.json b/packages/workers-tsconfig/worker.json new file mode 100644 index 0000000000000..9f14628efe31d --- /dev/null +++ b/packages/workers-tsconfig/worker.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "types": ["@cloudflare/workers-types/experimental", "vite/client"] + } +} diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index 38ce93d13f2ad..dbed25d882bdf 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -100,6 +100,7 @@ "@types/javascript-time-ago": "^2.0.3", "@types/mime": "^3.0.4", "@types/minimatch": "^5.1.2", + "@types/node": "catalog:default", "@types/prompts": "^2.0.14", "@types/resolve": "^1.20.6", "@types/shell-quote": "^1.7.2", @@ -149,6 +150,7 @@ "timeago.js": "^4.0.2", "ts-dedent": "^2.2.0", "ts-json-schema-generator": "^1.5.0", + "typescript": "catalog:default", "undici": "catalog:default", "update-check": "^1.5.4", "vitest": "catalog:default", diff --git a/packages/wrangler/src/__tests__/helpers/msw/handlers/script.ts b/packages/wrangler/src/__tests__/helpers/msw/handlers/script.ts index c084fa6c09820..0b379292ade36 100644 --- a/packages/wrangler/src/__tests__/helpers/msw/handlers/script.ts +++ b/packages/wrangler/src/__tests__/helpers/msw/handlers/script.ts @@ -15,13 +15,13 @@ const scripts: Record = { websocket: `new WebSocket("ws://dummy")`, response: `return new Response("ok")`, }; -function getBindings(scriptName: string | readonly string[]) { +function getBindings(scriptName: string | readonly string[] | undefined) { if (typeof scriptName !== "string") { return ""; } return scriptName.split("--").flatMap((part) => bindings[part] ?? []); } -function getScript(scriptName: string | readonly string[]): string { +function getScript(scriptName: string | readonly string[] | undefined): string { if (typeof scriptName !== "string") { return ""; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4fc3e88bcd00..24da27428806e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,9 +24,22 @@ catalogs: undici: specifier: ^5.28.4 version: 5.28.4 + vite: + specifier: ^5.0.12 + version: 5.0.12 vitest: specifier: ~2.1.8 version: 2.1.8 + vite-plugin: + typescript: + specifier: ^5.7.2 + version: 5.7.3 + unenv: + specifier: npm:unenv-nightly@2.0.0-20241218-183400-5d6aec3 + version: 2.0.0-20241218-183400-5d6aec3 + vite: + specifier: ^6.0.7 + version: 6.0.7 overrides: '@types/react-dom@18>@types/react': ^18 @@ -119,7 +132,7 @@ importers: version: 5.0.12(@types/node@18.19.59) vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) fixtures/additional-modules: devDependencies: @@ -137,7 +150,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -155,7 +168,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -182,7 +195,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -203,7 +216,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -221,7 +234,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -242,7 +255,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -266,7 +279,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -290,7 +303,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) fixtures/isomorphic-random-example: {} @@ -315,7 +328,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -327,7 +340,7 @@ importers: version: 7.0.0 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -352,7 +365,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -370,7 +383,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -397,7 +410,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -415,7 +428,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -436,7 +449,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -464,7 +477,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -482,7 +495,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -500,7 +513,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -521,7 +534,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -539,7 +552,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -576,7 +589,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -597,7 +610,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -612,7 +625,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -633,7 +646,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -651,7 +664,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -669,7 +682,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -687,7 +700,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -705,7 +718,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -723,7 +736,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -741,7 +754,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -759,7 +772,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -780,7 +793,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -795,7 +808,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -820,7 +833,7 @@ importers: devDependencies: vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -862,7 +875,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -889,7 +902,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -921,7 +934,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -942,7 +955,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -963,7 +976,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -981,7 +994,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -999,7 +1012,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1152,10 +1165,10 @@ importers: version: 17.0.24 '@typescript-eslint/eslint-plugin': specifier: ^6.9.0 - version: 6.10.0(@typescript-eslint/parser@6.10.0(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0)(typescript@5.7.3) + version: 6.10.0(@typescript-eslint/parser@6.10.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: ^6.9.0 - version: 6.10.0(eslint@8.57.0)(typescript@5.7.3) + version: 6.10.0(eslint@8.57.0)(typescript@5.6.3) chalk: specifier: ^5.2.0 version: 5.3.0 @@ -1204,12 +1217,21 @@ importers: semver: specifier: ^7.5.1 version: 7.6.3 + typescript: + specifier: catalog:default + version: 5.6.3 undici: specifier: catalog:default version: 5.28.4 + vite: + specifier: catalog:default + version: 5.0.12(@types/node@18.19.59) vite-tsconfig-paths: specifier: ^4.0.8 - version: 4.2.0(typescript@5.7.3)(vite@5.0.12(@types/node@18.19.59)) + version: 4.2.0(typescript@5.6.3)(vite@5.0.12(@types/node@18.19.59)) + vitest: + specifier: catalog:default + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) which-pm-runs: specifier: ^1.1.0 version: 1.1.0 @@ -1652,23 +1674,467 @@ importers: devDependencies: '@types/node-unenv': specifier: npm:@types/node@^22.10.5 - version: '@types/node@22.10.7' + version: '@types/node@22.10.6' typescript: - specifier: ^5.7.3 - version: 5.7.3 + specifier: catalog:default + version: 5.6.3 unbuild: specifier: ^3.2.0 - version: 3.3.1(typescript@5.7.3)(vue-tsc@2.0.29(typescript@5.7.3)) + version: 3.3.1(typescript@5.6.3)(vue-tsc@2.0.29(typescript@5.6.3)) undici: specifier: catalog:default version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + wrangler: + specifier: workspace:* + version: link:../wrangler + + packages/vite-plugin-cloudflare: + dependencies: + '@hattip/adapter-node': + specifier: ^0.0.49 + version: 0.0.49 + miniflare: + specifier: workspace:* + version: link:../miniflare + unenv: + specifier: catalog:vite-plugin + version: unenv-nightly@2.0.0-20241218-183400-5d6aec3 + ws: + specifier: ^8.18.0 + version: 8.18.0 + devDependencies: + '@cloudflare/workers-shared': + specifier: workspace:* + version: link:../workers-shared + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + '@types/node': + specifier: ^18.19.59 + version: 18.19.59 + '@types/ws': + specifier: ^8.5.13 + version: 8.5.13 + magic-string: + specifier: ^0.30.12 + version: 0.30.17 + tsup: + specifier: ^8.3.0 + version: 8.3.5(@microsoft/api-extractor@7.47.4(@types/node@18.19.59))(jiti@2.4.2)(postcss@8.4.49)(typescript@5.7.3) + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: specifier: workspace:* version: link:../wrangler + packages/vite-plugin-cloudflare/playground: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../workers-tsconfig + playwright-chromium: + specifier: ^1.48.1 + version: 1.49.1 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + + packages/vite-plugin-cloudflare/playground/cloudflare-env: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/durable-objects: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/external-durable-objects: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/external-workflows: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/hot-channel: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/module-resolution: + devDependencies: + '@cloudflare-dev-module-resolution/imports': + specifier: file:./packages/imports + version: '@playground/module-resolution-imports@file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports' + '@cloudflare-dev-module-resolution/requires': + specifier: file:./packages/requires + version: '@playground/module-resolution-requires@file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires' + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + '@remix-run/cloudflare': + specifier: 2.12.0 + version: 2.12.0(@cloudflare/workers-types@4.20241230.0)(typescript@5.7.3) + '@types/react': + specifier: ^18.3.11 + version: 18.3.18 + discord-api-types: + specifier: 0.37.98 + version: 0.37.98 + react: + specifier: 18.3.1 + version: 18.3.1 + slash-create: + specifier: 6.2.1 + version: 6.2.1 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/multi-worker: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/node-compat: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + '@types/node': + specifier: ^18.19.59 + version: 18.19.59 + '@types/pg': + specifier: ^8.11.2 + version: 8.11.6 + cross-fetch: + specifier: ^4.0.0 + version: 4.1.0(encoding@0.1.13) + pg: + specifier: ^8.13.0 + version: 8.13.1 + pg-cloudflare: + specifier: ^1.1.1 + version: 1.1.1 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/react-spa: + dependencies: + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + '@types/react': + specifier: ^19.0.0 + version: 19.0.7 + '@types/react-dom': + specifier: ^19.0.0 + version: 19.0.3(@types/react@19.0.7) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@6.0.7(@types/node@18.19.59)(jiti@2.4.2)) + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/spa-with-api: + dependencies: + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + '@types/react': + specifier: ^19.0.0 + version: 19.0.7 + '@types/react-dom': + specifier: ^19.0.0 + version: 19.0.3(@types/react@19.0.7) + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.3.4(vite@6.0.7(@types/node@18.19.59)(jiti@2.4.2)) + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/static-mpa: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/virtual-modules: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/websockets: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/worker: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + + packages/vite-plugin-cloudflare/playground/workflows: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20241230.0 + version: 4.20241230.0 + typescript: + specifier: catalog:vite-plugin + version: 5.7.3 + vite: + specifier: catalog:vite-plugin + version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + packages/vitest-pool-workers: dependencies: birpc: @@ -1734,7 +2200,7 @@ importers: version: 5.28.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) packages/workers-editor-shared: dependencies: @@ -1896,6 +2362,9 @@ importers: undici: specifier: catalog:default version: 5.28.4 + vite: + specifier: catalog:default + version: 5.0.12(@types/node@18.19.59) wrangler: specifier: workspace:^ version: link:../wrangler @@ -1941,7 +2410,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) packages/workers-tsconfig: {} @@ -1964,7 +2433,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: specifier: workspace:* version: link:../wrangler @@ -2007,7 +2476,7 @@ importers: version: 5.6.3 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) packages/wrangler: dependencies: @@ -2057,7 +2526,7 @@ importers: version: link:../pages-shared '@cloudflare/types': specifier: 6.18.4 - version: 6.18.4(react@18.3.1) + version: 6.18.4(react@19.0.0) '@cloudflare/workers-shared': specifier: workspace:* version: link:../workers-shared @@ -2103,6 +2572,9 @@ importers: '@types/minimatch': specifier: ^5.1.2 version: 5.1.2 + '@types/node': + specifier: ^18.19.59 + version: 18.19.59 '@types/prompts': specifier: ^2.0.14 version: 2.0.14 @@ -2198,7 +2670,7 @@ importers: version: 9.3.1 msw: specifier: 2.4.3 - version: 2.4.3(typescript@5.7.3) + version: 2.4.3(typescript@5.6.3) open: specifier: ^8.4.0 version: 8.4.0 @@ -2250,6 +2722,9 @@ importers: ts-json-schema-generator: specifier: ^1.5.0 version: 1.5.0 + typescript: + specifier: catalog:default + version: 5.6.3 undici: specifier: catalog:default version: 5.28.4 @@ -2258,7 +2733,7 @@ importers: version: 1.5.4 vitest: specifier: catalog:default - version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + version: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) vitest-websocket-mock: specifier: ^0.4.0 version: 0.4.0(vitest@2.1.8) @@ -2810,6 +3285,9 @@ packages: peerDependencies: react: ^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0 + '@cloudflare/kv-asset-handler@0.1.3': + resolution: {integrity: sha512-FNcunDuTmEfQTLRLtA6zz+buIXUHj1soPvSWzzQFBC+n2lsy+CGf/NIrR3SEPCmsVNQj70/Jx2lViCpq+09YpQ==} + '@cloudflare/kv-asset-handler@0.3.4': resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} engines: {node: '>=16.13'} @@ -3669,6 +4147,22 @@ packages: resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} engines: {node: '>=14'} + '@hattip/adapter-node@0.0.49': + resolution: {integrity: sha512-BE+Y8Q4U0YcH34FZUYU4DssGKOaZLbNL0zK57Z41UZp0m9kS79ZIolBmjjpPhTVpIlRY3Rs+uhXbVXKk7mUcJA==} + + '@hattip/core@0.0.49': + resolution: {integrity: sha512-3/ZJtC17cv8m6Sph8+nw4exUp9yhEf2Shi7HK6AHSUSBtaaQXZ9rJBVxTfZj3PGNOR/P49UBXOym/52WYKFTJQ==} + + '@hattip/headers@0.0.49': + resolution: {integrity: sha512-rrB2lEhTf0+MNVt5WdW184Ky706F1Ze9Aazn/R8c+/FMUYF9yjem2CgXp49csPt3dALsecrnAUOHFiV0LrrHXA==} + + '@hattip/polyfills@0.0.49': + resolution: {integrity: sha512-5g7W5s6Gq+HDxwULGFQ861yAnEx3yd9V8GDwS96HBZ1nM1u93vN+KTuwXvNsV7Z3FJmCrD/pgU8WakvchclYuA==} + + '@hattip/walk@0.0.49': + resolution: {integrity: sha512-AgJgKLooZyQnzMfoFg5Mo/aHM+HGBC9ExpXIjNqGimYTRgNbL/K7X5EM1kR2JY90BNKk9lo6Usq1T/nWFdT7TQ==} + hasBin: true + '@hono/zod-validator@0.1.8': resolution: {integrity: sha512-/Kd/p/dUVKM7/1TY3jafTV+ngwEh3fOLmXCJQKb9esWsCom5WOJaNmZYv8jeRhyipu1xBvmN5jceRA3Ev3rmQw==} peerDependencies: @@ -3752,6 +4246,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@kamilkisiela/fast-url-parser@1.1.4': + resolution: {integrity: sha512-gbkePEBupNydxCelHCESvFSFM8XPh1Zs/OAVRW/rKpEqPAl5PbOM90Si8mv9bvnR53uPD2s/FiRxdvSejpRJew==} + '@manypkg/cli@0.21.4': resolution: {integrity: sha512-EACxxb+c/t6G0l1FrlyewZeBnyR5V1cLkXjnBfsay5TN1UgbilFpG6POglzn+lVJet9NqnEKe3RLHABzkIDZ0Q==} engines: {node: '>=14.18.0'} @@ -3918,6 +4415,12 @@ packages: resolution: {integrity: sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@playground/module-resolution-imports@file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports': + resolution: {directory: packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports, type: directory} + + '@playground/module-resolution-requires@file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires': + resolution: {directory: packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires, type: directory} + '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -4049,6 +4552,29 @@ packages: '@types/react': optional: true + '@remix-run/cloudflare@2.12.0': + resolution: {integrity: sha512-6bW1Zd4VmKWfRBro2ab06fO/wGauiLM1/Euhy87XnrPgmfDRW65q/HgAyJvt6/ldhaDyTk1NHQN8bxQOr1vdAg==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@cloudflare/workers-types': ^4.0.0 + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + + '@remix-run/router@1.19.2': + resolution: {integrity: sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==} + engines: {node: '>=14.0.0'} + + '@remix-run/server-runtime@2.12.0': + resolution: {integrity: sha512-o9ukOr3XKmyY8UufTrDdkgD3fiy+z+f4qEzvCQnvC0+EasCyN9hb1Vbui6Koo/5HKvahC4Ga8RcWyvhykKrG3g==} + engines: {node: '>=18.0.0'} + peerDependencies: + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + '@rollup/plugin-alias@5.1.1': resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==} engines: {node: '>=14.0.0'} @@ -4718,8 +5244,8 @@ packages: '@types/node@18.19.59': resolution: {integrity: sha512-vizm2EqwV/7Zay+A6J3tGl9Lhr7CjZe2HmWS988sefiEmsyP9CeXEleho6i4hJk/8UtZAo0bWN4QPZZr83RxvQ==} - '@types/node@22.10.7': - resolution: {integrity: sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==} + '@types/node@22.10.6': + resolution: {integrity: sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==} '@types/normalize-package-data@2.4.1': resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -4739,9 +5265,20 @@ packages: '@types/react-dom@18.2.0': resolution: {integrity: sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA==} + '@types/react-dom@19.0.3': + resolution: {integrity: sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@18.3.18': + resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + '@types/react@18.3.3': resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/react@19.0.7': + resolution: {integrity: sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==} + '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -4808,6 +5345,9 @@ packages: '@types/ws@8.5.10': resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + '@types/ws@8.5.13': + resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} + '@types/yargs-parser@20.2.1': resolution: {integrity: sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==} @@ -4944,6 +5484,12 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + '@vitest/expect@2.1.8': resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} @@ -5038,9 +5584,20 @@ packages: '@vue/shared@3.4.36': resolution: {integrity: sha512-fdPLStwl1sDfYuUftBaUVn2pIrVFDASYerZSrlBvVBfylObPA1gtcWJHy5Ox8jLEJ524zBibss488Q3SZtU1uA==} + '@web3-storage/multipart-parser@1.0.0': + resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} + '@webcontainer/env@1.1.0': resolution: {integrity: sha512-DreJFHUui8vq1N3nQGU3HwK5UI4hVNW7VQfhAOmeQbBVnpKtGhoNobjDNF8LzlN7ssmFI9Uhkc9Au4mtKvPK6Q==} + '@whatwg-node/fetch@0.9.23': + resolution: {integrity: sha512-7xlqWel9JsmxahJnYVUj/LLxWcnA93DR4c9xlw3U814jWTiYalryiH1qToik1hOxweKKRLi4haXHM5ycRksPBA==} + engines: {node: '>=18.0.0'} + + '@whatwg-node/node-fetch@0.6.0': + resolution: {integrity: sha512-tcZAhrpx6oVlkEsRngeTEEE7I5/QdLjeEz4IlekabGaESP7+Dkm/6a9KcF1KdCBB7mO9PXtBkwCuTCt8+UPg8Q==} + engines: {node: '>=18.0.0'} + '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} @@ -5135,6 +5692,9 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -5386,6 +5946,12 @@ packages: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -5731,6 +6297,9 @@ packages: engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} hasBin: true + cross-fetch@4.1.0: + resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} + cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -6008,6 +6577,9 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + discord-api-types@0.37.98: + resolution: {integrity: sha512-xsH4UwmnCQl4KjAf01/p9ck9s+/vDqzHbUxPOBzo8fcVUa/hQG6qInD7Cr44KAuCM+xCxGJFSAUx450pBrX0+g==} + dns2@2.1.0: resolution: {integrity: sha512-m27K11aQalRbmUs7RLaz6aPyceLjAoqjPRNTdE7qUouQpl+PC8Bi67O+i9SuJUPbQC8dxFrczAxfmTPuTKHNkw==} @@ -6378,6 +6950,9 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -6419,6 +6994,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -6438,6 +7016,9 @@ packages: fast-loops@1.1.3: resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==} + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + fast-xml-parser@4.4.1: resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} hasBin: true @@ -7230,6 +7811,10 @@ packages: jose@5.9.3: resolution: {integrity: sha512-egLIoYSpcd+QUF+UHgobt5YzI2Pkw/H39ou9suW687MY6PmCwPmkNV/4TNjn1p2tX5xO3j0d0sq5hiYE24bSlg==} + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + js-string-escape@1.0.1: resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} engines: {node: '>= 0.8'} @@ -7362,6 +7947,10 @@ packages: resolution: {integrity: sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + load-yaml-file@0.2.0: resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} engines: {node: '>=6'} @@ -7409,6 +7998,9 @@ packages: lodash.isarray@3.0.4: resolution: {integrity: sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==} + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + lodash.isplainobject@3.2.0: resolution: {integrity: sha512-P4wZnho5curNqeEq/x292Pb57e1v+woR7DJ84DURelKB46lby8aDEGVobSaYtzHdQBWQrJSdxcCwjlGOvvdIyg==} @@ -7424,6 +8016,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} @@ -7590,6 +8185,11 @@ packages: engines: {node: '>=4'} hasBin: true + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + mime@3.0.0: resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} engines: {node: '>=10.0.0'} @@ -7748,6 +8348,9 @@ packages: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nan@2.18.0: resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==} @@ -7785,6 +8388,9 @@ packages: node-addon-api@4.3.0: resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} + node-fetch-native@1.6.4: + resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} + node-fetch@2.6.11: resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==} engines: {node: 4.x || >=6.0.0} @@ -7794,6 +8400,15 @@ packages: encoding: optional: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-forge@1.3.0: resolution: {integrity: sha512-08ARB91bUi6zNKzVmaj3QO7cr397uiDT2nJ63cHjyNtCTWIgvS47j3eT0WfzUwS9+6Z5YshRaoasFkXCKrIYbA==} engines: {node: '>= 6.13.0'} @@ -8168,6 +8783,9 @@ packages: pg-connection-string@2.6.4: resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==} + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -8181,9 +8799,17 @@ packages: peerDependencies: pg: '>=8.0' + pg-pool@3.7.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + peerDependencies: + pg: '>=8.0' + pg-protocol@1.6.1: resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==} + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} @@ -8201,6 +8827,15 @@ packages: pg-native: optional: true + pg@8.13.1: + resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} @@ -8232,6 +8867,10 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -8242,6 +8881,16 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + playwright-chromium@1.49.1: + resolution: {integrity: sha512-XAQDkZ1Eem1OONhfS8B2LM2mgHG/i5jIxooxjvqjbF/9GnLnRTJHdQamNjo1e4FZvt7J0BFD/15+qAcT0eKlfA==} + engines: {node: '>=18'} + hasBin: true + + playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + engines: {node: '>=18'} + hasBin: true + plur@5.1.0: resolution: {integrity: sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -8296,6 +8945,24 @@ packages: peerDependencies: postcss: ^8.4.31 + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + postcss-merge-longhand@7.0.4: resolution: {integrity: sha512-zer1KoZA54Q8RVHKOY5vMke0cCdNxMP3KBfDerjH/BYHh4nCIh+1Yy0t1pAEQF18ac/4z3OFclO+ZVH8azjR4A==} engines: {node: ^18.12.0 || ^20.9.0 || >=22.0} @@ -8603,6 +9270,11 @@ packages: peerDependencies: react: ^18.3.1 + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + peerDependencies: + react: ^19.0.0 + react-fela@11.7.0: resolution: {integrity: sha512-c6sTQ2AFX8WT8Epd6icTxulC6Z/pzqIlEiywTvy5e5VJw8uKxJLs997I3v71nJ9XXs4B5HPxWayflhycaCJiaQ==} peerDependencies: @@ -8663,6 +9335,10 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -8904,6 +9580,9 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} @@ -9022,6 +9701,18 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash-create@6.2.1: + resolution: {integrity: sha512-ootudSRNVVeOdTgD4WLWwLOhw31Tfgi3243du3UiqDpGcAUJQNJ4dSBRcf44pmlmT7T/8YppHU1bS847S1iYbg==} + engines: {node: '>=16'} + peerDependencies: + express: ^4 + fastify: ^3 || ^4 + peerDependenciesMeta: + express: + optional: true + fastify: + optional: true + slash@2.0.0: resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} engines: {node: '>=6'} @@ -9080,6 +9771,14 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead @@ -9236,6 +9935,11 @@ packages: stylis@4.3.0: resolution: {integrity: sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + supertap@3.0.1: resolution: {integrity: sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -9299,6 +10003,13 @@ packages: text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -9370,6 +10081,9 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + traverse@0.3.9: resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} @@ -9391,6 +10105,9 @@ packages: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-json-schema-generator@1.5.0: resolution: {integrity: sha512-RkiaJ6YxGc5EWVPfyHxszTmpGxX8HC2XBvcFlAl1zcvpOG4tjjh+eXioStXJQYTvr9MoK8zCOWzAUlko3K0DiA==} engines: {node: '>=10.0.0'} @@ -9435,6 +10152,25 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.3.5: + resolution: {integrity: sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + tsx@3.12.10: resolution: {integrity: sha512-2+46h4xvUt1aLDNvk5YBT8Uzw+b7BolGbn7iSMucYqCXZiDc+1IMghLVdw8kKjING32JFOeO+Am9posvjkeclA==} hasBin: true @@ -9471,6 +10207,9 @@ packages: cpu: [arm64] os: [linux] + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + turbo-windows-64@2.2.3: resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} cpu: [x64] @@ -9485,6 +10224,9 @@ packages: resolution: {integrity: sha512-5lDvSqIxCYJ/BAd6rQGK/AzFRhBkbu4JHVMLmGh/hCb7U3CqSnr5Tjwfy9vc+/5wG2DJ6wttgAaA7MoCgvBKZQ==} hasBin: true + tweetnacl@1.0.3: + resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + twirp-ts@2.5.0: resolution: {integrity: sha512-JTKIK5Pf/+3qCrmYDFlqcPPUx+ohEWKBaZy8GL8TmvV2VvC0SXVyNYILO39+GCRbqnuP6hBIF+BVr8ZxRz+6fw==} hasBin: true @@ -9621,6 +10363,9 @@ packages: unenv-nightly@2.0.0-20241111-080453-894aa31: resolution: {integrity: sha512-0W39QQOQ9VE8kVVUpGwEG+pZcsCXk5wqNG6rDPE6Gr+fiA69LR0qERM61hW5KCOkC1/ArCFrfCGjwHyyv/bI0Q==} + unenv-nightly@2.0.0-20241218-183400-5d6aec3: + resolution: {integrity: sha512-7Xpi29CJRbOV1/IrC03DawMJ0hloklDLq/cigSe+J2jkcC+iDres2Cy0r4ltj5f0x7DqsaGaB4/dLuCPPFZnZA==} + unenv-nightly@2.0.0-20250109-100802-88ad671: resolution: {integrity: sha512-Uij6gODNNNNsNBoDlnaMvZI99I6YlVJLRfYH8AOLMlbFrW7k2w872v9VLuIdch2vF8QBeSC4EftIh5sG4ibzdA==} @@ -9685,6 +10430,9 @@ packages: resolution: {integrity: sha512-d6GYsr992Bo9rzTZFc9BUw3UFAAg3prE9JGVBgW2TLTbI3rSvg4VDa0BFXHMzKkWbAuhrmaFWpucpRJl+3W7Jg==} deprecated: now available as @ungap/url-search-params + urlpattern-polyfill@10.0.0: + resolution: {integrity: sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==} + urlpattern-polyfill@4.0.3: resolution: {integrity: sha512-DOE84vZT2fEcl9gqCUTcnAw5ZY5Id55ikUcziSUntuEFL3pRvavg5kwDmTEUJkeCHInTlV/HexFomgYnzO5kdQ==} @@ -9778,6 +10526,46 @@ packages: terser: optional: true + vite@6.0.7: + resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.19.59 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitest-websocket-mock@0.4.0: resolution: {integrity: sha512-tGnOwE2nC8jfioQXDrX+lZ8EVrF+IO2NVqe1vV9h945W/hlR0S6ZYbMqCJGG3Nyd//c5XSe1IGLD2ZgE2D1I7Q==} peerDependencies: @@ -9836,6 +10624,9 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + well-known-symbols@2.0.0: resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} engines: {node: '>=6'} @@ -9851,6 +10642,9 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -11183,6 +11977,14 @@ snapshots: dependencies: react: 18.3.1 + '@cloudflare/intl-types@1.5.1(react@19.0.0)': + dependencies: + react: 19.0.0 + + '@cloudflare/kv-asset-handler@0.1.3': + dependencies: + mime: 2.6.0 + '@cloudflare/kv-asset-handler@0.3.4': dependencies: mime: 3.0.0 @@ -11224,11 +12026,11 @@ snapshots: react-fela: 11.7.0(fela@11.7.0)(react@18.3.1) react-test-renderer: 17.0.2(react@18.3.1) - '@cloudflare/types@6.18.4(react@18.3.1)': + '@cloudflare/types@6.18.4(react@19.0.0)': dependencies: - '@cloudflare/intl-types': 1.5.1(react@18.3.1) + '@cloudflare/intl-types': 1.5.1(react@19.0.0) '@cloudflare/util-en-garde': 8.0.10 - react: 18.3.1 + react: 19.0.0 '@cloudflare/types@6.23.6(react@18.3.1)': dependencies: @@ -11261,7 +12063,7 @@ snapshots: esbuild: 0.17.19 miniflare: 3.20241106.1 semver: 7.6.3 - vitest: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + vitest: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) wrangler: 3.90.0(@cloudflare/workers-types@4.20241230.0) zod: 3.22.3 transitivePeerDependencies: @@ -11709,6 +12511,30 @@ snapshots: '@fastify/busboy@2.1.1': {} + '@hattip/adapter-node@0.0.49': + dependencies: + '@hattip/core': 0.0.49 + '@hattip/polyfills': 0.0.49 + '@hattip/walk': 0.0.49 + + '@hattip/core@0.0.49': {} + + '@hattip/headers@0.0.49': + dependencies: + '@hattip/core': 0.0.49 + + '@hattip/polyfills@0.0.49': + dependencies: + '@hattip/core': 0.0.49 + '@whatwg-node/fetch': 0.9.23 + node-fetch-native: 1.6.4 + + '@hattip/walk@0.0.49': + dependencies: + '@hattip/headers': 0.0.49 + cac: 6.7.14 + mime-types: 2.1.35 + '@hono/zod-validator@0.1.8(hono@3.12.11)(zod@3.22.3)': dependencies: hono: 3.12.11 @@ -11810,6 +12636,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.5.0 + '@kamilkisiela/fast-url-parser@1.1.4': {} + '@manypkg/cli@0.21.4': dependencies: '@manypkg/get-packages': 2.2.1 @@ -12093,6 +12921,10 @@ snapshots: picocolors: 1.1.1 tslib: 2.8.1 + '@playground/module-resolution-imports@file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/imports': {} + + '@playground/module-resolution-requires@file:packages/vite-plugin-cloudflare/playground/module-resolution/packages/requires': {} + '@pnpm/config.env-replace@1.1.0': {} '@pnpm/network.ca-file@1.0.2': @@ -12218,6 +13050,28 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 + '@remix-run/cloudflare@2.12.0(@cloudflare/workers-types@4.20241230.0)(typescript@5.7.3)': + dependencies: + '@cloudflare/kv-asset-handler': 0.1.3 + '@cloudflare/workers-types': 4.20241230.0 + '@remix-run/server-runtime': 2.12.0(typescript@5.7.3) + optionalDependencies: + typescript: 5.7.3 + + '@remix-run/router@1.19.2': {} + + '@remix-run/server-runtime@2.12.0(typescript@5.7.3)': + dependencies: + '@remix-run/router': 1.19.2 + '@types/cookie': 0.6.0 + '@web3-storage/multipart-parser': 1.0.0 + cookie: 0.6.0 + set-cookie-parser: 2.6.0 + source-map: 0.7.4 + turbo-stream: 2.4.0 + optionalDependencies: + typescript: 5.7.3 + '@rollup/plugin-alias@5.1.1(rollup@4.30.1)': optionalDependencies: rollup: 4.30.1 @@ -12998,7 +13852,7 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@22.10.7': + '@types/node@22.10.6': dependencies: undici-types: 6.20.0 @@ -13020,13 +13874,26 @@ snapshots: '@types/react-dom@18.2.0': dependencies: - '@types/react': 18.3.3 + '@types/react': 18.3.18 + + '@types/react-dom@19.0.3(@types/react@19.0.7)': + dependencies: + '@types/react': 19.0.7 + + '@types/react@18.3.18': + dependencies: + '@types/prop-types': 15.7.4 + csstype: 3.1.2 '@types/react@18.3.3': dependencies: '@types/prop-types': 15.7.4 csstype: 3.1.2 + '@types/react@19.0.7': + dependencies: + csstype: 3.1.2 + '@types/resolve@1.20.2': {} '@types/resolve@1.20.6': {} @@ -13079,12 +13946,36 @@ snapshots: dependencies: '@types/node': 18.19.59 + '@types/ws@8.5.13': + dependencies: + '@types/node': 18.19.59 + '@types/yargs-parser@20.2.1': {} '@types/yargs@17.0.24': dependencies: '@types/yargs-parser': 20.2.1 + '@typescript-eslint/eslint-plugin@6.10.0(@typescript-eslint/parser@6.10.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0)(typescript@5.6.3)': + dependencies: + '@eslint-community/regexpp': 4.10.1 + '@typescript-eslint/parser': 6.10.0(eslint@8.57.0)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 6.10.0 + '@typescript-eslint/type-utils': 6.10.0(eslint@8.57.0)(typescript@5.6.3) + '@typescript-eslint/utils': 6.10.0(eslint@8.57.0)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 6.10.0 + debug: 4.3.7(supports-color@9.2.2) + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.6.3 + ts-api-utils: 1.4.3(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/eslint-plugin@6.10.0(@typescript-eslint/parser@6.10.0(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.10.1 @@ -13123,6 +14014,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@6.10.0(eslint@8.57.0)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.10.0 + '@typescript-eslint/types': 6.10.0 + '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 6.10.0 + debug: 4.3.7(supports-color@9.2.2) + eslint: 8.57.0 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@6.10.0(eslint@8.57.0)(typescript@5.7.3)': dependencies: '@typescript-eslint/scope-manager': 6.10.0 @@ -13159,6 +14063,18 @@ snapshots: '@typescript-eslint/types': 7.18.0 '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/type-utils@6.10.0(eslint@8.57.0)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.6.3) + '@typescript-eslint/utils': 6.10.0(eslint@8.57.0)(typescript@5.6.3) + debug: 4.3.7(supports-color@9.2.2) + eslint: 8.57.0 + ts-api-utils: 1.4.3(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/type-utils@6.10.0(eslint@8.57.0)(typescript@5.7.3)': dependencies: '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.7.3) @@ -13187,6 +14103,20 @@ snapshots: '@typescript-eslint/types@7.18.0': {} + '@typescript-eslint/typescript-estree@6.10.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 6.10.0 + '@typescript-eslint/visitor-keys': 6.10.0 + debug: 4.3.7(supports-color@9.2.2) + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.6.3 + ts-api-utils: 1.4.3(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/typescript-estree@6.10.0(typescript@5.7.3)': dependencies: '@typescript-eslint/types': 6.10.0 @@ -13216,6 +14146,20 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@6.10.0(eslint@8.57.0)(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.13 + '@types/semver': 7.5.1 + '@typescript-eslint/scope-manager': 6.10.0 + '@typescript-eslint/types': 6.10.0 + '@typescript-eslint/typescript-estree': 6.10.0(typescript@5.6.3) + eslint: 8.57.0 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/utils@6.10.0(eslint@8.57.0)(typescript@5.7.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) @@ -13282,6 +14226,17 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitejs/plugin-react@4.3.4(vite@6.0.7(@types/node@18.19.59)(jiti@2.4.2))': + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) + transitivePeerDependencies: + - supports-color + '@vitest/expect@2.1.8': dependencies: '@vitest/spy': 2.1.8 @@ -13289,13 +14244,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.8(msw@2.4.3(typescript@5.7.3))(vite@5.0.12(@types/node@18.19.59))': + '@vitest/mocker@2.1.8(msw@2.4.3(typescript@5.6.3))(vite@5.0.12(@types/node@18.19.59))': dependencies: '@vitest/spy': 2.1.8 estree-walker: 3.0.3 - magic-string: 0.30.14 + magic-string: 0.30.17 optionalDependencies: - msw: 2.4.3(typescript@5.7.3) + msw: 2.4.3(typescript@5.6.3) vite: 5.0.12(@types/node@18.19.59) '@vitest/pretty-format@2.1.8': @@ -13310,7 +14265,7 @@ snapshots: '@vitest/snapshot@2.1.8': dependencies: '@vitest/pretty-format': 2.1.8 - magic-string: 0.30.14 + magic-string: 0.30.17 pathe: 1.1.2 '@vitest/spy@2.1.8': @@ -13326,7 +14281,7 @@ snapshots: sirv: 3.0.0 tinyglobby: 0.2.10 tinyrainbow: 1.2.0 - vitest: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + vitest: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) '@vitest/utils@2.1.8': dependencies: @@ -13402,7 +14357,7 @@ snapshots: '@vue/reactivity-transform': 3.3.4 '@vue/shared': 3.3.4 estree-walker: 2.0.2 - magic-string: 0.30.14 + magic-string: 0.30.17 postcss: 8.4.49 source-map-js: 1.2.1 @@ -13416,6 +14371,20 @@ snapshots: de-indent: 1.0.2 he: 1.2.0 + '@vue/language-core@2.0.29(typescript@5.6.3)': + dependencies: + '@volar/language-core': 2.4.0-alpha.18 + '@vue/compiler-dom': 3.4.36 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.4.36 + computeds: 0.0.1 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.6.3 + optional: true + '@vue/language-core@2.0.29(typescript@5.7.3)': dependencies: '@volar/language-core': 2.4.0-alpha.18 @@ -13435,14 +14404,28 @@ snapshots: '@vue/compiler-core': 3.3.4 '@vue/shared': 3.3.4 estree-walker: 2.0.2 - magic-string: 0.30.14 + magic-string: 0.30.17 '@vue/shared@3.3.4': {} '@vue/shared@3.4.36': {} + '@web3-storage/multipart-parser@1.0.0': {} + '@webcontainer/env@1.1.0': {} + '@whatwg-node/fetch@0.9.23': + dependencies: + '@whatwg-node/node-fetch': 0.6.0 + urlpattern-polyfill: 10.0.0 + + '@whatwg-node/node-fetch@0.6.0': + dependencies: + '@kamilkisiela/fast-url-parser': 1.1.4 + busboy: 1.6.0 + fast-querystring: 1.1.2 + tslib: 2.8.1 + '@yarnpkg/lockfile@1.1.0': {} abbrev@1.1.1: {} @@ -13525,6 +14508,8 @@ snapshots: ansi-styles@6.2.1: {} + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -13855,6 +14840,11 @@ snapshots: dependencies: run-applescript: 5.0.0 + bundle-require@5.1.0(esbuild@0.24.2): + dependencies: + esbuild: 0.24.2 + load-tsconfig: 0.2.5 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -14239,6 +15229,12 @@ snapshots: dependencies: cross-spawn: 7.0.3 + cross-fetch@4.1.0(encoding@0.1.13): + dependencies: + node-fetch: 2.7.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + cross-spawn@5.1.0: dependencies: lru-cache: 4.1.5 @@ -14526,6 +15522,8 @@ snapshots: dependencies: path-type: 4.0.0 + discord-api-types@0.37.98: {} + dns2@2.1.0: {} doctrine@2.1.0: @@ -15077,12 +16075,14 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 esutils@2.0.3: {} eventemitter3@4.0.7: {} + eventemitter3@5.0.1: {} + events@3.3.0: {} execa@5.1.1: @@ -15139,6 +16139,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-decode-uri-component@1.0.1: {} + fast-deep-equal@3.1.3: {} fast-diff@1.2.0: {} @@ -15157,6 +16159,10 @@ snapshots: fast-loops@1.1.3: {} + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + fast-xml-parser@4.4.1: dependencies: strnum: 1.0.5 @@ -15991,6 +16997,8 @@ snapshots: jose@5.9.3: {} + joycon@3.1.1: {} + js-string-escape@1.0.1: {} js-tokens@4.0.0: {} @@ -16111,6 +17119,8 @@ snapshots: load-json-file@7.0.1: {} + load-tsconfig@0.2.5: {} + load-yaml-file@0.2.0: dependencies: graceful-fs: 4.2.10 @@ -16155,6 +17165,8 @@ snapshots: lodash.isarray@3.0.4: {} + lodash.isequal@4.5.0: {} + lodash.isplainobject@3.2.0: dependencies: lodash._basefor: 3.0.3 @@ -16172,6 +17184,8 @@ snapshots: lodash.merge@4.6.2: {} + lodash.sortby@4.7.0: {} + lodash.startcase@4.4.0: {} lodash.union@4.6.0: {} @@ -16331,6 +17345,8 @@ snapshots: mime@1.6.0: {} + mime@2.6.0: {} + mime@3.0.0: {} mimic-fn@2.1.0: {} @@ -16419,7 +17435,7 @@ snapshots: mkdirp@1.0.4: {} - mkdist@2.2.0(typescript@5.7.3)(vue-tsc@2.0.29(typescript@5.7.3)): + mkdist@2.2.0(typescript@5.6.3)(vue-tsc@2.0.29(typescript@5.6.3)): dependencies: autoprefixer: 10.4.20(postcss@8.4.49) citty: 0.1.6 @@ -16435,8 +17451,8 @@ snapshots: semver: 7.6.3 tinyglobby: 0.2.10 optionalDependencies: - typescript: 5.7.3 - vue-tsc: 2.0.29(typescript@5.7.3) + typescript: 5.6.3 + vue-tsc: 2.0.29(typescript@5.6.3) mlly@1.7.3: dependencies: @@ -16483,7 +17499,7 @@ snapshots: ms@2.1.3: {} - msw@2.4.3(typescript@5.7.3): + msw@2.4.3(typescript@5.6.3): dependencies: '@bundled-es-modules/cookie': 2.0.0 '@bundled-es-modules/statuses': 1.0.1 @@ -16503,7 +17519,7 @@ snapshots: type-fest: 4.26.1 yargs: 17.7.2 optionalDependencies: - typescript: 5.7.3 + typescript: 5.6.3 muggle-string@0.4.1: {} @@ -16513,6 +17529,12 @@ snapshots: mute-stream@1.0.0: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nan@2.18.0: optional: true @@ -16543,12 +17565,20 @@ snapshots: node-addon-api@4.3.0: {} + node-fetch-native@1.6.4: {} + node-fetch@2.6.11(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 optionalDependencies: encoding: 0.1.13 + node-fetch@2.7.0(encoding@0.1.13): + dependencies: + whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 + node-forge@1.3.0: {} node-gyp-build@4.8.0: {} @@ -16972,6 +18002,8 @@ snapshots: pg-connection-string@2.6.4: {} + pg-connection-string@2.7.0: {} + pg-int8@1.0.1: {} pg-numeric@1.0.2: {} @@ -16980,8 +18012,14 @@ snapshots: dependencies: pg: 8.11.3(patch_hash=afbi5l3roukyz2s7kkn56yzjte) + pg-pool@3.7.0(pg@8.13.1): + dependencies: + pg: 8.13.1 + pg-protocol@1.6.1: {} + pg-protocol@1.7.0: {} + pg-types@2.2.0: dependencies: pg-int8: 1.0.1 @@ -17012,6 +18050,16 @@ snapshots: optionalDependencies: pg-cloudflare: 1.1.1 + pg@8.13.1: + dependencies: + pg-connection-string: 2.7.0 + pg-pool: 3.7.0(pg@8.13.1) + pg-protocol: 1.7.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + pgpass@1.0.5: dependencies: split2: 4.2.0 @@ -17030,6 +18078,8 @@ snapshots: pify@4.0.1: {} + pirates@4.0.6: {} + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -17037,7 +18087,7 @@ snapshots: pkg-types@1.2.1: dependencies: confbox: 0.1.8 - mlly: 1.7.3 + mlly: 1.7.4 pathe: 1.1.2 pkg-types@1.3.1: @@ -17046,6 +18096,12 @@ snapshots: mlly: 1.7.4 pathe: 2.0.1 + playwright-chromium@1.49.1: + dependencies: + playwright-core: 1.49.1 + + playwright-core@1.49.1: {} + plur@5.1.0: dependencies: irregular-plurals: 3.5.0 @@ -17093,6 +18149,13 @@ snapshots: dependencies: postcss: 8.4.49 + postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.4.49): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 2.4.2 + postcss: 8.4.49 + postcss-merge-longhand@7.0.4(postcss@8.4.49): dependencies: postcss: 8.4.49 @@ -17387,6 +18450,11 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-dom@19.0.0(react@19.0.0): + dependencies: + react: 19.0.0 + scheduler: 0.25.0 + react-fela@11.7.0(fela@11.7.0)(react@18.3.1): dependencies: fela: 11.7.0 @@ -17449,6 +18517,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + react@19.0.0: {} + read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 @@ -17629,11 +18699,11 @@ snapshots: dependencies: glob: 10.4.5 - rollup-plugin-dts@6.1.1(rollup@4.30.1)(typescript@5.7.3): + rollup-plugin-dts@6.1.1(rollup@4.30.1)(typescript@5.6.3): dependencies: magic-string: 0.30.17 rollup: 4.30.1 - typescript: 5.7.3 + typescript: 5.6.3 optionalDependencies: '@babel/code-frame': 7.26.2 @@ -17747,6 +18817,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + scheduler@0.25.0: {} + scule@1.3.0: {} selfsigned@2.1.1: @@ -17863,6 +18935,13 @@ snapshots: sisteransi@1.0.5: {} + slash-create@6.2.1: + dependencies: + eventemitter3: 5.0.1 + lodash.isequal: 4.5.0 + tweetnacl: 1.0.3 + undici: 5.28.4 + slash@2.0.0: {} slash@3.0.0: {} @@ -17924,6 +19003,12 @@ snapshots: source-map@0.6.1: {} + source-map@0.7.4: {} + + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + sourcemap-codec@1.4.8: {} spawn-command@0.0.2: {} @@ -18100,6 +19185,16 @@ snapshots: stylis@4.3.0: {} + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + supertap@3.0.1: dependencies: indent-string: 5.0.0 @@ -18179,6 +19274,14 @@ snapshots: text-table@0.2.0: {} + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + through@2.3.8: {} time-zone@1.0.0: {} @@ -18242,6 +19345,10 @@ snapshots: tr46@0.0.3: {} + tr46@1.0.1: + dependencies: + punycode: 2.1.1 + traverse@0.3.9: {} tree-kill@1.2.2: {} @@ -18258,6 +19365,8 @@ snapshots: ts-dedent@2.2.0: {} + ts-interface-checker@0.1.13: {} + ts-json-schema-generator@1.5.0: dependencies: '@types/json-schema': 7.0.13 @@ -18291,9 +19400,9 @@ snapshots: lodash: 4.17.21 prettier: 2.7.1 - tsconfck@2.1.1(typescript@5.7.3): + tsconfck@2.1.1(typescript@5.6.3): optionalDependencies: - typescript: 5.7.3 + typescript: 5.6.3 tsconfig-paths@3.14.1: dependencies: @@ -18313,6 +19422,34 @@ snapshots: tslib@2.8.1: {} + tsup@8.3.5(@microsoft/api-extractor@7.47.4(@types/node@18.19.59))(jiti@2.4.2)(postcss@8.4.49)(typescript@5.7.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.24.2) + cac: 6.7.14 + chokidar: 4.0.1 + consola: 3.3.3 + debug: 4.3.7(supports-color@9.2.2) + esbuild: 0.24.2 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.4.49) + resolve-from: 5.0.0 + rollup: 4.30.1 + source-map: 0.8.0-beta.0 + sucrase: 3.35.0 + tinyexec: 0.3.1 + tinyglobby: 0.2.10 + tree-kill: 1.2.2 + optionalDependencies: + '@microsoft/api-extractor': 7.47.4(@types/node@18.19.59) + postcss: 8.4.49 + typescript: 5.7.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + tsx@3.12.10: dependencies: '@esbuild-kit/cjs-loader': 2.4.4 @@ -18349,6 +19486,8 @@ snapshots: turbo-linux-arm64@2.2.3: optional: true + turbo-stream@2.4.0: {} + turbo-windows-64@2.2.3: optional: true @@ -18364,6 +19503,8 @@ snapshots: turbo-windows-64: 2.2.3 turbo-windows-arm64: 2.2.3 + tweetnacl@1.0.3: {} + twirp-ts@2.5.0(@protobuf-ts/plugin@2.9.3): dependencies: '@protobuf-ts/plugin-framework': 2.9.3 @@ -18458,7 +19599,7 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.0.2 - unbuild@3.3.1(typescript@5.7.3)(vue-tsc@2.0.29(typescript@5.7.3)): + unbuild@3.3.1(typescript@5.6.3)(vue-tsc@2.0.29(typescript@5.6.3)): dependencies: '@rollup/plugin-alias': 5.1.1(rollup@4.30.1) '@rollup/plugin-commonjs': 28.0.2(rollup@4.30.1) @@ -18473,18 +19614,18 @@ snapshots: hookable: 5.5.3 jiti: 2.4.2 magic-string: 0.30.17 - mkdist: 2.2.0(typescript@5.7.3)(vue-tsc@2.0.29(typescript@5.7.3)) + mkdist: 2.2.0(typescript@5.6.3)(vue-tsc@2.0.29(typescript@5.6.3)) mlly: 1.7.4 pathe: 2.0.1 pkg-types: 1.3.1 pretty-bytes: 6.1.1 rollup: 4.30.1 - rollup-plugin-dts: 6.1.1(rollup@4.30.1)(typescript@5.7.3) + rollup-plugin-dts: 6.1.1(rollup@4.30.1)(typescript@5.6.3) scule: 1.3.0 tinyglobby: 0.2.10 untyped: 1.5.2 optionalDependencies: - typescript: 5.7.3 + typescript: 5.6.3 transitivePeerDependencies: - sass - supports-color @@ -18510,6 +19651,14 @@ snapshots: pathe: 1.1.2 ufo: 1.5.4 + unenv-nightly@2.0.0-20241218-183400-5d6aec3: + dependencies: + defu: 6.1.4 + mlly: 1.7.4 + ohash: 1.1.4 + pathe: 1.1.2 + ufo: 1.5.4 + unenv-nightly@2.0.0-20250109-100802-88ad671: dependencies: defu: 6.1.4 @@ -18580,6 +19729,8 @@ snapshots: url-search-params@0.10.2: {} + urlpattern-polyfill@10.0.0: {} + urlpattern-polyfill@4.0.3: {} use-sync-external-store@1.2.0(react@18.3.1): @@ -18650,7 +19801,7 @@ snapshots: debug: 4.3.7(supports-color@9.2.2) kolorist: 1.8.0 local-pkg: 0.5.0 - magic-string: 0.30.14 + magic-string: 0.30.17 typescript: 5.7.3 vue-tsc: 2.0.29(typescript@5.7.3) optionalDependencies: @@ -18660,11 +19811,11 @@ snapshots: - rollup - supports-color - vite-tsconfig-paths@4.2.0(typescript@5.7.3)(vite@5.0.12(@types/node@18.19.59)): + vite-tsconfig-paths@4.2.0(typescript@5.6.3)(vite@5.0.12(@types/node@18.19.59)): dependencies: debug: 4.3.7(supports-color@9.2.2) globrex: 0.1.2 - tsconfck: 2.1.1(typescript@5.7.3) + tsconfck: 2.1.1(typescript@5.6.3) optionalDependencies: vite: 5.0.12(@types/node@18.19.59) transitivePeerDependencies: @@ -18680,16 +19831,26 @@ snapshots: '@types/node': 18.19.59 fsevents: 2.3.3 + vite@6.0.7(@types/node@18.19.59)(jiti@2.4.2): + dependencies: + esbuild: 0.24.2 + postcss: 8.4.49 + rollup: 4.30.1 + optionalDependencies: + '@types/node': 18.19.59 + fsevents: 2.3.3 + jiti: 2.4.2 + vitest-websocket-mock@0.4.0(vitest@2.1.8): dependencies: '@vitest/utils': 2.1.8 mock-socket: 9.3.1 - vitest: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2) + vitest: 2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) - vitest@2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.7.3))(supports-color@9.2.2): + vitest@2.1.8(@types/node@18.19.59)(@vitest/ui@2.1.8)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2): dependencies: '@vitest/expect': 2.1.8 - '@vitest/mocker': 2.1.8(msw@2.4.3(typescript@5.7.3))(vite@5.0.12(@types/node@18.19.59)) + '@vitest/mocker': 2.1.8(msw@2.4.3(typescript@5.6.3))(vite@5.0.12(@types/node@18.19.59)) '@vitest/pretty-format': 2.1.8 '@vitest/runner': 2.1.8 '@vitest/snapshot': 2.1.8 @@ -18698,7 +19859,7 @@ snapshots: chai: 5.1.2 debug: 4.3.7(supports-color@9.2.2) expect-type: 1.1.0 - magic-string: 0.30.14 + magic-string: 0.30.17 pathe: 1.1.2 std-env: 3.8.0 tinybench: 2.9.0 @@ -18746,6 +19907,14 @@ snapshots: vscode-uri@3.0.8: {} + vue-tsc@2.0.29(typescript@5.6.3): + dependencies: + '@volar/typescript': 2.4.0-alpha.18 + '@vue/language-core': 2.0.29(typescript@5.6.3) + semver: 7.6.3 + typescript: 5.6.3 + optional: true + vue-tsc@2.0.29(typescript@5.7.3): dependencies: '@volar/typescript': 2.4.0-alpha.18 @@ -18767,6 +19936,8 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@4.0.2: {} + well-known-symbols@2.0.0: {} whatwg-encoding@3.1.1: @@ -18780,6 +19951,12 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bf9c65e8c875d..0b62ff1c46db1 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,7 @@ packages: - "packages/*" + - "packages/vite-plugin-cloudflare/playground/*" + - "packages/vite-plugin-cloudflare/playground" - "fixtures/*" - "tools" @@ -12,4 +14,12 @@ catalog: rimraf: "^5.0.10" typescript: "~5.6.3" undici: "^5.28.4" - vitest: ~2.1.8 + vitest: "~2.1.8" + vite: "^5.0.12" + +catalogs: + vite-plugin: + "typescript": "^5.7.2" + "vite": "^6.0.7" + "@types/node": "^22.10.1" + "unenv": "npm:unenv-nightly@2.0.0-20241218-183400-5d6aec3" diff --git a/tools/deployments/__tests__/validate-changesets.test.ts b/tools/deployments/__tests__/validate-changesets.test.ts index a345f5a705e2b..77c19fae4a4cb 100644 --- a/tools/deployments/__tests__/validate-changesets.test.ts +++ b/tools/deployments/__tests__/validate-changesets.test.ts @@ -31,6 +31,7 @@ describe("findPackageNames()", () => { "@cloudflare/workers-editor-shared", "@cloudflare/workers-shared", "@cloudflare/workflows-shared", + "@cloudflare/vite-plugin", "cloudflare-workers-bindings-extension", "create-cloudflare", "devprod-status-bot", From ce165ff11f1fbd0cb4e99b44923af83c4fc5664a Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 20 Jan 2025 11:18:11 +0000 Subject: [PATCH 03/24] fix Cloudflare Vite plugin beta release script (#7826) --- .github/workflows/prereleases.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prereleases.yml b/.github/workflows/prereleases.yml index c1c685cba0f45..464b764cc84d1 100644 --- a/.github/workflows/prereleases.yml +++ b/.github/workflows/prereleases.yml @@ -80,8 +80,8 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} - - name: Publish vite-plugin-cloudflare@beta to NPM - run: pnpm --filter vite-plugin-cloudflare publish --tag beta + - name: Publish @cloudflare/vite-plugin@beta to NPM + run: pnpm --filter vite-plugin publish --tag beta env: NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} From 7faabeb1d1534818d0e93fe4e4710e9b77af1bfb Mon Sep 17 00:00:00 2001 From: Daniel Walsh Date: Mon, 20 Jan 2025 14:22:58 +0000 Subject: [PATCH 04/24] Fix asset-worker not logging analytics for successful requests (#7808) * Fix asset-worker not logging analytics for successful requests * Fix env being undefined in handleRequest span and add version ID --- .changeset/heavy-parents-switch.md | 5 +++ .../workers-shared/asset-worker/src/index.ts | 34 ++++++++++++------- .../workers-shared/asset-worker/wrangler.toml | 19 ++++++----- 3 files changed, 38 insertions(+), 20 deletions(-) create mode 100644 .changeset/heavy-parents-switch.md diff --git a/.changeset/heavy-parents-switch.md b/.changeset/heavy-parents-switch.md new file mode 100644 index 0000000000000..8ed033dffe419 --- /dev/null +++ b/.changeset/heavy-parents-switch.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/workers-shared": patch +--- + +fix: fix analytics not being logged for `asset-worker` in the case of a successful request. diff --git a/packages/workers-shared/asset-worker/src/index.ts b/packages/workers-shared/asset-worker/src/index.ts index e2f3aa16216c1..a3ecb1e3c8aa8 100644 --- a/packages/workers-shared/asset-worker/src/index.ts +++ b/packages/workers-shared/asset-worker/src/index.ts @@ -106,6 +106,7 @@ export default class extends WorkerEntrypoint { hostname: url.hostname, eyeballPath: url.pathname, env: this.env.ENVIRONMENT, + version: this.env.VERSION_METADATA?.id, }); return handleRequest( @@ -115,21 +116,17 @@ export default class extends WorkerEntrypoint { this.unstable_exists.bind(this), this.unstable_getByETag.bind(this) ); - }).catch((err) => - this.handleError(sentry, analytics, performance, startTimeMs, err) - ); + }) + .catch((err) => this.handleError(sentry, analytics, err)) + .finally(() => this.submitMetrics(analytics, performance, startTimeMs)); } catch (err) { - return this.handleError(sentry, analytics, performance, startTimeMs, err); + const errorResponse = this.handleError(sentry, analytics, err); + this.submitMetrics(analytics, performance, startTimeMs); + return errorResponse; } } - handleError( - sentry: Toucan | undefined, - analytics: Analytics, - performance: PerformanceTimer, - startTimeMs: number, - err: unknown - ) { + handleError(sentry: Toucan | undefined, analytics: Analytics, err: unknown) { try { const response = new InternalServerErrorResponse(err as Error); @@ -143,9 +140,22 @@ export default class extends WorkerEntrypoint { } return response; - } finally { + } catch (e) { + console.error("Error handling error", e); + return new InternalServerErrorResponse(e as Error); + } + } + + submitMetrics( + analytics: Analytics, + performance: PerformanceTimer, + startTimeMs: number + ) { + try { analytics.setData({ requestTime: performance.now() - startTimeMs }); analytics.write(); + } catch (e) { + console.error("Error submitting metrics", e); } } diff --git a/packages/workers-shared/asset-worker/wrangler.toml b/packages/workers-shared/asset-worker/wrangler.toml index b5bc77b47f80f..dbafecdb89a37 100644 --- a/packages/workers-shared/asset-worker/wrangler.toml +++ b/packages/workers-shared/asset-worker/wrangler.toml @@ -15,6 +15,16 @@ compatibility_date = "2024-07-31" # nodejs_compat required when using @cloudflare/vitest-pool-workers compatibility_flags = ["nodejs_compat"] +[unsafe.metadata.build_options] +stable_id = "cloudflare/cf_asset_worker" +networks = ["cf","jdc"] + +[vars] +ENVIRONMENT = "production" + +[version_metadata] +binding = "VERSION_METADATA" + [[unsafe.bindings]] name = "CONFIG" type = "param" @@ -30,13 +40,6 @@ data_ref = true name = "ASSETS_KV_NAMESPACE" type = "internal_assets" -[unsafe.metadata.build_options] -stable_id = "cloudflare/cf_asset_worker" -networks = ["cf","jdc"] - -[version_metadata] -binding = "VERSION_METADATA" - [[unsafe.bindings]] name = "workers-asset-worker" -type = "internal_capability_grants" \ No newline at end of file +type = "internal_capability_grants" From d7adb50fcc9e3c509365fed8a86df485ea9f739b Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 20 Jan 2025 15:44:46 +0100 Subject: [PATCH 05/24] chore: update unenv to 2.0.0-rc.0 (#7806) --- .changeset/red-comics-sit.md | 14 ++++++++++++++ packages/unenv-preset/package.json | 2 +- packages/unenv-preset/tests/worker/index.ts | 9 +++++++++ packages/wrangler/package.json | 2 +- packages/wrangler/src/__tests__/deploy.test.ts | 2 +- pnpm-lock.yaml | 16 ++++++++-------- 6 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 .changeset/red-comics-sit.md diff --git a/.changeset/red-comics-sit.md b/.changeset/red-comics-sit.md new file mode 100644 index 0000000000000..705d1ffcbcce4 --- /dev/null +++ b/.changeset/red-comics-sit.md @@ -0,0 +1,14 @@ +--- +"@cloudflare/unenv-preset": patch +"wrangler": patch +--- + +chore: update unenv to 2.0.0-rc.0 + +Pull a couple changes in node:timers + +- unjs/unenv#384 fix function bindings in node:timer +- unjs/unenv#385 implement active and \_unrefActive in node:timer + +The unenv update also includes #unjs/unenv/381 which implements +`stdout`, `stderr` and `stdin` of `node:process` with `node:tty` diff --git a/packages/unenv-preset/package.json b/packages/unenv-preset/package.json index aa79e0897adc5..b8391a590de42 100644 --- a/packages/unenv-preset/package.json +++ b/packages/unenv-preset/package.json @@ -54,7 +54,7 @@ "wrangler": "workspace:*" }, "peerDependencies": { - "unenv": "npm:unenv-nightly@*", + "unenv": "2.0.0-rc.0", "workerd": "^1.20241230.0" }, "peerDependenciesMeta": { diff --git a/packages/unenv-preset/tests/worker/index.ts b/packages/unenv-preset/tests/worker/index.ts index 62cf9085a80cf..d851ca38dd8fb 100644 --- a/packages/unenv-preset/tests/worker/index.ts +++ b/packages/unenv-preset/tests/worker/index.ts @@ -9,6 +9,7 @@ export const TESTS = { testUtilImplements, testPath, testDns, + testTimers, }; export default { @@ -141,3 +142,11 @@ async function testDns() { assert.strictEqual(results[0].critical, 0); assert.strictEqual(results[0].issue, "pki.goog"); } + +async function testTimers() { + const timers = await import("node:timers"); + const timeout = timers.setTimeout(() => null, 1000); + // active is deprecated and no more in the type + (timers as any).active(timeout); + timers.clearTimeout(timeout); +} diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index dbed25d882bdf..128aeef01ebf8 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -76,7 +76,7 @@ "esbuild": "0.17.19", "miniflare": "workspace:*", "path-to-regexp": "6.3.0", - "unenv": "npm:unenv-nightly@2.0.0-20250109-100802-88ad671", + "unenv": "2.0.0-rc.0", "workerd": "1.20241230.0" }, "devDependencies": { diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index eea34c2388924..9eabca92bb7c3 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -9786,7 +9786,7 @@ addEventListener('fetch', event => {});` ) ) ).resolves.toMatchInlineSnapshot(` - "X [ERROR] Unexpected external import of \\"node:stream\\" and \\"node:timers/promises\\". + "X [ERROR] Unexpected external import of \\"node:events\\", \\"node:net\\", \\"node:stream\\", \\"node:timers/promises\\", and \\"node:tty\\". Your worker has no default export, which means it is assumed to be a Service Worker format Worker. Did you mean to create a ES Module format Worker? If so, try adding \`export default { ... }\` in your entry-point. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24da27428806e..4afa65dc96103 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1666,8 +1666,8 @@ importers: packages/unenv-preset: dependencies: unenv: - specifier: npm:unenv-nightly@* - version: unenv-nightly@2.0.0-20250109-100802-88ad671 + specifier: 2.0.0-rc.0 + version: 2.0.0-rc.0 workerd: specifier: ^1.20241230.0 version: 1.20241230.0 @@ -2502,8 +2502,8 @@ importers: specifier: 6.3.0 version: 6.3.0 unenv: - specifier: npm:unenv-nightly@2.0.0-20250109-100802-88ad671 - version: unenv-nightly@2.0.0-20250109-100802-88ad671 + specifier: 2.0.0-rc.0 + version: 2.0.0-rc.0 workerd: specifier: 1.20241230.0 version: 1.20241230.0 @@ -10366,8 +10366,8 @@ packages: unenv-nightly@2.0.0-20241218-183400-5d6aec3: resolution: {integrity: sha512-7Xpi29CJRbOV1/IrC03DawMJ0hloklDLq/cigSe+J2jkcC+iDres2Cy0r4ltj5f0x7DqsaGaB4/dLuCPPFZnZA==} - unenv-nightly@2.0.0-20250109-100802-88ad671: - resolution: {integrity: sha512-Uij6gODNNNNsNBoDlnaMvZI99I6YlVJLRfYH8AOLMlbFrW7k2w872v9VLuIdch2vF8QBeSC4EftIh5sG4ibzdA==} + unenv@2.0.0-rc.0: + resolution: {integrity: sha512-H0kl2w8jFL/FAk0xvjVing4bS3jd//mbg1QChDnn58l9Sc5RtduaKmLAL8n+eBw5jJo8ZjYV7CrEGage5LAOZQ==} unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} @@ -19659,10 +19659,10 @@ snapshots: pathe: 1.1.2 ufo: 1.5.4 - unenv-nightly@2.0.0-20250109-100802-88ad671: + unenv@2.0.0-rc.0: dependencies: defu: 6.1.4 - mlly: 1.7.3 + mlly: 1.7.4 ohash: 1.1.4 pathe: 1.1.2 ufo: 1.5.4 From fcaa02cdf4f3f648d7218e8f7fb411a2324eebb5 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 20 Jan 2025 16:05:57 +0100 Subject: [PATCH 06/24] fix(wrangler): fix schema defaults (#7821) --- .changeset/dry-snakes-deliver.md | 5 ++ .../src/__tests__/config/configSchema.test.ts | 17 +++++ packages/wrangler/src/config/environment.ts | 70 ++++++++++--------- 3 files changed, 58 insertions(+), 34 deletions(-) create mode 100644 .changeset/dry-snakes-deliver.md create mode 100644 packages/wrangler/src/__tests__/config/configSchema.test.ts diff --git a/.changeset/dry-snakes-deliver.md b/.changeset/dry-snakes-deliver.md new file mode 100644 index 0000000000000..b8f13f6885379 --- /dev/null +++ b/.changeset/dry-snakes-deliver.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +fix(wrangler): fix wrangler config schema defaults diff --git a/packages/wrangler/src/__tests__/config/configSchema.test.ts b/packages/wrangler/src/__tests__/config/configSchema.test.ts new file mode 100644 index 0000000000000..c969034a827fe --- /dev/null +++ b/packages/wrangler/src/__tests__/config/configSchema.test.ts @@ -0,0 +1,17 @@ +import fs from "node:fs"; +import path from "node:path"; + +describe("src/config/environment.ts", () => { + // `@default` values must not be escaped in order to generate a valid schema. + test("default values are not escaped", () => { + const srcFile = path.join(__dirname, "../../config/environment.ts"); + const srcLines = fs.readFileSync(srcFile, "utf-8").split("\n"); + const hasEscapedDefaultRegex = /@default\s+`/; + srcLines.forEach((line, lineNumber) => { + const hasEscapedDefault = hasEscapedDefaultRegex.test(line); + expect + .soft(hasEscapedDefault, `On line ${lineNumber + 1}: "${line}"`) + .toEqual(false); + }); + }); +}); diff --git a/packages/wrangler/src/config/environment.ts b/packages/wrangler/src/config/environment.ts index 56f309928f7b2..b11b42895dbfc 100644 --- a/packages/wrangler/src/config/environment.ts +++ b/packages/wrangler/src/config/environment.ts @@ -119,11 +119,11 @@ interface EnvironmentInheritable { /** * A list of flags that enable features from upcoming features of - * the Workers runtime, usually used together with compatibility_flags. + * the Workers runtime, usually used together with compatibility_date. * - * More details at https://developers.cloudflare.com/workers/platform/compatibility-dates + * More details at https://developers.cloudflare.com/workers/platform/compatibility-flags * - * @default `[]` + * @default [] * @inheritable */ compatibility_flags: string[]; @@ -162,23 +162,25 @@ interface EnvironmentInheritable { */ base_dir: string | undefined; + // Carmen according to our tests the default is undefined + // warning: you must force "workers_dev: true" in tests to match expected behavior /** * Whether we use ..workers.dev to * test and deploy your Worker. * - * // Carmen according to our tests the default is undefined - * // warning: you must force "workers_dev: true" in tests to match expected behavior - * @default `true` (This is a breaking change from Wrangler v1) + * + * @default true * @breaking * @inheritable */ + workers_dev: boolean | undefined; /** * Whether we use -..workers.dev to * serve Preview URLs for your Worker. * - * @default `true` + * @default true * @inheritable */ preview_urls: boolean | undefined; @@ -214,7 +216,7 @@ interface EnvironmentInheritable { /** * The function to use to replace jsx syntax. * - * @default `"React.createElement"` + * @default "React.createElement" * @inheritable */ jsx_factory: string; @@ -222,7 +224,7 @@ interface EnvironmentInheritable { /** * The function to use to replace jsx fragment syntax. * - * @default `"React.Fragment"` + * @default "React.Fragment" * @inheritable */ jsx_fragment: string; @@ -246,7 +248,7 @@ interface EnvironmentInheritable { * * More details here https://developers.cloudflare.com/workers/platform/cron-triggers * - * @default `{crons:[]}` + * @default {crons:[]} * @inheritable */ triggers: { crons: string[] }; @@ -340,7 +342,7 @@ interface EnvironmentInheritable { /** * List of bindings that you will send to logfwdr * - * @default `{bindings:[]}` + * @default {bindings:[]} * @inheritable */ logfwdr: { @@ -432,7 +434,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `{}` + * @default {} * @nonInheritable */ define: Record; @@ -442,7 +444,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `{}` + * @default {} * @nonInheritable */ vars: Record; @@ -456,7 +458,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `{bindings:[]}` + * @default {bindings:[]} * @nonInheritable */ durable_objects: { @@ -469,7 +471,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ workflows: WorkflowBinding[]; @@ -480,7 +482,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `{}` + * @default {} * @nonInheritable */ cloudchamber: CloudchamberConfig; @@ -495,7 +497,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `{}` + * @default {} * @nonInheritable */ app: ContainerApp[]; @@ -511,7 +513,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ kv_namespaces: { @@ -529,7 +531,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ send_email: { @@ -547,7 +549,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `{consumers:[],producers:[]}` + * @default {consumers:[],producers:[]} * @nonInheritable */ queues: { @@ -600,7 +602,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ r2_buckets: { @@ -620,7 +622,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ d1_databases: { @@ -646,7 +648,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ vectorize: { @@ -662,7 +664,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ hyperdrive: { @@ -680,7 +682,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ services: @@ -702,7 +704,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ analytics_engine_datasets: { @@ -718,7 +720,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `{}` + * @default {} * @nonInheritable */ browser: @@ -733,7 +735,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `{}` + * @default {} * @nonInheritable */ ai: @@ -758,7 +760,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `{}` + * @default {} * @nonInheritable */ unsafe: { @@ -803,7 +805,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ mtls_certificates: { @@ -819,7 +821,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ tail_consumers?: TailConsumer[]; @@ -830,7 +832,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ dispatch_namespaces: { @@ -848,7 +850,7 @@ export interface EnvironmentNonInheritable { * NOTE: This field is not automatically inherited from the top level environment, * and so must be specified in every named environment. * - * @default `[]` + * @default [] * @nonInheritable */ pipelines: { @@ -882,7 +884,7 @@ interface EnvironmentDeprecated { /** * A list of services that your Worker should be bound to. * - * @default `[]` + * @default [] * @deprecated DO NOT USE. We'd added this to test the new service binding system, but the proper way to test experimental features is to use `unsafe.bindings` configuration. */ experimental_services?: { From 794b44606dc1e157ad2447a9e580578b79567678 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:54:58 +0000 Subject: [PATCH 07/24] [C3] Bump @angular/create from 19.0.7 to 19.1.2 in /packages/create-cloudflare/src/frameworks (#7824) * [C3] Bump @angular/create in /packages/create-cloudflare/src/frameworks Bumps [@angular/create](https://github.com/angular/angular-cli) from 19.0.7 to 19.1.2. - [Release notes](https://github.com/angular/angular-cli/releases) - [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular-cli/compare/19.0.7...19.1.2) --- updated-dependencies: - dependency-name: "@angular/create" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * chore: update dependencies of "create-cloudflare" package The following dependency versions have been updated: | Dependency | From | To | | --------------- | ------ | ------ | | @angular/create | 19.0.7 | 19.1.2 | --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Wrangler automated PR updater --- .changeset/c3-frameworks-update-7824.md | 11 +++++++++++ .../create-cloudflare/src/frameworks/package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .changeset/c3-frameworks-update-7824.md diff --git a/.changeset/c3-frameworks-update-7824.md b/.changeset/c3-frameworks-update-7824.md new file mode 100644 index 0000000000000..a34b11c36fd97 --- /dev/null +++ b/.changeset/c3-frameworks-update-7824.md @@ -0,0 +1,11 @@ +--- +"create-cloudflare": patch +--- + +chore: update dependencies of "create-cloudflare" package + +The following dependency versions have been updated: + +| Dependency | From | To | +| --------------- | ------ | ------ | +| @angular/create | 19.0.7 | 19.1.2 | diff --git a/packages/create-cloudflare/src/frameworks/package.json b/packages/create-cloudflare/src/frameworks/package.json index 7f0c9344106dd..d5c8e4fb22e37 100644 --- a/packages/create-cloudflare/src/frameworks/package.json +++ b/packages/create-cloudflare/src/frameworks/package.json @@ -7,7 +7,7 @@ "dependencies": { "create-astro": "4.11.0", "create-analog": "1.8.1", - "@angular/create": "19.0.7", + "@angular/create": "19.1.2", "create-docusaurus": "3.7.0", "create-hono": "0.14.3", "create-next-app": "15.1.4", From 4f84172314d217cce94f1e071bb0a300f8d787c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:55:31 +0000 Subject: [PATCH 08/24] [C3] Bump create-next-app from 15.1.4 to 15.1.5 in /packages/create-cloudflare/src/frameworks (#7823) * [C3] Bump create-next-app in /packages/create-cloudflare/src/frameworks Bumps [create-next-app](https://github.com/vercel/next.js/tree/HEAD/packages/create-next-app) from 15.1.4 to 15.1.5. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/commits/v15.1.5/packages/create-next-app) --- updated-dependencies: - dependency-name: create-next-app dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore: update dependencies of "create-cloudflare" package The following dependency versions have been updated: | Dependency | From | To | | --------------- | ------ | ------ | | create-next-app | 15.1.4 | 15.1.5 | --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Wrangler automated PR updater --- .changeset/c3-frameworks-update-7823.md | 11 +++++++++++ .../create-cloudflare/src/frameworks/package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .changeset/c3-frameworks-update-7823.md diff --git a/.changeset/c3-frameworks-update-7823.md b/.changeset/c3-frameworks-update-7823.md new file mode 100644 index 0000000000000..7aca3c4550053 --- /dev/null +++ b/.changeset/c3-frameworks-update-7823.md @@ -0,0 +1,11 @@ +--- +"create-cloudflare": patch +--- + +chore: update dependencies of "create-cloudflare" package + +The following dependency versions have been updated: + +| Dependency | From | To | +| --------------- | ------ | ------ | +| create-next-app | 15.1.4 | 15.1.5 | diff --git a/packages/create-cloudflare/src/frameworks/package.json b/packages/create-cloudflare/src/frameworks/package.json index d5c8e4fb22e37..c8b29c7ed5685 100644 --- a/packages/create-cloudflare/src/frameworks/package.json +++ b/packages/create-cloudflare/src/frameworks/package.json @@ -10,7 +10,7 @@ "@angular/create": "19.1.2", "create-docusaurus": "3.7.0", "create-hono": "0.14.3", - "create-next-app": "15.1.4", + "create-next-app": "15.1.5", "create-qwik": "1.12.0", "create-vite": "6.1.1", "create-remix": "2.15.2", From c6e8a1cccd6355fc2c4864fb1bc7b6da1c97704d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:55:54 +0000 Subject: [PATCH 09/24] [C3] Bump sv from 0.6.11 to 0.6.13 in /packages/create-cloudflare/src/frameworks (#7822) * [C3] Bump sv in /packages/create-cloudflare/src/frameworks Bumps [sv](https://github.com/sveltejs/cli/tree/HEAD/packages/cli) from 0.6.11 to 0.6.13. - [Release notes](https://github.com/sveltejs/cli/releases) - [Changelog](https://github.com/sveltejs/cli/blob/main/packages/cli/CHANGELOG.md) - [Commits](https://github.com/sveltejs/cli/commits/sv@0.6.13/packages/cli) --- updated-dependencies: - dependency-name: sv dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * chore: update dependencies of "create-cloudflare" package The following dependency versions have been updated: | Dependency | From | To | | ---------- | ------ | ------ | | sv | 0.6.11 | 0.6.13 | --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Wrangler automated PR updater --- .changeset/c3-frameworks-update-7822.md | 11 +++++++++++ .../create-cloudflare/src/frameworks/package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .changeset/c3-frameworks-update-7822.md diff --git a/.changeset/c3-frameworks-update-7822.md b/.changeset/c3-frameworks-update-7822.md new file mode 100644 index 0000000000000..aa84aef17d376 --- /dev/null +++ b/.changeset/c3-frameworks-update-7822.md @@ -0,0 +1,11 @@ +--- +"create-cloudflare": patch +--- + +chore: update dependencies of "create-cloudflare" package + +The following dependency versions have been updated: + +| Dependency | From | To | +| ---------- | ------ | ------ | +| sv | 0.6.11 | 0.6.13 | diff --git a/packages/create-cloudflare/src/frameworks/package.json b/packages/create-cloudflare/src/frameworks/package.json index c8b29c7ed5685..b70a3c9962aef 100644 --- a/packages/create-cloudflare/src/frameworks/package.json +++ b/packages/create-cloudflare/src/frameworks/package.json @@ -17,7 +17,7 @@ "create-solid": "0.5.14", "create-vue": "3.13.0", "gatsby": "5.14.1", - "sv": "0.6.11", + "sv": "0.6.13", "nuxi": "3.20.0" } } From a225f8a2843629c1e31aee4a012655437220880e Mon Sep 17 00:00:00 2001 From: emily-shen <69125074+emily-shen@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:56:42 +0000 Subject: [PATCH 10/24] tail refactor (#7818) --- packages/wrangler/src/index.ts | 10 +- packages/wrangler/src/tail/index.ts | 360 ++++++++++++++-------------- 2 files changed, 186 insertions(+), 184 deletions(-) diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 361614f6e2475..18ce144df5b1a 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -138,7 +138,7 @@ import { closeSentry, setupSentry, } from "./sentry"; -import { tailHandler, tailOptions } from "./tail"; +import { tailCommand } from "./tail"; import registerTriggersSubcommands from "./triggers"; import { typesHandler, typesOptions } from "./type-generation"; import { getAuthFromEnv } from "./user"; @@ -460,12 +460,8 @@ export function createCLIParser(argv: string[]) { ); // tail - wrangler.command( - "tail [worker]", - "🦚 Start a log tailing session for a Worker", - tailOptions, - tailHandler - ); + registry.define([{ command: "wrangler tail", definition: tailCommand }]); + registry.registerNamespace("tail"); // secret wrangler.command( diff --git a/packages/wrangler/src/tail/index.ts b/packages/wrangler/src/tail/index.ts index 0584f2cb0e99b..a2fdf30b00aeb 100644 --- a/packages/wrangler/src/tail/index.ts +++ b/packages/wrangler/src/tail/index.ts @@ -1,6 +1,7 @@ import { setTimeout } from "node:timers/promises"; import onExit from "signal-exit"; -import { configFileName, readConfig } from "../config"; +import { configFileName } from "../config"; +import { createCommand } from "../core/create-command"; import { createFatalError, UserError } from "../errors"; import { logger } from "../logger"; import * as metrics from "../metrics"; @@ -15,228 +16,233 @@ import { prettyPrintLogs, translateCLICommandToFilterMessage, } from "./createTail"; -import type { - CommonYargsArgv, - StrictYargsOptionsToInterface, -} from "../yargs-types"; import type { TailCLIFilters } from "./createTail"; import type WebSocket from "ws"; -export function tailOptions(yargs: CommonYargsArgv) { - return yargs - .positional("worker", { +export const tailCommand = createCommand({ + metadata: { + description: "🦚 Start a log tailing session for a Worker", + status: "stable", + owner: "Workers: Workers Observability", + }, + positionalArgs: ["worker"], + args: { + worker: { describe: "Name or route of the worker to tail", type: "string", - }) - .option("format", { - default: process.stdout.isTTY ? "pretty" : "json", + }, + format: { choices: ["json", "pretty"], describe: "The format of log entries", - }) - .option("status", { + }, + status: { choices: ["ok", "error", "canceled"], describe: "Filter by invocation status", array: true, - }) - .option("header", { + }, + header: { type: "string", requiresArg: true, describe: "Filter by HTTP header", - }) - .option("method", { + }, + method: { type: "string", requiresArg: true, describe: "Filter by HTTP method", array: true, - }) - .option("sampling-rate", { + }, + "sampling-rate": { type: "number", describe: "Adds a percentage of requests to log sampling rate", - }) - .option("search", { + }, + search: { type: "string", requiresArg: true, describe: "Filter by a text match in console.log messages", - }) - .option("ip", { + }, + ip: { type: "string", requiresArg: true, describe: 'Filter by the IP address the request originates from. Use "self" to filter for your own IP', array: true, - }) - .option("version-id", { + }, + "version-id": { type: "string", requiresArg: true, describe: "Filter by Worker version", - }) - .option("debug", { + }, + debug: { type: "boolean", hidden: true, default: false, describe: "If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.", - }) - .option("legacy-env", { + }, + "legacy-env": { type: "boolean", describe: "Use legacy environments", hidden: true, - }); -} - -type TailArgs = StrictYargsOptionsToInterface; - -export async function tailHandler(args: TailArgs) { - if (args.format === "pretty") { - await printWranglerBanner(); - } - const config = readConfig(args); - if (config.pages_build_output_dir) { - throw new UserError( - "It looks like you've run a Workers-specific command in a Pages project.\n" + - "For Pages, please run `wrangler pages deployment tail` instead." - ); - } - metrics.sendMetricsEvent("begin log stream", { - sendMetrics: config.send_metrics, - }); - - let scriptName; - - const accountId = await requireAuth(config); - - // Worker names can't contain "." (and most routes should), so use that as a discriminator - if (args.worker?.includes(".")) { - scriptName = await getWorkerForZone( - { - worker: args.worker, - accountId, - }, - config.configPath - ); + }, + }, + behaviour: { + printBanner: false, + }, + async handler(args, { config }) { + args.format ??= process.stdout.isTTY ? "pretty" : "json"; if (args.format === "pretty") { - logger.log(`Connecting to worker ${scriptName} at route ${args.worker}`); + await printWranglerBanner(); } - } else { - scriptName = getLegacyScriptName({ name: args.worker, ...args }, config); - } - if (!scriptName) { - throw new UserError( - `Required Worker name missing. Please specify the Worker name in your ${configFileName(config.configPath)} file, or pass it as an argument with \`wrangler tail \`` - ); - } - - const cliFilters: TailCLIFilters = { - status: args.status as ("ok" | "error" | "canceled")[] | undefined, - header: args.header, - method: args.method, - samplingRate: args.samplingRate, - search: args.search, - clientIp: args.ip, - versionId: args.versionId, - }; - - const filters = translateCLICommandToFilterMessage(cliFilters); - - const { tail, expiration, deleteTail } = await createTail( - accountId, - scriptName, - filters, - args.debug, - !isLegacyEnv(config) ? args.env : undefined - ); - - const scriptDisplayName = `${scriptName}${ - args.env && !isLegacyEnv(config) ? ` (${args.env})` : "" - }`; - - if (args.format === "pretty") { - logger.log( - `Successfully created tail, expires at ${expiration.toLocaleString()}` - ); - } - - const printLog: (data: WebSocket.RawData) => void = - args.format === "pretty" ? prettyPrintLogs : jsonPrintLogs; - - tail.on("message", printLog); - - while (tail.readyState !== tail.OPEN) { - switch (tail.readyState) { - case tail.CONNECTING: - await setTimeout(100); - break; - case tail.CLOSING: - await setTimeout(100); - break; - case tail.CLOSED: - metrics.sendMetricsEvent("end log stream", { - sendMetrics: config.send_metrics, - }); - throw new Error( - `Connection to ${scriptDisplayName} closed unexpectedly.` - ); + if (config.pages_build_output_dir) { + throw new UserError( + "It looks like you've run a Workers-specific command in a Pages project.\n" + + "For Pages, please run `wrangler pages deployment tail` instead." + ); } - } - - if (args.format === "pretty") { - logger.log(`Connected to ${scriptDisplayName}, waiting for logs...`); - } - - const cancelPing = startWebSocketPing(); - tail.on("close", exit); - onExit(exit); - - async function exit() { - cancelPing(); - tail.terminate(); - await deleteTail(); - metrics.sendMetricsEvent("end log stream", { + metrics.sendMetricsEvent("begin log stream", { sendMetrics: config.send_metrics, }); - } - - /** - * Start pinging the websocket to see if it is still connected. - * - * We need to know if the connection to the tail drops. - * To do this we send a ping message to the backend every few seconds. - * If we don't get a matching pong message back before the next ping is due - * then we have probably lost the connect. - */ - function startWebSocketPing() { - /** The corelation message to send to tail when pinging. */ - const PING_MESSAGE = Buffer.from("wrangler tail ping"); - /** How long to wait between pings. */ - const PING_INTERVAL = 10000; - - let waitingForPong = false; - - const pingInterval = setInterval(() => { - if (waitingForPong) { - // We didn't get a pong back quickly enough so assume the connection died and exit. - // This approach relies on the fact that throwing an error inside a `setInterval()` callback - // causes the process to exit. - // This is a bit nasty but otherwise we have to make wholesale changes to how the `tail` command - // works, since currently all the tests assume that `runWrangler()` will return immediately. - console.log(args.format); - throw createFatalError( - "Tail disconnected, exiting.", - args.format === "json", - 1 + + let scriptName; + + const accountId = await requireAuth(config); + + // Worker names can't contain "." (and most routes should), so use that as a discriminator + if (args.worker?.includes(".")) { + scriptName = await getWorkerForZone( + { + worker: args.worker, + accountId, + }, + config.configPath + ); + if (args.format === "pretty") { + logger.log( + `Connecting to worker ${scriptName} at route ${args.worker}` ); } - waitingForPong = true; - tail.ping(PING_MESSAGE); - }, PING_INTERVAL); + } else { + scriptName = getLegacyScriptName({ name: args.worker, ...args }, config); + } - tail.on("pong", (data) => { - if (data.equals(PING_MESSAGE)) { - waitingForPong = false; + if (!scriptName) { + throw new UserError( + `Required Worker name missing. Please specify the Worker name in your ${configFileName(config.configPath)} file, or pass it as an argument with \`wrangler tail \`` + ); + } + + const cliFilters: TailCLIFilters = { + status: args.status as ("ok" | "error" | "canceled")[] | undefined, + header: args.header, + method: args.method, + samplingRate: args.samplingRate, + search: args.search, + clientIp: args.ip, + versionId: args.versionId, + }; + + const filters = translateCLICommandToFilterMessage(cliFilters); + + const { tail, expiration, deleteTail } = await createTail( + accountId, + scriptName, + filters, + args.debug, + !isLegacyEnv(config) ? args.env : undefined + ); + + const scriptDisplayName = `${scriptName}${ + args.env && !isLegacyEnv(config) ? ` (${args.env})` : "" + }`; + + if (args.format === "pretty") { + logger.log( + `Successfully created tail, expires at ${expiration.toLocaleString()}` + ); + } + + const printLog: (data: WebSocket.RawData) => void = + args.format === "pretty" ? prettyPrintLogs : jsonPrintLogs; + + tail.on("message", printLog); + + while (tail.readyState !== tail.OPEN) { + switch (tail.readyState) { + case tail.CONNECTING: + await setTimeout(100); + break; + case tail.CLOSING: + await setTimeout(100); + break; + case tail.CLOSED: + metrics.sendMetricsEvent("end log stream", { + sendMetrics: config.send_metrics, + }); + throw new Error( + `Connection to ${scriptDisplayName} closed unexpectedly.` + ); } - }); + } - return () => clearInterval(pingInterval); - } -} + if (args.format === "pretty") { + logger.log(`Connected to ${scriptDisplayName}, waiting for logs...`); + } + + const cancelPing = startWebSocketPing(); + tail.on("close", exit); + onExit(exit); + + async function exit() { + cancelPing(); + tail.terminate(); + await deleteTail(); + metrics.sendMetricsEvent("end log stream", { + sendMetrics: config.send_metrics, + }); + } + + /** + * Start pinging the websocket to see if it is still connected. + * + * We need to know if the connection to the tail drops. + * To do this we send a ping message to the backend every few seconds. + * If we don't get a matching pong message back before the next ping is due + * then we have probably lost the connect. + */ + function startWebSocketPing() { + /** The corelation message to send to tail when pinging. */ + const PING_MESSAGE = Buffer.from("wrangler tail ping"); + /** How long to wait between pings. */ + const PING_INTERVAL = 10000; + + let waitingForPong = false; + + const pingInterval = setInterval(() => { + if (waitingForPong) { + // We didn't get a pong back quickly enough so assume the connection died and exit. + // This approach relies on the fact that throwing an error inside a `setInterval()` callback + // causes the process to exit. + // This is a bit nasty but otherwise we have to make wholesale changes to how the `tail` command + // works, since currently all the tests assume that `runWrangler()` will return immediately. + console.log(args.format); + throw createFatalError( + "Tail disconnected, exiting.", + args.format === "json", + 1 + ); + } + waitingForPong = true; + tail.ping(PING_MESSAGE); + }, PING_INTERVAL); + + tail.on("pong", (data) => { + if (data.equals(PING_MESSAGE)) { + waitingForPong = false; + } + }); + + return () => clearInterval(pingInterval); + } + }, +}); From f6cc0293d3a6bf45a323b6d9718b7162149cc84f Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Mon, 20 Jan 2025 16:28:42 +0000 Subject: [PATCH 11/24] add support for assets bindings to `getPlatformProxy` (#7816) --- .changeset/thick-dots-sit.md | 39 +++++++++++++++++++ fixtures/get-platform-proxy/public/test.txt | 1 + .../tests/get-platform-proxy.env.test.ts | 18 ++++++++- fixtures/get-platform-proxy/wrangler.toml | 6 +++ .../src/api/integrations/platform/index.ts | 9 +++++ 5 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .changeset/thick-dots-sit.md create mode 100644 fixtures/get-platform-proxy/public/test.txt diff --git a/.changeset/thick-dots-sit.md b/.changeset/thick-dots-sit.md new file mode 100644 index 0000000000000..9e4c2a7aaa6da --- /dev/null +++ b/.changeset/thick-dots-sit.md @@ -0,0 +1,39 @@ +--- +"wrangler": patch +--- + +add support for assets bindings to `getPlatformProxy` + +this change makes sure that that `getPlatformProxy`, when the input configuration +file contains an assets field, correctly returns the appropriate asset binding proxy + +example: + +```json +// wrangler.json +{ + "name": "my-worker", + "assets": { + "directory": "./public/", + "binding": "ASSETS" + }, + "vars": { + "MY_VAR": "my-var" + } +} +``` + +```js +import { getPlatformProxy } from "wrangler"; + +const { env } = await getPlatformProxy(); + +if (env.ASSETS) { + const text = await ( + await p.env.ASSETS.fetch("http://0.0.0.0/file.txt") + ).text(); + console.log(text); // logs the content of file.txt +} + +p.dispose(); +``` diff --git a/fixtures/get-platform-proxy/public/test.txt b/fixtures/get-platform-proxy/public/test.txt new file mode 100644 index 0000000000000..2dd981b4d6888 --- /dev/null +++ b/fixtures/get-platform-proxy/public/test.txt @@ -0,0 +1 @@ +this is a test text file! diff --git a/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts b/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts index 4fb22fb13e35c..8bb8246f887ad 100644 --- a/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts +++ b/fixtures/get-platform-proxy/tests/get-platform-proxy.env.test.ts @@ -2,7 +2,11 @@ import path from "path"; import { D1Database, R2Bucket } from "@cloudflare/workers-types"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { getPlatformProxy } from "./shared"; -import type { Hyperdrive, KVNamespace } from "@cloudflare/workers-types"; +import type { + Fetcher, + Hyperdrive, + KVNamespace, +} from "@cloudflare/workers-types"; import type { Unstable_DevWorker } from "wrangler"; type Env = { @@ -15,6 +19,7 @@ type Env = { MY_BUCKET: R2Bucket; MY_D1: D1Database; MY_HYPERDRIVE: Hyperdrive; + ASSETS: Fetcher; }; const wranglerTomlFilePath = path.join(__dirname, "..", "wrangler.toml"); @@ -115,6 +120,17 @@ describe("getPlatformProxy - env", () => { } }); + it("correctly obtains functioning ASSETS bindings", async () => { + const { env, dispose } = await getPlatformProxy({ + configPath: wranglerTomlFilePath, + }); + const { ASSETS } = env; + const res = await ASSETS.fetch("https://0.0.0.0/test.txt"); + const text = await res.text(); + expect(text).toEqual("this is a test text file!\n"); + await dispose(); + }); + it("correctly obtains functioning KV bindings", async () => { const { env, dispose } = await getPlatformProxy({ configPath: wranglerTomlFilePath, diff --git a/fixtures/get-platform-proxy/wrangler.toml b/fixtures/get-platform-proxy/wrangler.toml index 1b94a0034f1ea..fff309658ab95 100644 --- a/fixtures/get-platform-proxy/wrangler.toml +++ b/fixtures/get-platform-proxy/wrangler.toml @@ -7,6 +7,12 @@ MY_VAR = "my-var-value" MY_VAR_A = "my-var-a" MY_JSON_VAR = { test = true } +[assets] +directory = "./public" +binding = "ASSETS" +html_handling = "auto-trailing-slash" +not_found_handling = "none" + [[kv_namespaces]] binding = "MY_KV" id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" diff --git a/packages/wrangler/src/api/integrations/platform/index.ts b/packages/wrangler/src/api/integrations/platform/index.ts index d6b78c38f7bd2..2f19687b9cbc7 100644 --- a/packages/wrangler/src/api/integrations/platform/index.ts +++ b/packages/wrangler/src/api/integrations/platform/index.ts @@ -158,6 +158,14 @@ async function getMiniflareOptionsFromConfig( migrations: rawConfig.migrations, }); + const processedAssetOptions = getAssetsOptions( + { assets: undefined }, + rawConfig + ); + const assetOptions = processedAssetOptions + ? buildAssetOptions({ assets: processedAssetOptions }) + : {}; + const persistOptions = getMiniflarePersistOptions(options.persist); const serviceBindings = await getServiceBindings(bindings.services); @@ -172,6 +180,7 @@ async function getMiniflareOptionsFromConfig( ...serviceBindings, ...bindingOptions.serviceBindings, }, + ...assetOptions, }, ...externalWorkers, ], From 0f88d85fcbaaecafa62b960b7b569d80d72420dc Mon Sep 17 00:00:00 2001 From: emily-shen <69125074+emily-shen@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:12:41 +0000 Subject: [PATCH 12/24] types refactor (#7817) --- packages/wrangler/src/index.ts | 10 +- .../wrangler/src/type-generation/index.ts | 239 +++++++++--------- 2 files changed, 123 insertions(+), 126 deletions(-) diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 18ce144df5b1a..5b6afce6db28b 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -140,7 +140,7 @@ import { } from "./sentry"; import { tailCommand } from "./tail"; import registerTriggersSubcommands from "./triggers"; -import { typesHandler, typesOptions } from "./type-generation"; +import { typesCommand } from "./type-generation"; import { getAuthFromEnv } from "./user"; import { loginCommand, logoutCommand, whoamiCommand } from "./user/commands"; import { whoami } from "./user/whoami"; @@ -473,12 +473,8 @@ export function createCLIParser(argv: string[]) { ); // types - wrangler.command( - "types [path]", - "📝 Generate types from bindings and module rules in configuration\n", - typesOptions, - typesHandler - ); + registry.define([{ command: "wrangler types", definition: typesCommand }]); + registry.registerNamespace("types"); /******************** CMD GROUP ***********************/ registry.define([ diff --git a/packages/wrangler/src/type-generation/index.ts b/packages/wrangler/src/type-generation/index.ts index 04b24ea6f9725..57a0848ea602f 100644 --- a/packages/wrangler/src/type-generation/index.ts +++ b/packages/wrangler/src/type-generation/index.ts @@ -2,159 +2,160 @@ import * as fs from "node:fs"; import { basename, dirname, extname, join, relative, resolve } from "node:path"; import { findUpSync } from "find-up"; import { getNodeCompat } from "miniflare"; -import { experimental_readRawConfig, readConfig } from "../config"; +import { experimental_readRawConfig } from "../config"; +import { createCommand } from "../core/create-command"; import { getEntry } from "../deployment-bundle/entry"; import { getVarsForDev } from "../dev/dev-vars"; import { CommandLineArgsError, UserError } from "../errors"; import { logger } from "../logger"; import { parseJSONC } from "../parse"; -import { printWranglerBanner } from "../wrangler-banner"; import { generateRuntimeTypes } from "./runtime"; import { logRuntimeTypesMessage } from "./runtime/log-runtime-types-message"; import type { Config, RawEnvironment } from "../config"; import type { Entry } from "../deployment-bundle/entry"; import type { CfScriptFormat } from "../deployment-bundle/worker"; -import type { - CommonYargsArgv, - StrictYargsOptionsToInterface, -} from "../yargs-types"; - -export function typesOptions(yargs: CommonYargsArgv) { - return yargs - .positional("path", { + +export const typesCommand = createCommand({ + metadata: { + description: + "📝 Generate types from bindings and module rules in configuration\n", + status: "stable", + owner: "Workers: Authoring and Testing", + }, + positionalArgs: ["path"], + args: { + path: { describe: "The path to the declaration file to generate", type: "string", default: "worker-configuration.d.ts", demandOption: false, - }) - .option("env-interface", { + }, + "env-interface": { type: "string", default: "Env", describe: "The name of the generated environment interface", requiresArg: true, - }) - .option("experimental-include-runtime", { + }, + "experimental-include-runtime": { alias: "x-include-runtime", type: "string", describe: "The path of the generated runtime types file", demandOption: false, - }) - .option("strict-vars", { + }, + "strict-vars": { type: "boolean", default: true, describe: "Generate literal and union types for variables", - }); -} + }, + }, + validateArgs(args) { + const { envInterface, path: outputPath } = args; -export async function typesHandler( - args: StrictYargsOptionsToInterface -) { - const { envInterface, path: outputPath } = args; + const validInterfaceRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/; - const validInterfaceRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/; + if (!validInterfaceRegex.test(envInterface)) { + throw new CommandLineArgsError( + `The provided env-interface value ("${envInterface}") does not satisfy the validation regex: ${validInterfaceRegex}` + ); + } - if (!validInterfaceRegex.test(envInterface)) { - throw new CommandLineArgsError( - `The provided env-interface value ("${envInterface}") does not satisfy the validation regex: ${validInterfaceRegex}` - ); - } + if (!outputPath.endsWith(".d.ts")) { + throw new CommandLineArgsError( + `The provided path value ("${outputPath}") does not point to a declaration file (please use the 'd.ts' extension)` + ); + } + }, + async handler(args, { config }) { + const { envInterface, path: outputPath } = args; - if (!outputPath.endsWith(".d.ts")) { - throw new CommandLineArgsError( - `The provided path value ("${outputPath}") does not point to a declaration file (please use the 'd.ts' extension)` - ); - } + if ( + !config.configPath || + !fs.existsSync(config.configPath) || + fs.statSync(config.configPath).isDirectory() + ) { + logger.warn( + `No config file detected${ + args.config ? ` (at ${args.config})` : "" + }, aborting` + ); + return; + } - await printWranglerBanner(); + // args.xRuntime will be a string if the user passes "--x-include-runtime" or "--x-include-runtime=..." + if (typeof args.experimentalIncludeRuntime === "string") { + logger.log(`Generating runtime types...`); + + const { outFile } = await generateRuntimeTypes({ + config, + outFile: args.experimentalIncludeRuntime || undefined, + }); + + const tsconfigPath = + config.tsconfig ?? join(dirname(config.configPath), "tsconfig.json"); + const tsconfigTypes = readTsconfigTypes(tsconfigPath); + const { mode } = getNodeCompat( + config.compatibility_date, + config.compatibility_flags, + { + nodeCompat: config.node_compat, + } + ); - const config = readConfig(args); - if ( - !config.configPath || - !fs.existsSync(config.configPath) || - fs.statSync(config.configPath).isDirectory() - ) { - logger.warn( - `No config file detected${ - args.config ? ` (at ${args.config})` : "" - }, aborting` - ); - return; - } + logRuntimeTypesMessage( + outFile, + tsconfigTypes, + mode !== null, + config.configPath + ); + } - // args.xRuntime will be a string if the user passes "--x-include-runtime" or "--x-include-runtime=..." - if (typeof args.experimentalIncludeRuntime === "string") { - logger.log(`Generating runtime types...`); + const secrets = getVarsForDev( + // We do not want `getVarsForDev()` to merge in the standard vars into the dev vars + // because we want to be able to work with secrets differently to vars. + // So we pass in a fake vars object here. + { ...config, vars: {} }, + args.env, + true + ) as Record; + + const configBindingsWithSecrets = { + kv_namespaces: config.kv_namespaces ?? [], + vars: collectAllVars(args), + wasm_modules: config.wasm_modules, + text_blobs: { + ...config.text_blobs, + }, + data_blobs: config.data_blobs, + durable_objects: config.durable_objects, + r2_buckets: config.r2_buckets, + d1_databases: config.d1_databases, + services: config.services, + analytics_engine_datasets: config.analytics_engine_datasets, + dispatch_namespaces: config.dispatch_namespaces, + logfwdr: config.logfwdr, + unsafe: config.unsafe, + rules: config.rules, + queues: config.queues, + send_email: config.send_email, + vectorize: config.vectorize, + hyperdrive: config.hyperdrive, + mtls_certificates: config.mtls_certificates, + browser: config.browser, + ai: config.ai, + version_metadata: config.version_metadata, + secrets, + assets: config.assets, + workflows: config.workflows, + }; - const { outFile } = await generateRuntimeTypes({ + await generateTypes( + configBindingsWithSecrets, config, - outFile: args.experimentalIncludeRuntime || undefined, - }); - - const tsconfigPath = - config.tsconfig ?? join(dirname(config.configPath), "tsconfig.json"); - const tsconfigTypes = readTsconfigTypes(tsconfigPath); - const { mode } = getNodeCompat( - config.compatibility_date, - config.compatibility_flags, - { - nodeCompat: config.node_compat, - } - ); - - logRuntimeTypesMessage( - outFile, - tsconfigTypes, - mode !== null, - config.configPath + envInterface, + outputPath ); - } - - const secrets = getVarsForDev( - // We do not want `getVarsForDev()` to merge in the standard vars into the dev vars - // because we want to be able to work with secrets differently to vars. - // So we pass in a fake vars object here. - { ...config, vars: {} }, - args.env, - true - ) as Record; - - const configBindingsWithSecrets = { - kv_namespaces: config.kv_namespaces ?? [], - vars: collectAllVars(args), - wasm_modules: config.wasm_modules, - text_blobs: { - ...config.text_blobs, - }, - data_blobs: config.data_blobs, - durable_objects: config.durable_objects, - r2_buckets: config.r2_buckets, - d1_databases: config.d1_databases, - services: config.services, - analytics_engine_datasets: config.analytics_engine_datasets, - dispatch_namespaces: config.dispatch_namespaces, - logfwdr: config.logfwdr, - unsafe: config.unsafe, - rules: config.rules, - queues: config.queues, - send_email: config.send_email, - vectorize: config.vectorize, - hyperdrive: config.hyperdrive, - mtls_certificates: config.mtls_certificates, - browser: config.browser, - ai: config.ai, - version_metadata: config.version_metadata, - secrets, - assets: config.assets, - workflows: config.workflows, - }; - - await generateTypes( - configBindingsWithSecrets, - config, - envInterface, - outputPath - ); -} + }, +}); /** * Check if a string is a valid TypeScript identifier. This is a naive check and doesn't cover all cases @@ -584,7 +585,7 @@ type VarTypes = Record; * @returns an object which keys are the variable names and values are arrays containing all the computed types for such variables */ function collectAllVars( - args: StrictYargsOptionsToInterface + args: (typeof typesCommand)["args"] ): Record { const varsInfo: Record> = {}; From 9077a6748a30d5f24c9b7cbdc3a6514fec5aa66c Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Mon, 20 Jan 2025 17:17:04 +0000 Subject: [PATCH 13/24] fix(wrangler): improve multi account error message in non-interactive mode (#7828) --- .changeset/twenty-ears-rescue.md | 5 +++++ packages/wrangler/src/__tests__/deploy.test.ts | 2 +- packages/wrangler/src/__tests__/kv.test.ts | 6 +++--- packages/wrangler/src/__tests__/pages/secret.test.ts | 2 +- packages/wrangler/src/__tests__/secret.test.ts | 2 +- packages/wrangler/src/user/user.ts | 2 +- 6 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 .changeset/twenty-ears-rescue.md diff --git a/.changeset/twenty-ears-rescue.md b/.changeset/twenty-ears-rescue.md new file mode 100644 index 0000000000000..0c75eee6ca394 --- /dev/null +++ b/.changeset/twenty-ears-rescue.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +improve multi account error message in non-interactive mode diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index 9eabca92bb7c3..9966fa6778896 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -619,7 +619,7 @@ describe("deploy", () => { await expect(runWrangler("deploy index.js")).rejects .toMatchInlineSnapshot(` [Error: More than one account available but unable to select one in non-interactive mode. - Please set the appropriate \`account_id\` in your Wrangler configuration file. + Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. Available accounts are (\`\`: \`\`): \`enterprise\`: \`1701\` \`enterprise-nx\`: \`nx01\`] diff --git a/packages/wrangler/src/__tests__/kv.test.ts b/packages/wrangler/src/__tests__/kv.test.ts index 3ffe1f59f5818..2e01d419b698c 100644 --- a/packages/wrangler/src/__tests__/kv.test.ts +++ b/packages/wrangler/src/__tests__/kv.test.ts @@ -1275,7 +1275,7 @@ describe("wrangler", () => { await expect(runWrangler("kv key get key --namespace-id=xxxx")) .rejects.toThrowErrorMatchingInlineSnapshot(` [Error: More than one account available but unable to select one in non-interactive mode. - Please set the appropriate \`account_id\` in your Wrangler configuration file. + Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. Available accounts are (\`\`: \`\`): \`one\`: \`1\` \`two\`: \`2\`] @@ -1291,7 +1291,7 @@ describe("wrangler", () => { await expect(runWrangler("kv key get key --namespace-id=xxxx")) .rejects.toThrowErrorMatchingInlineSnapshot(` [Error: More than one account available but unable to select one in non-interactive mode. - Please set the appropriate \`account_id\` in your Wrangler configuration file. + Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. Available accounts are (\`\`: \`\`): \`one\`: \`1\` \`two\`: \`2\`] @@ -1332,7 +1332,7 @@ describe("wrangler", () => { await expect(runWrangler("kv key get key --namespace-id=xxxx")) .rejects.toThrowErrorMatchingInlineSnapshot(` [Error: More than one account available but unable to select one in non-interactive mode. - Please set the appropriate \`account_id\` in your Wrangler configuration file. + Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. Available accounts are (\`\`: \`\`): \`one\`: \`1\` \`two\`: \`2\`] diff --git a/packages/wrangler/src/__tests__/pages/secret.test.ts b/packages/wrangler/src/__tests__/pages/secret.test.ts index c6ba347b8a45b..d24564f48d1f4 100644 --- a/packages/wrangler/src/__tests__/pages/secret.test.ts +++ b/packages/wrangler/src/__tests__/pages/secret.test.ts @@ -274,7 +274,7 @@ describe("wrangler pages secret", () => { runWrangler("pages secret put the-key --project some-project-name") ).rejects.toThrowErrorMatchingInlineSnapshot(` [Error: More than one account available but unable to select one in non-interactive mode. - Please set the appropriate \`account_id\` in your Wrangler configuration file. + Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. Available accounts are (\`\`: \`\`): \`account-name-1\`: \`account-id-1\` \`account-name-2\`: \`account-id-2\` diff --git a/packages/wrangler/src/__tests__/secret.test.ts b/packages/wrangler/src/__tests__/secret.test.ts index 656d2bc747215..ce40ad5c120dd 100644 --- a/packages/wrangler/src/__tests__/secret.test.ts +++ b/packages/wrangler/src/__tests__/secret.test.ts @@ -413,7 +413,7 @@ describe("wrangler secret", () => { await expect(runWrangler("secret put the-key --name script-name")) .rejects.toThrowErrorMatchingInlineSnapshot(` [Error: More than one account available but unable to select one in non-interactive mode. - Please set the appropriate \`account_id\` in your Wrangler configuration file. + Please set the appropriate \`account_id\` in your Wrangler configuration file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. Available accounts are (\`\`: \`\`): \`account-name-1\`: \`account-id-1\` \`account-name-2\`: \`account-id-2\` diff --git a/packages/wrangler/src/user/user.ts b/packages/wrangler/src/user/user.ts index 44921f1480ea8..89b1e082f5a2f 100644 --- a/packages/wrangler/src/user/user.ts +++ b/packages/wrangler/src/user/user.ts @@ -1204,7 +1204,7 @@ export async function getAccountId(): Promise { if (e instanceof NoDefaultValueProvided) { throw new UserError( `More than one account available but unable to select one in non-interactive mode. -Please set the appropriate \`account_id\` in your ${configFileName(undefined)} file. +Please set the appropriate \`account_id\` in your ${configFileName(undefined)} file or assign it to the \`CLOUDFLARE_ACCOUNT_ID\` environment variable. Available accounts are (\`\`: \`\`): ${accounts .map((account) => ` \`${account.name}\`: \`${account.id}\``) From 806cee84676d37a9b0f72aa2dbfd422cc1aaba35 Mon Sep 17 00:00:00 2001 From: Sakib Hasan <90200484+shakibhasan09@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:53:50 +0600 Subject: [PATCH 14/24] chore(CONTRIBUTING.md): fix invalid link (#7829) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 389d1ddbaf541..3a8955059507b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -363,7 +363,7 @@ We use the following guidelines to determine the kind of change for a PR: ### Styleguide -When contributing to Wrangler, please refer to the [`STYLEGUIDE.md file`](https://github.com/STYLEGUIDE.md) file where possible to help maintain consistent patterns throughout Wrangler. +When contributing to Wrangler, please refer to the [`STYLEGUIDE.md file`](STYLEGUIDE.md) file where possible to help maintain consistent patterns throughout Wrangler. ## Releases From 26fa9e80279401ba5eea4e1522597953441402f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Somhairle=20MacLe=C3=B2id?= Date: Mon, 20 Jan 2025 19:04:48 +0000 Subject: [PATCH 15/24] Support service bindings from Pages projects to workers in a single `workerd` instance (#7715) * Support single-instance multiworker for Pages * Create gentle-sloths-eat.md * Throw an error if a pages project is used as a service binding target * Add clearer messaging * address comments --- .changeset/gentle-sloths-eat.md | 5 + packages/wrangler/e2e/multiworker-dev.test.ts | 85 +++++++++ .../api/startDevWorker/ConfigController.ts | 8 + .../src/api/startDevWorker/ProxyController.ts | 14 +- packages/wrangler/src/index.ts | 7 +- packages/wrangler/src/pages/dev.ts | 172 +++++++++++------- 6 files changed, 227 insertions(+), 64 deletions(-) create mode 100644 .changeset/gentle-sloths-eat.md diff --git a/.changeset/gentle-sloths-eat.md b/.changeset/gentle-sloths-eat.md new file mode 100644 index 0000000000000..047e0bc00f0bc --- /dev/null +++ b/.changeset/gentle-sloths-eat.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Support service bindings from Pages projects to Workers in a single `workerd` instance. To try it out, pass multiple `-c` flags to Wrangler: i.e. `wrangler pages dev -c wrangler.toml -c ../other-worker/wrangler.toml`. The first `-c` flag must point to your Pages config file, and the rest should point to Workers that are bound to your Pages project. diff --git a/packages/wrangler/e2e/multiworker-dev.test.ts b/packages/wrangler/e2e/multiworker-dev.test.ts index 0298f9918887a..d584242b085b2 100644 --- a/packages/wrangler/e2e/multiworker-dev.test.ts +++ b/packages/wrangler/e2e/multiworker-dev.test.ts @@ -361,4 +361,89 @@ describe("multiworker", () => { ); }); }); + + describe("pages", () => { + beforeEach(async () => { + await baseSeed(a, { + "wrangler.toml": dedent` + name = "${workerName}" + pages_build_output_dir = "./public" + compatibility_date = "2024-11-01" + + [[services]] + binding = "CEE" + service = '${workerName3}' + + [[services]] + binding = "BEE" + service = '${workerName2}' + `, + "functions/cee.ts": dedent/* javascript */ ` + export async function onRequest(context) { + return context.env.CEE.fetch("https://example.com"); + }`, + "functions/bee.ts": dedent/* javascript */ ` + export async function onRequest(context) { + return context.env.BEE.fetch("https://example.com"); + }`, + "public/index.html": `

hello pages assets

`, + }); + }); + + it("pages project assets", async () => { + const pages = helper.runLongLived( + `wrangler pages dev -c wrangler.toml -c ${b}/wrangler.toml -c ${c}/wrangler.toml`, + { cwd: a } + ); + const { url } = await pages.waitForReady(5_000); + + await vi.waitFor( + async () => + await expect(fetchText(`${url}`)).resolves.toBe( + "

hello pages assets

" + ), + { interval: 1000, timeout: 10_000 } + ); + }); + + it("pages project fetching service worker", async () => { + const pages = helper.runLongLived( + `wrangler pages dev -c wrangler.toml -c ${b}/wrangler.toml -c ${c}/wrangler.toml`, + { cwd: a } + ); + const { url } = await pages.waitForReady(5_000); + + await vi.waitFor( + async () => + await expect(fetchText(`${url}/cee`)).resolves.toBe( + "Hello from service worker" + ), + { interval: 1000, timeout: 10_000 } + ); + }); + + it("pages project fetching module worker", async () => { + const pages = helper.runLongLived( + `wrangler pages dev -c wrangler.toml -c ${b}/wrangler.toml -c ${c}/wrangler.toml`, + { cwd: a } + ); + const { url } = await pages.waitForReady(5_000); + + await vi.waitFor( + async () => + await expect(fetchText(`${url}/bee`)).resolves.toBe("hello world"), + { interval: 1000, timeout: 10_000 } + ); + }); + + it("should error if multiple pages configs are provided", async () => { + const pages = helper.runLongLived( + `wrangler pages dev -c wrangler.toml -c wrangler.toml`, + { cwd: a } + ); + await pages.readUntil( + /You cannot use a Pages project as a service binding target/ + ); + }); + }); }); diff --git a/packages/wrangler/src/api/startDevWorker/ConfigController.ts b/packages/wrangler/src/api/startDevWorker/ConfigController.ts index 561b5eac952e8..c95f422a3c053 100644 --- a/packages/wrangler/src/api/startDevWorker/ConfigController.ts +++ b/packages/wrangler/src/api/startDevWorker/ConfigController.ts @@ -227,6 +227,14 @@ async function resolveConfig( config: Config, input: StartDevWorkerInput ): Promise { + if ( + config.pages_build_output_dir && + input.dev?.multiworkerPrimary === false + ) { + throw new UserError( + `You cannot use a Pages project as a service binding target.\nIf you are trying to develop Pages and Workers together, please use \`wrangler pages dev\`. Note the first config file specified must be for the Pages project` + ); + } const legacySite = unwrapHook(input.legacy?.site, config); const legacyAssets = unwrapHook(input.legacy?.legacyAssets, config); diff --git a/packages/wrangler/src/api/startDevWorker/ProxyController.ts b/packages/wrangler/src/api/startDevWorker/ProxyController.ts index 036d9219eed72..4ffec334df499 100644 --- a/packages/wrangler/src/api/startDevWorker/ProxyController.ts +++ b/packages/wrangler/src/api/startDevWorker/ProxyController.ts @@ -193,12 +193,24 @@ export class ProxyController extends Controller { void Promise.all([ proxyWorker.ready, proxyWorker.unsafeGetDirectURL("InspectorProxyWorker"), - this.reconnectInspectorProxyWorker(), ]) + .then(([url, inspectorUrl]) => { + // Don't connect the inspector proxy worker until we have a valid ready Miniflare instance. + // Otherwise, tearing down the ProxyController immediately after setting it up + // will result in proxyWorker.ready throwing, but reconnectInspectorProxyWorker hanging for ever, + // preventing teardown + return this.reconnectInspectorProxyWorker().then(() => [ + url, + inspectorUrl, + ]); + }) .then(([url, inspectorUrl]) => { this.emitReadyEvent(proxyWorker, url, inspectorUrl); }) .catch((error) => { + if (this._torndown) { + return; + } this.emitErrorEvent( "Failed to start ProxyWorker or InspectorProxyWorker", error diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 5b6afce6db28b..ac6697642f29e 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -220,7 +220,12 @@ export function createCLIParser(argv: string[]) { requiresArg: true, }) .check( - demandSingleValue("config", (configArgv) => configArgv["_"][0] === "dev") + demandSingleValue( + "config", + (configArgv) => + configArgv["_"][0] === "dev" || + (configArgv["_"][0] === "pages" && configArgv["_"][1] === "dev") + ) ) .option("env", { alias: "e", diff --git a/packages/wrangler/src/pages/dev.ts b/packages/wrangler/src/pages/dev.ts index a547e089bb364..0b224531f30a3 100644 --- a/packages/wrangler/src/pages/dev.ts +++ b/packages/wrangler/src/pages/dev.ts @@ -1,15 +1,17 @@ import { execSync, spawn } from "node:child_process"; +import events from "node:events"; import { existsSync, lstatSync, readFileSync } from "node:fs"; -import { dirname, join, normalize, resolve } from "node:path"; +import path, { dirname, join, normalize, resolve } from "node:path"; import { watch } from "chokidar"; import * as esbuild from "esbuild"; -import { unstable_dev } from "../api"; import { configFileName, readConfig } from "../config"; import { isBuildFailure } from "../deployment-bundle/build-failures"; import { shouldCheckFetch } from "../deployment-bundle/bundle"; import { esbuildAliasExternalPlugin } from "../deployment-bundle/esbuild-plugins/alias-external"; import { validateNodeCompatMode } from "../deployment-bundle/node-compat"; +import { startDev } from "../dev"; import { FatalError } from "../errors"; +import { run } from "../experimental-flags"; import { logger } from "../logger"; import * as metrics from "../metrics"; import { isNavigatorDefined } from "../navigator-user-agent"; @@ -223,12 +225,6 @@ export function Options(yargs: CommonYargsArgv) { deprecated: true, hidden: true, }, - config: { - describe: - "Pages does not support custom paths for the Wrangler configuration file", - type: "string", - hidden: true, - }, "log-level": { choices: ["debug", "info", "log", "warn", "error", "none"] as const, describe: "Specify logging level", @@ -262,7 +258,7 @@ export const Handler = async (args: PagesDevArguments) => { ); } - if (args.config) { + if (args.config && !Array.isArray(args.config)) { throw new FatalError( "Pages does not support custom paths for the Wrangler configuration file", 1 @@ -285,9 +281,21 @@ export const Handler = async (args: PagesDevArguments) => { // for `dev` we always use the top-level config, which means we need // to read the config file with `env` set to `undefined` const config = readConfig( - { ...args, env: undefined }, + { ...args, env: undefined, config: undefined }, { useRedirectIfAvailable: true } ); + + if ( + args.config && + Array.isArray(args.config) && + config.configPath && + path.resolve(process.cwd(), args.config[0]) !== config.configPath + ) { + throw new FatalError( + "The first `--config` argument must point to your Pages configuration file: " + + path.relative(process.cwd(), config.configPath) + ); + } const resolvedDirectory = args.directory ?? config.pages_build_output_dir; const [_pages, _dev, ...remaining] = args._; const command = remaining; @@ -525,8 +533,8 @@ export const Handler = async (args: PagesDevArguments) => { try { await runBuild(); - watcher.on("all", async (eventName, path) => { - logger.debug(`🌀 "${eventName}" event detected at ${path}.`); + watcher.on("all", async (eventName, p) => { + logger.debug(`🌀 "${eventName}" event detected at ${p}.`); // Skip re-building the Worker if "_worker.js" was deleted. // This is necessary for Pages projects + Frameworks, where @@ -708,8 +716,8 @@ export const Handler = async (args: PagesDevArguments) => { await buildFn(); // If Functions found routes, continue using Functions - watcher.on("all", async (eventName, path) => { - logger.debug(`🌀 "${eventName}" event detected at ${path}.`); + watcher.on("all", async (eventName, p) => { + logger.debug(`🌀 "${eventName}" event detected at ${p}.`); debouncedBuildFn(); }); @@ -862,61 +870,101 @@ export const Handler = async (args: PagesDevArguments) => { } } - const { stop, waitUntilExit } = await unstable_dev(scriptEntrypoint, { - env: undefined, - ip, - port, - inspectorPort, - localProtocol, - httpsKeyPath: args.httpsKeyPath, - httpsCertPath: args.httpsCertPath, - compatibilityDate, - compatibilityFlags, - nodeCompat: nodejsCompatMode === "legacy", - vars, - kv: kv_namespaces, - durableObjects: do_bindings, - r2: r2_buckets, - services, - ai, - rules: usingWorkerDirectory - ? [ - { - type: "ESModule", - globs: ["**/*.js", "**/*.mjs"], - }, - ] - : undefined, - bundle: enableBundling, - persistTo: args.persistTo, - inspect: undefined, - logLevel: args.logLevel, - experimental: { - processEntrypoint: true, - additionalModules: modules, - d1Databases: d1_databases, - disableExperimentalWarning: true, - enablePagesAssetsServiceBinding: { - proxyPort, - directory, - }, - liveReload: args.liveReload, - forceLocal: true, - showInteractiveDevSession: args.showInteractiveDevSession, - testMode: false, - watch: true, - enableIpc: true, + const devServer = await run( + { + MULTIWORKER: Array.isArray(args.config), + RESOURCES_PROVISION: false, }, - }); - metrics.sendMetricsEvent("run pages dev"); + () => + startDev({ + script: scriptEntrypoint, + _: [], + $0: "", + remote: false, + local: true, + experimentalLocal: undefined, + d1Databases: d1_databases, + testScheduled: false, + enablePagesAssetsServiceBinding: { + proxyPort, + directory, + }, + forceLocal: true, + liveReload: args.liveReload, + showInteractiveDevSession: args.showInteractiveDevSession, + processEntrypoint: true, + additionalModules: modules, + v: undefined, + assets: undefined, + name: undefined, + noBundle: false, + format: undefined, + latest: false, + routes: undefined, + host: undefined, + localUpstream: undefined, + experimentalPublic: undefined, + upstreamProtocol: undefined, + var: undefined, + define: undefined, + alias: undefined, + jsxFactory: undefined, + jsxFragment: undefined, + tsconfig: undefined, + minify: undefined, + experimentalEnableLocalPersistence: undefined, + legacyEnv: undefined, + public: undefined, + env: undefined, + ip, + port, + inspectorPort, + localProtocol, + httpsKeyPath: args.httpsKeyPath, + httpsCertPath: args.httpsCertPath, + compatibilityDate, + compatibilityFlags, + nodeCompat: nodejsCompatMode === "legacy", + vars, + kv: kv_namespaces, + durableObjects: do_bindings, + r2: r2_buckets, + services, + ai, + rules: usingWorkerDirectory + ? [ + { + type: "ESModule", + globs: ["**/*.js", "**/*.mjs"], + }, + ] + : undefined, + bundle: enableBundling, + persistTo: args.persistTo, + logLevel: args.logLevel ?? "log", + experimentalProvision: undefined, + experimentalVectorizeBindToProd: false, + enableIpc: true, + config: Array.isArray(args.config) ? args.config : undefined, + legacyAssets: undefined, + site: undefined, + siteInclude: undefined, + siteExclude: undefined, + inspect: undefined, + }) + ); - CLEANUP_CALLBACKS.push(stop); + metrics.sendMetricsEvent("run pages dev"); process.on("exit", CLEANUP); process.on("SIGINT", CLEANUP); process.on("SIGTERM", CLEANUP); - await waitUntilExit(); + await events.once(devServer.devEnv, "teardown"); + const teardownRegistry = await devServer.teardownRegistryPromise; + await teardownRegistry?.(devServer.devEnv.config.latestConfig?.name); + + devServer.unregisterHotKeys?.(); CLEANUP(); process.exit(0); }; From a783c2d2753a4d9c094af325e85401016725bae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Somhairle=20MacLe=C3=B2id?= Date: Mon, 20 Jan 2025 20:44:23 +0000 Subject: [PATCH 16/24] Only retry API failures (#7831) * Only retry API failures * fix lint --- .../versions/versions.upload.test.ts | 2 ++ packages/wrangler/src/deploy/deploy.ts | 6 ++-- packages/wrangler/src/parse.ts | 4 +++ packages/wrangler/src/pipelines/index.ts | 4 +-- packages/wrangler/src/utils/retry.ts | 35 ++++++++++++++++--- packages/wrangler/src/versions/upload.ts | 4 +-- 6 files changed, 44 insertions(+), 11 deletions(-) diff --git a/packages/wrangler/src/__tests__/versions/versions.upload.test.ts b/packages/wrangler/src/__tests__/versions/versions.upload.test.ts index 1cdeefa362acf..d85f86b04a82d 100644 --- a/packages/wrangler/src/__tests__/versions/versions.upload.test.ts +++ b/packages/wrangler/src/__tests__/versions/versions.upload.test.ts @@ -169,5 +169,7 @@ describe("versions upload", () => { Uploaded test-name (TIMINGS) Worker Version ID: 51e4886e-2db7-4900-8d38-fbfecfeab993" `); + + expect(std.info).toContain("Retrying API call after error..."); }); }); diff --git a/packages/wrangler/src/deploy/deploy.ts b/packages/wrangler/src/deploy/deploy.ts index d93c6cc20fe63..2f956bc2933b2 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -49,7 +49,7 @@ import { } from "../sourcemap"; import triggersDeploy from "../triggers/deploy"; import { printBindings } from "../utils/print-bindings"; -import { retryOnError } from "../utils/retry"; +import { retryOnAPIFailure } from "../utils/retry"; import { createDeployment, patchNonVersionedScriptSettings, @@ -826,7 +826,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m // If we're using the new APIs, first upload the version if (canUseNewVersionsDeploymentsApi) { // Upload new version - const versionResult = await retryOnError(async () => + const versionResult = await retryOnAPIFailure(async () => fetchResult( `/accounts/${accountId}/workers/scripts/${scriptName}/versions`, { @@ -861,7 +861,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m startup_time_ms: versionResult.startup_time_ms, }; } else { - result = await retryOnError(async () => + result = await retryOnAPIFailure(async () => fetchResult<{ id: string | null; etag: string | null; diff --git a/packages/wrangler/src/parse.ts b/packages/wrangler/src/parse.ts index e2fc1aa03551d..a798e3d858209 100644 --- a/packages/wrangler/src/parse.ts +++ b/packages/wrangler/src/parse.ts @@ -88,6 +88,10 @@ export class APIError extends ParseError { return false; } + isRetryable() { + return String(this.#status).startsWith("5"); + } + // Allow `APIError`s to be marked as handled. #reportable = true; get reportable() { diff --git a/packages/wrangler/src/pipelines/index.ts b/packages/wrangler/src/pipelines/index.ts index dcd9bf1a80391..a0a493954eab0 100644 --- a/packages/wrangler/src/pipelines/index.ts +++ b/packages/wrangler/src/pipelines/index.ts @@ -5,7 +5,7 @@ import { logger } from "../logger"; import * as metrics from "../metrics"; import { APIError } from "../parse"; import { requireAuth } from "../user"; -import { retryOnError } from "../utils/retry"; +import { retryOnAPIFailure } from "../utils/retry"; import { printWranglerBanner } from "../wrangler-banner"; import { createPipeline, @@ -63,7 +63,7 @@ async function authorizeR2Bucket( // Wait for token to settle/propagate, retry up to 10 times, with 1s waits in-between errors !__testSkipDelaysFlag && - (await retryOnError( + (await retryOnAPIFailure( async () => { await r2.send( new HeadBucketCommand({ diff --git a/packages/wrangler/src/utils/retry.ts b/packages/wrangler/src/utils/retry.ts index ac35efef4ab2d..1f7091252fbdf 100644 --- a/packages/wrangler/src/utils/retry.ts +++ b/packages/wrangler/src/utils/retry.ts @@ -1,18 +1,45 @@ import { setTimeout } from "node:timers/promises"; +import chalk from "chalk"; +import { logger } from "../logger"; +import { APIError } from "../parse"; -export async function retryOnError( +const MAX_ATTEMPTS = 3; +/** + * Wrap around calls to the Cloudflare API to automatically retry + * calls that result in a 5xx error code, indicating an API failure. + * + * Retries will back off at a rate of 1000ms per retry, with a 0ms delay for the first retry + * + * Note: this will not retry 4xx or other failures, as those are + * likely legitimate user error. + */ +export async function retryOnAPIFailure( action: () => T | Promise, - backoff = 2_000, - attempts = 3 + backoff = 0, + attempts = MAX_ATTEMPTS ): Promise { try { return await action(); } catch (err) { + if ( + (err instanceof APIError && !err.isRetryable()) || + !(err instanceof TypeError) + ) { + throw err; + } + + logger.info(chalk.dim(`Retrying API call after error...`)); + logger.debug(err); + if (attempts <= 1) { throw err; } await setTimeout(backoff); - return retryOnError(action, backoff, attempts - 1); + return retryOnAPIFailure( + action, + backoff + (MAX_ATTEMPTS - attempts) * 1000, + attempts - 1 + ); } } diff --git a/packages/wrangler/src/versions/upload.ts b/packages/wrangler/src/versions/upload.ts index 5951a0667d8fd..2b8cb2e9510f3 100644 --- a/packages/wrangler/src/versions/upload.ts +++ b/packages/wrangler/src/versions/upload.ts @@ -53,7 +53,7 @@ import { getRules } from "../utils/getRules"; import { getScriptName } from "../utils/getScriptName"; import { isLegacyEnv } from "../utils/isLegacyEnv"; import { printBindings } from "../utils/print-bindings"; -import { retryOnError } from "../utils/retry"; +import { retryOnAPIFailure } from "../utils/retry"; import type { AssetsOptions } from "../assets"; import type { Config } from "../config"; import type { Rule } from "../config/environment"; @@ -741,7 +741,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m try { const body = createWorkerUploadForm(worker); - const result = await retryOnError(async () => + const result = await retryOnAPIFailure(async () => fetchResult<{ id: string; startup_time_ms: number; From e2f5756c29bbaa0d080e785de393a102f4170f38 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 20 Jan 2025 21:15:55 +0000 Subject: [PATCH 17/24] Relax the Wrangler peer dependency for the Vite plugin (#7833) We only need the Wrangler peer dependency to be at least 3.101.0, which is when we added support for the redirected config. By using the peer dependency range version in the playgrounds, we ensure that we are testing against what will be used in practice. It will also ensure that this dependency gets bumped when needed, since the playgrounds might start to fail otherwise. --- packages/vite-plugin-cloudflare/package.json | 2 +- .../playground/cloudflare-env/package.json | 2 +- .../playground/durable-objects/package.json | 2 +- .../external-durable-objects/package.json | 2 +- .../external-workflows/package.json | 2 +- .../playground/hot-channel/package.json | 2 +- .../playground/module-resolution/package.json | 2 +- .../playground/multi-worker/package.json | 2 +- .../playground/node-compat/package.json | 2 +- .../playground/react-spa/package.json | 2 +- .../playground/spa-with-api/package.json | 2 +- .../playground/static-mpa/package.json | 2 +- .../playground/virtual-modules/package.json | 2 +- .../playground/websockets/package.json | 2 +- .../playground/worker/package.json | 2 +- .../playground/workflows/package.json | 2 +- pnpm-lock.yaml | 127 +++++++++++++----- pnpm-workspace.yaml | 2 + 18 files changed, 115 insertions(+), 46 deletions(-) diff --git a/packages/vite-plugin-cloudflare/package.json b/packages/vite-plugin-cloudflare/package.json index 638c9ff1a0990..fe928b18d25be 100644 --- a/packages/vite-plugin-cloudflare/package.json +++ b/packages/vite-plugin-cloudflare/package.json @@ -59,7 +59,7 @@ }, "peerDependencies": { "vite": "catalog:vite-plugin", - "wrangler": "workspace:^" + "wrangler": "catalog:vite-plugin" }, "publishConfig": { "access": "public" diff --git a/packages/vite-plugin-cloudflare/playground/cloudflare-env/package.json b/packages/vite-plugin-cloudflare/playground/cloudflare-env/package.json index 47ced0a2e8aac..fcacf1ddb5c3d 100644 --- a/packages/vite-plugin-cloudflare/playground/cloudflare-env/package.json +++ b/packages/vite-plugin-cloudflare/playground/cloudflare-env/package.json @@ -17,6 +17,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/durable-objects/package.json b/packages/vite-plugin-cloudflare/playground/durable-objects/package.json index 36fc859b8f86b..2b2ee70ad93b5 100644 --- a/packages/vite-plugin-cloudflare/playground/durable-objects/package.json +++ b/packages/vite-plugin-cloudflare/playground/durable-objects/package.json @@ -14,6 +14,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/external-durable-objects/package.json b/packages/vite-plugin-cloudflare/playground/external-durable-objects/package.json index 8b0925ce9a194..93cd4fb16b433 100644 --- a/packages/vite-plugin-cloudflare/playground/external-durable-objects/package.json +++ b/packages/vite-plugin-cloudflare/playground/external-durable-objects/package.json @@ -14,6 +14,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/external-workflows/package.json b/packages/vite-plugin-cloudflare/playground/external-workflows/package.json index fe2750ee99b89..5f2a8e590db99 100644 --- a/packages/vite-plugin-cloudflare/playground/external-workflows/package.json +++ b/packages/vite-plugin-cloudflare/playground/external-workflows/package.json @@ -14,6 +14,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/package.json b/packages/vite-plugin-cloudflare/playground/hot-channel/package.json index 4c6b1b82700c0..1c648fe8dc872 100644 --- a/packages/vite-plugin-cloudflare/playground/hot-channel/package.json +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/package.json @@ -13,6 +13,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/module-resolution/package.json b/packages/vite-plugin-cloudflare/playground/module-resolution/package.json index 46e03c0e5719b..5e4f74d69f822 100644 --- a/packages/vite-plugin-cloudflare/playground/module-resolution/package.json +++ b/packages/vite-plugin-cloudflare/playground/module-resolution/package.json @@ -24,6 +24,6 @@ "slash-create": "6.2.1", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/multi-worker/package.json b/packages/vite-plugin-cloudflare/playground/multi-worker/package.json index 1fe5345ec0a2f..cb0913987d630 100644 --- a/packages/vite-plugin-cloudflare/playground/multi-worker/package.json +++ b/packages/vite-plugin-cloudflare/playground/multi-worker/package.json @@ -19,6 +19,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/package.json b/packages/vite-plugin-cloudflare/playground/node-compat/package.json index 9e6d0a39dd5f8..b13e5e06929d1 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/package.json +++ b/packages/vite-plugin-cloudflare/playground/node-compat/package.json @@ -42,6 +42,6 @@ "pg-cloudflare": "^1.1.1", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/react-spa/package.json b/packages/vite-plugin-cloudflare/playground/react-spa/package.json index 9c2dc74b6520e..a16161c4b6fa9 100644 --- a/packages/vite-plugin-cloudflare/playground/react-spa/package.json +++ b/packages/vite-plugin-cloudflare/playground/react-spa/package.json @@ -21,6 +21,6 @@ "@vitejs/plugin-react": "^4.3.4", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/spa-with-api/package.json b/packages/vite-plugin-cloudflare/playground/spa-with-api/package.json index 2341ec95b17cb..d9c06b6c3b39f 100644 --- a/packages/vite-plugin-cloudflare/playground/spa-with-api/package.json +++ b/packages/vite-plugin-cloudflare/playground/spa-with-api/package.json @@ -23,6 +23,6 @@ "@vitejs/plugin-react": "^4.3.4", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/static-mpa/package.json b/packages/vite-plugin-cloudflare/playground/static-mpa/package.json index adc85541cb249..a5111ae613bd8 100644 --- a/packages/vite-plugin-cloudflare/playground/static-mpa/package.json +++ b/packages/vite-plugin-cloudflare/playground/static-mpa/package.json @@ -17,6 +17,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/virtual-modules/package.json b/packages/vite-plugin-cloudflare/playground/virtual-modules/package.json index 502af7ac8ab1a..a30f5e0cc9bcc 100644 --- a/packages/vite-plugin-cloudflare/playground/virtual-modules/package.json +++ b/packages/vite-plugin-cloudflare/playground/virtual-modules/package.json @@ -14,6 +14,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/websockets/package.json b/packages/vite-plugin-cloudflare/playground/websockets/package.json index d5ae9c3339fb4..4b69ebd595b61 100644 --- a/packages/vite-plugin-cloudflare/playground/websockets/package.json +++ b/packages/vite-plugin-cloudflare/playground/websockets/package.json @@ -14,6 +14,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/worker/package.json b/packages/vite-plugin-cloudflare/playground/worker/package.json index 78bc7cd70c8b8..d19776b5571cf 100644 --- a/packages/vite-plugin-cloudflare/playground/worker/package.json +++ b/packages/vite-plugin-cloudflare/playground/worker/package.json @@ -14,6 +14,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/packages/vite-plugin-cloudflare/playground/workflows/package.json b/packages/vite-plugin-cloudflare/playground/workflows/package.json index 0b273fc15f224..b0b3435788a06 100644 --- a/packages/vite-plugin-cloudflare/playground/workflows/package.json +++ b/packages/vite-plugin-cloudflare/playground/workflows/package.json @@ -14,6 +14,6 @@ "@cloudflare/workers-types": "^4.20241230.0", "typescript": "catalog:vite-plugin", "vite": "catalog:vite-plugin", - "wrangler": "workspace:*" + "wrangler": "catalog:vite-plugin" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4afa65dc96103..fb8879444a974 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,9 @@ catalogs: vite: specifier: ^6.0.7 version: 6.0.7 + wrangler: + specifier: ^3.101.0 + version: 3.103.2 overrides: '@types/react-dom@18>@types/react': ^18 @@ -1770,8 +1773,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/durable-objects: devDependencies: @@ -1791,8 +1794,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/external-durable-objects: devDependencies: @@ -1812,8 +1815,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/external-workflows: devDependencies: @@ -1833,8 +1836,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/hot-channel: devDependencies: @@ -1854,8 +1857,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/module-resolution: devDependencies: @@ -1896,8 +1899,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/multi-worker: devDependencies: @@ -1917,8 +1920,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/node-compat: devDependencies: @@ -1953,8 +1956,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/react-spa: dependencies: @@ -1990,8 +1993,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/spa-with-api: dependencies: @@ -2027,8 +2030,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/static-mpa: devDependencies: @@ -2048,8 +2051,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/virtual-modules: devDependencies: @@ -2069,8 +2072,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/websockets: devDependencies: @@ -2090,8 +2093,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/worker: devDependencies: @@ -2111,8 +2114,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vite-plugin-cloudflare/playground/workflows: devDependencies: @@ -2132,8 +2135,8 @@ importers: specifier: catalog:vite-plugin version: 6.0.7(@types/node@18.19.59)(jiti@2.4.2) wrangler: - specifier: workspace:* - version: link:../../../wrangler + specifier: catalog:vite-plugin + version: 3.103.2(@cloudflare/workers-types@4.20241230.0) packages/vitest-pool-workers: dependencies: @@ -8224,6 +8227,11 @@ packages: engines: {node: '>=16.13'} hasBin: true + miniflare@3.20241230.2: + resolution: {integrity: sha512-gFC3IaUKrLGdtA6y6PLpC/QE5YAjB5ITCfBZHkosRyFZ9ApaCHKcHRvrEFMc/R19QxxtHD+G3tExEHp7MmtsYQ==} + engines: {node: '>=16.13'} + hasBin: true + minimatch@3.0.8: resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} @@ -10366,6 +10374,9 @@ packages: unenv-nightly@2.0.0-20241218-183400-5d6aec3: resolution: {integrity: sha512-7Xpi29CJRbOV1/IrC03DawMJ0hloklDLq/cigSe+J2jkcC+iDres2Cy0r4ltj5f0x7DqsaGaB4/dLuCPPFZnZA==} + unenv-nightly@2.0.0-20250109-100802-88ad671: + resolution: {integrity: sha512-Uij6gODNNNNsNBoDlnaMvZI99I6YlVJLRfYH8AOLMlbFrW7k2w872v9VLuIdch2vF8QBeSC4EftIh5sG4ibzdA==} + unenv@2.0.0-rc.0: resolution: {integrity: sha512-H0kl2w8jFL/FAk0xvjVing4bS3jd//mbg1QChDnn58l9Sc5RtduaKmLAL8n+eBw5jJo8ZjYV7CrEGage5LAOZQ==} @@ -10708,6 +10719,16 @@ packages: workerpool@6.5.1: resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + wrangler@3.103.2: + resolution: {integrity: sha512-eYcnubPhPBU1QMZYTam+vfCLpaQx+x1EWA6nFbLhid1eqNDAk1dNwNlbo+ZryrOHDEX3XlOxn2Z3Fx8vVv3hKw==} + engines: {node: '>=16.17.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20241230.0 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrangler@3.90.0: resolution: {integrity: sha512-E/6E9ORAl987+3kP8wDiE3L1lj9r4vQ32/dl5toIxIkSMssmPRQVdxqwgMxbxJrytbFNo8Eo6swgjd4y4nUaLg==} engines: {node: '>=16.17.0'} @@ -17380,6 +17401,25 @@ snapshots: - supports-color - utf-8-validate + miniflare@3.20241230.2: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + acorn: 8.14.0 + acorn-walk: 8.3.2 + capnp-ts: 0.7.0(patch_hash=l4yimnxyvkiyj6alnps2ec3sii) + exit-hook: 2.2.1 + glob-to-regexp: 0.4.1 + stoppable: 1.1.0 + undici: 5.28.4 + workerd: 1.20241230.0 + ws: 8.18.0 + youch: 3.2.3 + zod: 3.22.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + minimatch@3.0.8: dependencies: brace-expansion: 1.1.11 @@ -19659,6 +19699,14 @@ snapshots: pathe: 1.1.2 ufo: 1.5.4 + unenv-nightly@2.0.0-20250109-100802-88ad671: + dependencies: + defu: 6.1.4 + mlly: 1.7.4 + ohash: 1.1.4 + pathe: 1.1.2 + ufo: 1.5.4 + unenv@2.0.0-rc.0: dependencies: defu: 6.1.4 @@ -20044,6 +20092,25 @@ snapshots: workerpool@6.5.1: {} + wrangler@3.103.2(@cloudflare/workers-types@4.20241230.0): + dependencies: + '@cloudflare/kv-asset-handler': 0.3.4 + '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) + '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) + blake3-wasm: 2.1.5 + esbuild: 0.17.19 + miniflare: 3.20241230.2 + path-to-regexp: 6.3.0 + unenv: unenv-nightly@2.0.0-20250109-100802-88ad671 + workerd: 1.20241230.0 + optionalDependencies: + '@cloudflare/workers-types': 4.20241230.0 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + wrangler@3.90.0(@cloudflare/workers-types@4.20241230.0): dependencies: '@cloudflare/kv-asset-handler': 0.3.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0b62ff1c46db1..17a5c44259d8a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -23,3 +23,5 @@ catalogs: "vite": "^6.0.7" "@types/node": "^22.10.1" "unenv": "npm:unenv-nightly@2.0.0-20241218-183400-5d6aec3" + # This is the minimum Wrangler peer dependency for Vite + "wrangler": "^3.101.0" From 97d2a1bb56ea0bb94531f9c41b737ba43ed5996f Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 21 Jan 2025 08:02:02 +0000 Subject: [PATCH 18/24] Relax the messaging when Wrangler uses redirected configuration (#7832) Previously the messaging was rendered as a warning, which implied that the user had done something wrong. Now it is just a regular info message. --- .changeset/green-mice-carry.md | 8 ++++ .../config/findWranglerConfig.test.ts | 48 +++++++------------ .../wrangler/src/config/config-helpers.ts | 8 ++-- 3 files changed, 30 insertions(+), 34 deletions(-) create mode 100644 .changeset/green-mice-carry.md diff --git a/.changeset/green-mice-carry.md b/.changeset/green-mice-carry.md new file mode 100644 index 0000000000000..317f448b0c3d8 --- /dev/null +++ b/.changeset/green-mice-carry.md @@ -0,0 +1,8 @@ +--- +"wrangler": patch +--- + +Relax the messaging when Wrangler uses redirected configuration + +Previously the messaging was rendered as a warning, which implied that the user +had done something wrong. Now it is just a regular info message. diff --git a/packages/wrangler/src/__tests__/config/findWranglerConfig.test.ts b/packages/wrangler/src/__tests__/config/findWranglerConfig.test.ts index 792b5c3d35850..93f8754c4abe7 100644 --- a/packages/wrangler/src/__tests__/config/findWranglerConfig.test.ts +++ b/packages/wrangler/src/__tests__/config/findWranglerConfig.test.ts @@ -105,22 +105,16 @@ describe("config findWranglerConfig()", () => { Object { "debug": "", "err": "", - "info": "", + "info": "Using redirected Wrangler configuration. + - Configuration being used: \\"dist/wrangler.json\\" + - Original user's configuration: \\"\\" + - Deploy configuration file: \\".wrangler/deploy/config.json\\" + Using redirected Wrangler configuration. + - Configuration being used: \\"dist/wrangler.json\\" + - Original user's configuration: \\"\\" + - Deploy configuration file: \\".wrangler/deploy/config.json\\"", "out": "", - "warn": "▲ [WARNING] Using redirected Wrangler configuration. - - Configuration being used: \\"dist/wrangler.json\\" - Original user's configuration: \\"\\" - Deploy configuration file: \\".wrangler/deploy/config.json\\" - - - ▲ [WARNING] Using redirected Wrangler configuration. - - Configuration being used: \\"dist/wrangler.json\\" - Original user's configuration: \\"\\" - Deploy configuration file: \\".wrangler/deploy/config.json\\" - - ", + "warn": "", } `); }); @@ -148,22 +142,16 @@ describe("config findWranglerConfig()", () => { Object { "debug": "", "err": "", - "info": "", + "info": "Using redirected Wrangler configuration. + - Configuration being used: \\"dist/wrangler.json\\" + - Original user's configuration: \\"wrangler.toml\\" + - Deploy configuration file: \\".wrangler/deploy/config.json\\" + Using redirected Wrangler configuration. + - Configuration being used: \\"dist/wrangler.json\\" + - Original user's configuration: \\"wrangler.toml\\" + - Deploy configuration file: \\".wrangler/deploy/config.json\\"", "out": "", - "warn": "▲ [WARNING] Using redirected Wrangler configuration. - - Configuration being used: \\"dist/wrangler.json\\" - Original user's configuration: \\"wrangler.toml\\" - Deploy configuration file: \\".wrangler/deploy/config.json\\" - - - ▲ [WARNING] Using redirected Wrangler configuration. - - Configuration being used: \\"dist/wrangler.json\\" - Original user's configuration: \\"wrangler.toml\\" - Deploy configuration file: \\".wrangler/deploy/config.json\\" - - ", + "warn": "", } `); }); diff --git a/packages/wrangler/src/config/config-helpers.ts b/packages/wrangler/src/config/config-helpers.ts index 11201a86116aa..bd707fbef550a 100644 --- a/packages/wrangler/src/config/config-helpers.ts +++ b/packages/wrangler/src/config/config-helpers.ts @@ -128,11 +128,11 @@ function findRedirectedWranglerConfig( } } - logger.warn(dedent` + logger.info(dedent` Using redirected Wrangler configuration. - Configuration being used: "${path.relative(".", redirectedConfigPath)}" - Original user's configuration: "${userConfigPath ? path.relative(".", userConfigPath) : ""}" - Deploy configuration file: "${path.relative(".", deployConfigPath)}" + - Configuration being used: "${path.relative(".", redirectedConfigPath)}" + - Original user's configuration: "${userConfigPath ? path.relative(".", userConfigPath) : ""}" + - Deploy configuration file: "${path.relative(".", deployConfigPath)}" `); return redirectedConfigPath; } From a4634f15bfd71154de489c2a7112b6058d40d234 Mon Sep 17 00:00:00 2001 From: James Opstad <13586373+jamesopstad@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:11:27 +0000 Subject: [PATCH 19/24] Added JSDoc to cloudflare function (#7838) --- packages/vite-plugin-cloudflare/src/index.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/vite-plugin-cloudflare/src/index.ts b/packages/vite-plugin-cloudflare/src/index.ts index f75bebeacdca4..7ca137b66b285 100644 --- a/packages/vite-plugin-cloudflare/src/index.ts +++ b/packages/vite-plugin-cloudflare/src/index.ts @@ -26,12 +26,19 @@ import { getWarningForWorkersConfigs } from "./workers-configs"; import type { PluginConfig, ResolvedPluginConfig } from "./plugin-config"; import type { Unstable_RawConfig } from "wrangler"; +/** + * Vite plugin that enables a full-featured integration between Vite and the Cloudflare Workers runtime. + * + * See the [README](https://github.com/cloudflare/workers-sdk/tree/main/packages/vite-plugin-cloudflare#readme) for more details. + * + * @param pluginConfig An optional {@link PluginConfig} object. + */ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin { let resolvedPluginConfig: ResolvedPluginConfig; let resolvedViteConfig: vite.ResolvedConfig; let miniflare: Miniflare | undefined; - // this flag is used to shown the workers configs warning only once + // this flag is used to show the workers configs warning only once let workersConfigsWarningShown = false; return { From 29f2076a64d6b74d6cb72d297bea2babd87edb69 Mon Sep 17 00:00:00 2001 From: James Opstad <13586373+jamesopstad@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:12:47 +0000 Subject: [PATCH 20/24] Vite plugin README updates (#7837) * Fix link in README * Used semicolons in README code examples * Match default prettier config in README --- .prettierrc | 3 +- packages/vite-plugin-cloudflare/README.md | 60 +++++++++++------------ 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/.prettierrc b/.prettierrc index 897577d5f75b6..fd35e6a89ef86 100644 --- a/.prettierrc +++ b/.prettierrc @@ -30,8 +30,7 @@ "files": "packages/vite-plugin-cloudflare/README.md", "options": { "useTabs": false, - "semi": false, - "singleQuote": true, + "trailingComma": "all", "plugins": [] } } diff --git a/packages/vite-plugin-cloudflare/README.md b/packages/vite-plugin-cloudflare/README.md index 20a3f4e79b6b0..edfe588b62722 100644 --- a/packages/vite-plugin-cloudflare/README.md +++ b/packages/vite-plugin-cloudflare/README.md @@ -1,6 +1,6 @@ # `@cloudflare/vite-plugin` -[Intro](#intro) | [Quick start](#quick-start) | [Tutorial](#tutorial) | [API](#api) | [Cloudflare environments](#worker-environments) | [Migrating from `wrangler dev`](#migrating-from-wrangler-dev) +[Intro](#intro) | [Quick start](#quick-start) | [Tutorial](#tutorial) | [API](#api) | [Cloudflare environments](#cloudflare-environments) | [Migrating from `wrangler dev`](#migrating-from-wrangler-dev) ## Intro @@ -27,12 +27,12 @@ npm install @cloudflare/vite-plugin wrangler --save-dev ```ts // vite.config.ts -import { defineConfig } from 'vite' -import { cloudflare } from '@cloudflare/vite-plugin' +import { defineConfig } from "vite"; +import { cloudflare } from "@cloudflare/vite-plugin"; export default defineConfig({ plugins: [cloudflare()], -}) +}); ``` ### Create your Worker config file @@ -52,9 +52,9 @@ main = "./src/index.ts" export default { fetch() { - return new Response(`Running in ${navigator.userAgent}!`) + return new Response(`Running in ${navigator.userAgent}!`); }, -} +}; ``` You can now develop (`vite dev`), build (`vite build`), preview (`vite preview`), and deploy (`wrangler deploy`) your application. @@ -88,13 +88,13 @@ npm install @cloudflare/vite-plugin wrangler --save-dev ```ts // vite.config.ts -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import { cloudflare } from '@cloudflare/vite-plugin' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { cloudflare } from "@cloudflare/vite-plugin"; export default defineConfig({ plugins: [react(), cloudflare()], -}) +}); ``` #### Create your Worker config file @@ -181,22 +181,22 @@ The assets `binding` defined here will allow you to access the assets functional // api/index.ts interface Env { - ASSETS: Fetcher + ASSETS: Fetcher; } export default { fetch(request, env) { - const url = new URL(request.url) + const url = new URL(request.url); - if (url.pathname.startsWith('/api/')) { + if (url.pathname.startsWith("/api/")) { return Response.json({ - name: 'Cloudflare', - }) + name: "Cloudflare", + }); } - return env.ASSETS.fetch(request) + return env.ASSETS.fetch(request); }, -} satisfies ExportedHandler +} satisfies ExportedHandler; ``` The Worker above will be invoked for any request not matching a static asset. @@ -211,14 +211,14 @@ Replace the file contents with the following code: ```tsx // src/App.tsx -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' +import { useState } from "react"; +import reactLogo from "./assets/react.svg"; +import viteLogo from "/vite.svg"; +import "./App.css"; function App() { - const [count, setCount] = useState(0) - const [name, setName] = useState('unknown') + const [count, setCount] = useState(0); + const [name, setName] = useState("unknown"); return ( <> @@ -245,9 +245,9 @@ function App() {