diff --git a/.changeset/many-fishes-raise.md b/.changeset/many-fishes-raise.md new file mode 100644 index 000000000000..1a360dbe28d5 --- /dev/null +++ b/.changeset/many-fishes-raise.md @@ -0,0 +1,13 @@ +--- +"@cloudflare/vitest-pool-workers": minor +--- + +Support Vitest 4 in `@cloudflare/vitest-pool-workers`. + +This a breaking change to the `@cloudflare/vitest-pool-workers` integration in order to support Vitest v4. Along with supporting Vitest v4 (and dropping support for Vitest v2 and v3), we've made a number of changes that may require changes to your tests. Our aim has been to improve stability & the foundations of `@cloudflare/vitest-pool-workers` as we move towards a v1 release of the package. + +We've made a codemod to make the migration easier: `wrangler codemod vitest-pool-v3-to-v4`, which will make the required changes to your config file. + +- **`isolatedStorage` & `singleWorker`:** These have been removed in favour of a simpler isolation model that more closely matches Vitest. Storage isolation is now on a per test file basis, and you can make your test files share the same storage by using the Vitest flags `--max-workers=1 --no-isolate` +- **`import { env, SELF } from "cloudflare:test"`:** These have been removed in favour of `import { env, exports } from "cloudflare:workers"`. `exports.default.fetch()` has the same behaviour as `SELF.fetch()`, except that it doesn't expose Assets. To test your assets, use the `env.ASSETS` bindings or write an integration test using [`startDevWorker()` (recommended)](https://developers.cloudflare.com/workers/testing/unstable_startworker/) +- **`import { fetchMock } from "cloudflare:test"`:** This has been removed. Instead, [mock `globalThis.fetch`](https://github.com/cloudflare/workers-sdk/blob/main/fixtures/vitest-pool-workers-examples/request-mocking/test/imperative.test.ts) or use ecosystem libraries like [MSW (recommended)](https://mswjs.io/). diff --git a/.changeset/wrangler-codemod-command.md b/.changeset/wrangler-codemod-command.md new file mode 100644 index 000000000000..a776d6452c36 --- /dev/null +++ b/.changeset/wrangler-codemod-command.md @@ -0,0 +1,5 @@ +--- +"wrangler": minor +--- + +Add `wrangler codemod vitest-pool-v3-to-v4` command to help migrate `@cloudflare/vitest-pool-workers` config files from v3 to v4. 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 f0e59b0025d7..d7cc9a1c5bed 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 @@ -37,6 +37,7 @@ describe("getPlatformProxy - env", () => { vi.spyOn(console, "log").mockImplementation(() => {}); vi.spyOn(console, "error").mockImplementation(() => {}); warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + warn.mockReset(); }); describe("var bindings", () => { diff --git a/fixtures/vitest-pool-workers-examples/ai-vectorize/test/env.d.ts b/fixtures/vitest-pool-workers-examples/ai-vectorize/test/env.d.ts index 67b3610dbc7d..508751ce4ac9 100644 --- a/fixtures/vitest-pool-workers-examples/ai-vectorize/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/ai-vectorize/test/env.d.ts @@ -1,3 +1,3 @@ -declare module "cloudflare:test" { - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env {} } diff --git a/fixtures/vitest-pool-workers-examples/ai-vectorize/vitest.config.ts b/fixtures/vitest-pool-workers-examples/ai-vectorize/vitest.config.ts index 01335d4d60b3..64d5b14ec898 100644 --- a/fixtures/vitest-pool-workers-examples/ai-vectorize/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/ai-vectorize/vitest.config.ts @@ -1,14 +1,15 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + remoteBindings: false, + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], -export default defineWorkersProject({ test: { globalSetup: ["./global-setup.ts"], - poolOptions: { - workers: { - singleWorker: true, - remoteBindings: false, - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, }, }); diff --git a/fixtures/vitest-pool-workers-examples/basics-integration-auxiliary/test/env.d.ts b/fixtures/vitest-pool-workers-examples/basics-integration-auxiliary/test/env.d.ts index 076efb377d6f..8792875b089f 100644 --- a/fixtures/vitest-pool-workers-examples/basics-integration-auxiliary/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/basics-integration-auxiliary/test/env.d.ts @@ -1,6 +1,5 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv { +declare namespace Cloudflare { + interface Env { WORKER: Fetcher; } } diff --git a/fixtures/vitest-pool-workers-examples/basics-integration-auxiliary/vitest.config.ts b/fixtures/vitest-pool-workers-examples/basics-integration-auxiliary/vitest.config.ts index 8cd3a2ce8265..cb50080f7019 100644 --- a/fixtures/vitest-pool-workers-examples/basics-integration-auxiliary/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/basics-integration-auxiliary/vitest.config.ts @@ -1,42 +1,43 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - globalSetup: ["./global-setup.ts"], - poolOptions: { - workers: { - singleWorker: true, - miniflare: { - // Configuration for the test runner Worker - compatibilityDate: "2024-01-01", - compatibilityFlags: [ - // This illustrates a Worker that in production only wants v1 of Node.js compatibility. - // The Vitest pool integration will need to remove this flag since the `MockAgent` requires v2. - "no_nodejs_compat_v2", - "nodejs_compat", - // Required to use `WORKER.scheduled()`. This is an experimental - // compatibility flag, and cannot be enabled in production. - "service_binding_extra_handlers", - ], - serviceBindings: { - WORKER: "worker-under-test", - }, - - workers: [ - // Configuration for the "auxiliary" Worker under test. - // Unfortunately, auxiliary Workers cannot load their configuration - // from `wrangler.toml` files, and must be configured with Miniflare - // `WorkerOptions`. - { - name: "worker-under-test", - modules: true, - scriptPath: "./dist/index.js", // Built by `global-setup.ts` - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - ], +export default defineConfig({ + plugins: [ + cloudflareTest({ + miniflare: { + // Configuration for the test runner Worker + compatibilityDate: "2024-01-01", + compatibilityFlags: [ + // This illustrates a Worker that in production only wants v1 of Node.js compatibility. + // The Vitest pool integration will need to remove this flag since the `MockAgent` requires v2. + "no_nodejs_compat_v2", + "nodejs_compat", + // Required to use `WORKER.scheduled()`. This is an experimental + // compatibility flag, and cannot be enabled in production. + "service_binding_extra_handlers", + ], + serviceBindings: { + WORKER: "worker-under-test", }, + + workers: [ + // Configuration for the "auxiliary" Worker under test. + // Unfortunately, auxiliary Workers cannot load their configuration + // from `wrangler.toml` files, and must be configured with Miniflare + // `WorkerOptions`. + { + name: "worker-under-test", + modules: true, + scriptPath: "./dist/index.js", // Built by `global-setup.ts` + compatibilityDate: "2024-01-01", + compatibilityFlags: ["nodejs_compat"], + }, + ], }, - }, + }), + ], + + test: { + globalSetup: ["./global-setup.ts"], }, }); diff --git a/fixtures/vitest-pool-workers-examples/basics-unit-integration-self/vitest.config.ts b/fixtures/vitest-pool-workers-examples/basics-unit-integration-self/vitest.config.ts index 1b738407bce0..1e79f1c6e71a 100644 --- a/fixtures/vitest-pool-workers-examples/basics-unit-integration-self/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/basics-unit-integration-self/vitest.config.ts @@ -1,19 +1,19 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - singleWorker: true, - miniflare: { - // Required to use `SELF.scheduled()`. This is an experimental - // compatibility flag, and cannot be enabled in production. - compatibilityFlags: ["service_binding_extra_handlers"], - }, - wrangler: { - configPath: "./wrangler.jsonc", - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + miniflare: { + // Required to use `SELF.scheduled()`. This is an experimental + // compatibility flag, and cannot be enabled in production. + compatibilityFlags: ["service_binding_extra_handlers"], }, - }, - }, + wrangler: { + configPath: "./wrangler.jsonc", + }, + }), + ], + + test: {}, }); diff --git a/fixtures/vitest-pool-workers-examples/container-app/test/env.d.ts b/fixtures/vitest-pool-workers-examples/container-app/test/env.d.ts index d2a42e257b6f..0ac83a71e24e 100644 --- a/fixtures/vitest-pool-workers-examples/container-app/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/container-app/test/env.d.ts @@ -1,6 +1,5 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env { +declare namespace Cloudflare { + interface Env { MY_CONTAINER: DurableObjectNamespace; } } diff --git a/fixtures/vitest-pool-workers-examples/container-app/vitest.config.ts b/fixtures/vitest-pool-workers-examples/container-app/vitest.config.ts index ed7d33c5d8b2..ef0e590042db 100644 --- a/fixtures/vitest-pool-workers-examples/container-app/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/container-app/vitest.config.ts @@ -1,12 +1,12 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - singleWorker: true, - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], + + test: {}, }); diff --git a/fixtures/vitest-pool-workers-examples/context-exports/src/worker-configuration.d.ts b/fixtures/vitest-pool-workers-examples/context-exports/src/worker-configuration.d.ts index 67961094775b..25f6ac991314 100644 --- a/fixtures/vitest-pool-workers-examples/context-exports/src/worker-configuration.d.ts +++ b/fixtures/vitest-pool-workers-examples/context-exports/src/worker-configuration.d.ts @@ -5,6 +5,7 @@ declare namespace Cloudflare { } interface Env { NAME: string; + AUXILIARY_WORKER: Fetcher; } } interface Env extends Cloudflare.Env {} diff --git a/fixtures/vitest-pool-workers-examples/context-exports/test/auxiliary.test.ts b/fixtures/vitest-pool-workers-examples/context-exports/test/auxiliary.test.ts index a7c8c80c842f..e791dc87384c 100644 --- a/fixtures/vitest-pool-workers-examples/context-exports/test/auxiliary.test.ts +++ b/fixtures/vitest-pool-workers-examples/context-exports/test/auxiliary.test.ts @@ -1,13 +1,6 @@ import { env } from "cloudflare:test"; import { it } from "vitest"; -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv { - AUXILIARY_WORKER: Fetcher; - } -} - it("uses the correct context exports on the auxiliary worker", async ({ expect, }) => { diff --git a/fixtures/vitest-pool-workers-examples/context-exports/test/integration-self.test.ts b/fixtures/vitest-pool-workers-examples/context-exports/test/integration-self.test.ts index 3a1e9657074e..7da1c676d717 100644 --- a/fixtures/vitest-pool-workers-examples/context-exports/test/integration-self.test.ts +++ b/fixtures/vitest-pool-workers-examples/context-exports/test/integration-self.test.ts @@ -9,20 +9,22 @@ it("can use context exports on the SELF worker", async ({ expect }) => { ); }); -it("can use context exports (parameterized with props) on the SELF worker", async ({ +it("can use context exports (parameterized with props) on the exports.default worker", async ({ expect, }) => { - const response = await SELF.fetch("http://example.com/props"); + const response = await exports.default.fetch("http://example.com/props"); expect(await response.text()).toBe( "👋 Hello MainWorker from Main NamedEntryPoint!\nAdditional props!!" ); }); -it("will warn on missing context exports on the SELF worker", async ({ +it("will warn on missing context exports on the exports.default worker", async ({ expect, }) => { const warnSpy = vi.spyOn(console, "warn"); - const response = await SELF.fetch("http://example.com/invalid-export"); + const response = await exports.default.fetch( + "http://example.com/invalid-export" + ); expect(await response.text()).toMatchInlineSnapshot(`"👋 undefined"`); expect(warnSpy).toHaveBeenCalledWith( "Attempted to access 'ctx.exports.InvalidExport', which was not defined for the main 'SELF' Worker.\n" + @@ -31,13 +33,15 @@ it("will warn on missing context exports on the SELF worker", async ({ ); }); -it("will warn on implicit re-exports that will exist in production but cannot not be guessed on the SELF worker", async ({ +it("will warn on implicit re-exports that will exist in production but cannot not be guessed on the exports.default worker", async ({ expect, }) => { // In this test, we are trying to access an entry-point that is wildcard (*) re-exported from a virtual module. // This virtual module is understood by Vitest and TypeScript but not the lightweight esbuild that we use to guess exports. const warnSpy = vi.spyOn(console, "warn"); - const response = await SELF.fetch("http://example.com/virtual-implicit"); + const response = await exports.default.fetch( + "http://example.com/virtual-implicit" + ); expect(await response.text()).toMatchInlineSnapshot(`"👋 undefined"`); expect(warnSpy).toHaveBeenCalledWith( "Attempted to access 'ctx.exports.ReexportedVirtualEntryPoint', which was not defined for the main 'SELF' Worker.\n" + @@ -46,31 +50,33 @@ it("will warn on implicit re-exports that will exist in production but cannot no ); }); -it("will still guess re-exports on the SELF worker that cannot be fully analyzed by esbuild", async ({ +it("will still guess re-exports on the exports.default worker that cannot be fully analyzed by esbuild", async ({ expect, }) => { // In this test, we are trying to access an entry-point that is explicitly re-exported from a virtual module. // Although esbuild cannot really analyze what is being re-exported, it can at least see that something is being re-exported with that name. - const warnSpy = vi.spyOn(console, "warn"); - const response = await SELF.fetch("http://example.com/virtual-explicit"); + const response = await exports.default.fetch( + "http://example.com/virtual-explicit" + ); expect(await response.text()).toBe( "👋 Hello MainWorker from ExplicitVirtualEntryPoint!" ); }); -it("can access configured virtual entry points on the SELF worker that cannot be fully analyzed by esbuild", async ({ +it("can access configured virtual entry points on the exports.default worker that cannot be fully analyzed by esbuild", async ({ expect, }) => { // In this test, we are trying to access an entry-point that is explicitly re-exported from a virtual module. // Although esbuild cannot really analyze what is being re-exported, it can at least see that something is being re-exported with that name. - const warnSpy = vi.spyOn(console, "warn"); - const response = await SELF.fetch("http://example.com/virtual-configured"); + const response = await exports.default.fetch( + "http://example.com/virtual-configured" + ); expect(await response.text()).toBe( "👋 Hello MainWorker from ConfiguredVirtualEntryPoint!" ); }); -it("can access imported context exports for SELF worker", async ({ +it("can access imported context exports for exports.default worker", async ({ expect, }) => { const msg = await exports.NamedEntryPoint.greet(); diff --git a/fixtures/vitest-pool-workers-examples/context-exports/vitest.config.ts b/fixtures/vitest-pool-workers-examples/context-exports/vitest.config.ts index 9c45cb60458b..fc5f5c5ccef7 100644 --- a/fixtures/vitest-pool-workers-examples/context-exports/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/context-exports/vitest.config.ts @@ -1,4 +1,5 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; // Configuration for the "auxiliary" Worker under test. // Unfortunately, auxiliary Workers cannot load their configuration @@ -15,21 +16,22 @@ export const auxiliaryWorker = { }, }; -export default defineWorkersProject({ +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./src/wrangler.jsonc" }, + miniflare: { + workers: [auxiliaryWorker], + }, + additionalExports: { + // This entrypoint is wildcard re-exported from a virtual module so we cannot automatically infer it. + ConfiguredVirtualEntryPoint: "WorkerEntrypoint", + }, + }), + ], + test: { globalSetup: ["./global-setup.ts"], - poolOptions: { - workers: { - wrangler: { configPath: "./src/wrangler.jsonc" }, - miniflare: { - workers: [auxiliaryWorker], - }, - additionalExports: { - // This entrypoint is wildcard re-exported from a virtual module so we cannot automatically infer it. - ConfiguredVirtualEntryPoint: "WorkerEntrypoint", - }, - }, - }, alias: { // This alias is used to simulate a virtual module that Vitest and TypeScript can understand, // but esbuild (used by the vitest-pool-workers to guess exports) cannot. diff --git a/fixtures/vitest-pool-workers-examples/context-exports/vitest.isolated-storage.config.ts b/fixtures/vitest-pool-workers-examples/context-exports/vitest.isolated-storage.config.ts deleted file mode 100644 index 2fb6a96f2b07..000000000000 --- a/fixtures/vitest-pool-workers-examples/context-exports/vitest.isolated-storage.config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; -import { auxiliaryWorker } from "./vitest.config"; - -export default defineWorkersProject({ - test: { - name: "context-exports-isolated-storage", - globalSetup: ["./global-setup.ts"], - poolOptions: { - workers: { - isolatedStorage: true, - wrangler: { configPath: "./src/wrangler.jsonc" }, - miniflare: { - workers: [auxiliaryWorker], - }, - additionalExports: { - // This entrypoint is wildcard re-exported from a virtual module so we cannot automatically infer it. - ConfiguredVirtualEntryPoint: "WorkerEntrypoint", - }, - }, - }, - alias: { - // This alias is used to simulate a virtual module that Vitest and TypeScript can understand, - // but esbuild (used by the vitest-pool-workers to guess exports) cannot. - "@virtual-module": "./virtual.ts", - }, - }, -}); diff --git a/fixtures/vitest-pool-workers-examples/context-exports/vitest.single-worker.config.ts b/fixtures/vitest-pool-workers-examples/context-exports/vitest.single-worker.config.ts deleted file mode 100644 index 6e8010d36ce2..000000000000 --- a/fixtures/vitest-pool-workers-examples/context-exports/vitest.single-worker.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; -import { kCurrentWorker } from "../../../packages/miniflare/dist/src"; -import { auxiliaryWorker } from "./vitest.config"; - -export default defineWorkersProject({ - test: { - name: "context-exports-single-worker", - globalSetup: ["./global-setup.ts"], - poolOptions: { - workers: { - singleWorker: true, - wrangler: { configPath: "./src/wrangler.jsonc" }, - miniflare: { - workers: [auxiliaryWorker], - }, - additionalExports: { - // This entrypoint is wildcard re-exported from a virtual module so we cannot automatically infer it. - ConfiguredVirtualEntryPoint: "WorkerEntrypoint", - }, - }, - }, - alias: { - // This alias is used to simulate a virtual module that Vitest and TypeScript can understand, - // but esbuild (used by the vitest-pool-workers to guess exports) cannot. - "@virtual-module": "./virtual.ts", - }, - }, -}); diff --git a/fixtures/vitest-pool-workers-examples/d1/test/env.d.ts b/fixtures/vitest-pool-workers-examples/d1/test/env.d.ts index d177afa47e0c..afabea1a21a5 100644 --- a/fixtures/vitest-pool-workers-examples/d1/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/d1/test/env.d.ts @@ -1,6 +1,6 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env { - TEST_MIGRATIONS: D1Migration[]; // Defined in `vitest.config.mts` +declare namespace Cloudflare { + interface Env { + DATABASE: D1Database; + TEST_MIGRATIONS: import("cloudflare:test").D1Migration[]; // Defined in `vitest.config.mts` } } diff --git a/fixtures/vitest-pool-workers-examples/d1/test/queries.test.ts b/fixtures/vitest-pool-workers-examples/d1/test/queries.test.ts index b54c96d79e90..f294673758bd 100644 --- a/fixtures/vitest-pool-workers-examples/d1/test/queries.test.ts +++ b/fixtures/vitest-pool-workers-examples/d1/test/queries.test.ts @@ -25,8 +25,9 @@ it("should list posts", async ({ expect }) => { await upsertPost(env, "/three", "3"); const posts = await listPosts(env); - expect(posts.length).toBe(3); // Note changes from previous test undone - expect(posts[0].body).toBe("1"); - expect(posts[1].body).toBe("2"); - expect(posts[2].body).toBe("3"); + expect(posts.length).toBe(4); + expect(posts[0].body).toBe("👋"); + expect(posts[1].body).toBe("1"); + expect(posts[2].body).toBe("2"); + expect(posts[3].body).toBe("3"); }); diff --git a/fixtures/vitest-pool-workers-examples/d1/test/routes.test.ts b/fixtures/vitest-pool-workers-examples/d1/test/routes.test.ts index 85fdaf4c56e3..18f11ddfcc59 100644 --- a/fixtures/vitest-pool-workers-examples/d1/test/routes.test.ts +++ b/fixtures/vitest-pool-workers-examples/d1/test/routes.test.ts @@ -22,7 +22,11 @@ it("should list posts", async ({ expect }) => { const response = await SELF.fetch("https://example.com/"); expect(response.status).toBe(200); expect(await response.text()).toMatchInlineSnapshot(` - "https://example.com/one + "https://example.com/hello + 👋 + + -------------------- + https://example.com/one 1 -------------------- diff --git a/fixtures/vitest-pool-workers-examples/d1/vitest.config.ts b/fixtures/vitest-pool-workers-examples/d1/vitest.config.ts index 48072e7bcb7d..6cf835fdcb2a 100644 --- a/fixtures/vitest-pool-workers-examples/d1/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/d1/vitest.config.ts @@ -1,31 +1,31 @@ import path from "node:path"; import { - defineWorkersProject, + cloudflareTest, readD1Migrations, -} from "@cloudflare/vitest-pool-workers/config"; +} from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject(async () => { +export default defineConfig(async () => { // Read all migrations in the `migrations` directory const migrationsPath = path.join(__dirname, "migrations"); const migrations = await readD1Migrations(migrationsPath); return { + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", + environment: "production", + }, + miniflare: { + // Add a test-only binding for migrations, so we can apply them in a + // setup file + bindings: { TEST_MIGRATIONS: migrations }, + }, + }), + ], test: { setupFiles: ["./test/apply-migrations.ts"], - poolOptions: { - workers: { - singleWorker: true, - wrangler: { - configPath: "./wrangler.jsonc", - environment: "production", - }, - miniflare: { - // Add a test-only binding for migrations, so we can apply them in a - // setup file - bindings: { TEST_MIGRATIONS: migrations }, - }, - }, - }, }, }; }); diff --git a/fixtures/vitest-pool-workers-examples/durable-objects/test/direct-access.test.ts b/fixtures/vitest-pool-workers-examples/durable-objects/test/direct-access.test.ts index bfa37100f408..2fe702691898 100644 --- a/fixtures/vitest-pool-workers-examples/durable-objects/test/direct-access.test.ts +++ b/fixtures/vitest-pool-workers-examples/durable-objects/test/direct-access.test.ts @@ -35,13 +35,3 @@ it("increments count and allows direct access to instance/storage", async ({ expect(ids.length).toBe(1); expect(ids[0].equals(id)).toBe(true); }); - -it("uses isolated storage for each test", async ({ expect }) => { - // Check Durable Object from previous test removed - const ids = await listDurableObjectIds(env.COUNTER); - expect(ids.length).toBe(0); - - // Check writes in previous test undone - const response = await SELF.fetch("https://example.com/path"); - expect(await response.text()).toBe("1"); -}); diff --git a/fixtures/vitest-pool-workers-examples/durable-objects/test/env.d.ts b/fixtures/vitest-pool-workers-examples/durable-objects/test/env.d.ts index bd9eb32999df..e66694406360 100644 --- a/fixtures/vitest-pool-workers-examples/durable-objects/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/durable-objects/test/env.d.ts @@ -1,4 +1,6 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env { + COUNTER: DurableObjectNamespace; + SQL: DurableObjectNamespace; + } } diff --git a/fixtures/vitest-pool-workers-examples/durable-objects/vitest.config.ts b/fixtures/vitest-pool-workers-examples/durable-objects/vitest.config.ts index 37061a9d29d5..ccf6ad0bc9ca 100644 --- a/fixtures/vitest-pool-workers-examples/durable-objects/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/durable-objects/vitest.config.ts @@ -1,15 +1,16 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", + }, + }), + ], -export default defineWorkersProject({ test: { name: "@scoped/durable-objects", - poolOptions: { - workers: { - singleWorker: true, - wrangler: { - configPath: "./wrangler.jsonc", - }, - }, - }, }, }); diff --git a/fixtures/vitest-pool-workers-examples/hyperdrive/test/env.d.ts b/fixtures/vitest-pool-workers-examples/hyperdrive/test/env.d.ts index bd9eb32999df..508751ce4ac9 100644 --- a/fixtures/vitest-pool-workers-examples/hyperdrive/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/hyperdrive/test/env.d.ts @@ -1,4 +1,3 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env {} } diff --git a/fixtures/vitest-pool-workers-examples/hyperdrive/vitest.config.ts b/fixtures/vitest-pool-workers-examples/hyperdrive/vitest.config.ts index 08468b45b3c4..1260a0a8f799 100644 --- a/fixtures/vitest-pool-workers-examples/hyperdrive/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/hyperdrive/vitest.config.ts @@ -1,25 +1,26 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - globalSetup: ["./global-setup.ts"], - poolOptions: { - workers: ({ inject }) => { - // Provided in `global-setup.ts` - const echoServerPort = inject("echoServerPort"); +export default defineConfig({ + plugins: [ + cloudflareTest(({ inject }) => { + // Provided in `global-setup.ts` + const echoServerPort = inject("echoServerPort"); - return { - singleWorker: true, - miniflare: { - hyperdrives: { - ECHO_SERVER_HYPERDRIVE: `postgres://user:pass@127.0.0.1:${echoServerPort}/db`, - }, - }, - wrangler: { - configPath: "./wrangler.jsonc", + return { + miniflare: { + hyperdrives: { + ECHO_SERVER_HYPERDRIVE: `postgres://user:pass@127.0.0.1:${echoServerPort}/db`, }, - }; - }, - }, + }, + wrangler: { + configPath: "./wrangler.jsonc", + }, + }; + }), + ], + + test: { + globalSetup: ["./global-setup.ts"], }, }); diff --git a/fixtures/vitest-pool-workers-examples/images/test/env.d.ts b/fixtures/vitest-pool-workers-examples/images/test/env.d.ts index bd9eb32999df..508751ce4ac9 100644 --- a/fixtures/vitest-pool-workers-examples/images/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/images/test/env.d.ts @@ -1,4 +1,3 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env {} } diff --git a/fixtures/vitest-pool-workers-examples/images/vitest.config.ts b/fixtures/vitest-pool-workers-examples/images/vitest.config.ts index 42e8f9ffd97f..b90cb174ce09 100644 --- a/fixtures/vitest-pool-workers-examples/images/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/images/vitest.config.ts @@ -1,14 +1,14 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - singleWorker: true, - wrangler: { - configPath: "./wrangler.jsonc", - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", }, - }, - }, + }), + ], + + test: {}, }); diff --git a/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/env.d.ts b/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/env.d.ts index bd9eb32999df..440cf1afbb7f 100644 --- a/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/env.d.ts @@ -1,4 +1,6 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env { + KV_NAMESPACE: KVNamespace; + R2_BUCKET: R2Bucket; + } } diff --git a/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/isolation.test.ts b/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/isolation.test.ts deleted file mode 100644 index 0ccf6714a9f7..000000000000 --- a/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/isolation.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { env } from "cloudflare:test"; -import { beforeAll, beforeEach, describe, test } from "vitest"; - -// Illustrative example for `isolatedStorage` option - -// Get the current list stored in a KV namespace -async function get(): Promise { - return (await env.KV_NAMESPACE.get("list", "json")) ?? []; -} -// Add an item to the end of the list -async function append(item: string) { - const value = await get(); - value.push(item); - await env.KV_NAMESPACE.put("list", JSON.stringify(value)); -} - -beforeAll(() => append("all")); -beforeEach(() => append("each")); - -test("one", async ({ expect }) => { - // Each test gets its own storage environment copied from the parent - await append("one"); - expect(await get()).toStrictEqual(["all", "each", "one"]); -}); -// `append("each")` and `append("one")` undone -test("two", async ({ expect }) => { - await append("two"); - expect(await get()).toStrictEqual(["all", "each", "two"]); -}); -// `append("each")` and `append("two")` undone - -describe("describe", async () => { - beforeAll(() => append("describe all")); - beforeEach(() => append("describe each")); - - test("three", async ({ expect }) => { - await append("three"); - expect(await get()).toStrictEqual([ - // All `beforeAll()`s run before `beforeEach()`s - "all", - "describe all", - "each", - "describe each", - "three", - ]); - }); - // `append("each")`, `append("describe each")` and `append("three")` undone - test("four", async ({ expect }) => { - await append("four"); - expect(await get()).toStrictEqual([ - "all", - "describe all", - "each", - "describe each", - "four", - ]); - }); - // `append("each")`, `append("describe each")` and `append("four")` undone -}); diff --git a/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/kv.test.ts b/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/kv.test.ts index 63156a93cc3c..1bc3b1aed4ed 100644 --- a/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/kv.test.ts +++ b/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/kv.test.ts @@ -12,9 +12,3 @@ it("stores in KV namespace", async ({ expect }) => { expect(response.status).toBe(200); expect(await response.text()).toBe("value"); }); - -it("uses isolated storage for each test", async ({ expect }) => { - // Check write in previous test undone - const response = await SELF.fetch("https://example.com/kv/key"); - expect(response.status).toBe(204); -}); diff --git a/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/r2.test.ts b/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/r2.test.ts index bd323b641573..b5a55c49fa3b 100644 --- a/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/r2.test.ts +++ b/fixtures/vitest-pool-workers-examples/kv-r2-caches/test/r2.test.ts @@ -31,9 +31,3 @@ it("stores in R2 bucket", async ({ expect }) => { expect(response.headers.get("CF-Cache-Status")).toBe("HIT"); expect(await response.text()).toBe("value"); }); - -it("uses isolated storage for each test", async ({ expect }) => { - // Check write in previous test undone - const response = await SELF.fetch("https://example.com/r2/key"); - expect(response.status).toBe(204); -}); diff --git a/fixtures/vitest-pool-workers-examples/kv-r2-caches/vitest.config.ts b/fixtures/vitest-pool-workers-examples/kv-r2-caches/vitest.config.ts index 42e8f9ffd97f..b90cb174ce09 100644 --- a/fixtures/vitest-pool-workers-examples/kv-r2-caches/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/kv-r2-caches/vitest.config.ts @@ -1,14 +1,14 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - singleWorker: true, - wrangler: { - configPath: "./wrangler.jsonc", - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", }, - }, - }, + }), + ], + + test: {}, }); diff --git a/fixtures/vitest-pool-workers-examples/misc/global-setup.ts b/fixtures/vitest-pool-workers-examples/misc/global-setup.ts new file mode 100644 index 000000000000..3ebff2e7876f --- /dev/null +++ b/fixtures/vitest-pool-workers-examples/misc/global-setup.ts @@ -0,0 +1,12 @@ +// Regression test for #9957: In v3, `provide` data was sent via HTTP headers +// (~8 KB limit). Large payloads caused silent failures. v4 sends provide data +// through WebSocket messages (32 MiB limit). +import type { TestProject } from "vitest/node"; + +export default function ({ provide }: TestProject) { + // No ProvidedContext declaration for this key, so cast is needed + (provide as (key: string, value: unknown) => void)( + "largePayload", + "x".repeat(50_000) + ); +} diff --git a/fixtures/vitest-pool-workers-examples/misc/test/assets.test.ts b/fixtures/vitest-pool-workers-examples/misc/test/assets.test.ts deleted file mode 100644 index c2b582890f29..000000000000 --- a/fixtures/vitest-pool-workers-examples/misc/test/assets.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { SELF } from "cloudflare:test"; -import { it } from "vitest"; - -it("reads assets from the configured directory", async ({ expect }) => { - expect( - await (await SELF.fetch("http://example.com/test.txt")).text() - ).toMatchInlineSnapshot(`"Hello, World!"`); -}); diff --git a/fixtures/vitest-pool-workers-examples/misc/test/bare-specifiers.test.ts b/fixtures/vitest-pool-workers-examples/misc/test/bare-specifiers.test.ts new file mode 100644 index 000000000000..6fbeea5debfe --- /dev/null +++ b/fixtures/vitest-pool-workers-examples/misc/test/bare-specifiers.test.ts @@ -0,0 +1,26 @@ +// Regression test for #6214: bare Node.js module specifiers (without `node:` +// prefix) should resolve. The `module-fallback.ts` `viteResolve()` codepath +// prepends `node:` when a bare specifier isn't found in workerd built-ins. +import { describe, it } from "vitest"; + +describe("bare Node module specifiers", () => { + it("resolves 'url' without node: prefix", async ({ expect }) => { + const urlModule = await import("url"); + expect(urlModule.URL).toBeDefined(); + expect(new urlModule.URL("https://example.com").hostname).toBe( + "example.com" + ); + }); + + it("resolves 'path' without node: prefix", async ({ expect }) => { + const pathModule = await import("path"); + expect(pathModule.join).toBeDefined(); + expect(pathModule.join("/foo", "bar")).toBe("/foo/bar"); + }); + + it("resolves 'buffer' without node: prefix", async ({ expect }) => { + const bufferModule = await import("buffer"); + expect(bufferModule.Buffer).toBeDefined(); + expect(bufferModule.Buffer.from("hello").toString()).toBe("hello"); + }); +}); diff --git a/fixtures/vitest-pool-workers-examples/misc/test/env.d.ts b/fixtures/vitest-pool-workers-examples/misc/test/env.d.ts index c5077a66cf49..9e4376dafd67 100644 --- a/fixtures/vitest-pool-workers-examples/misc/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/misc/test/env.d.ts @@ -1,5 +1,5 @@ -declare module "cloudflare:test" { - interface ProvidedEnv { +declare namespace Cloudflare { + interface Env { ASSETS: Fetcher; KV_NAMESPACE: KVNamespace; OTHER_OBJECT: DurableObjectNamespace; diff --git a/fixtures/vitest-pool-workers-examples/misc/test/fetch-mock.test.ts b/fixtures/vitest-pool-workers-examples/misc/test/fetch-mock.test.ts deleted file mode 100644 index 9e0499060d1f..000000000000 --- a/fixtures/vitest-pool-workers-examples/misc/test/fetch-mock.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { fetchMock } from "cloudflare:test"; -import { - afterAll, - afterEach, - beforeAll, - beforeEach, - describe, - it, - vi, -} from "vitest"; -import type { MockInstance } from "vitest"; - -beforeEach(() => fetchMock.activate()); -afterEach(() => { - fetchMock.assertNoPendingInterceptors(); - fetchMock.deactivate(); -}); - -it("falls through to global fetch() if unmatched", async ({ expect }) => { - fetchMock - .get("https://example.com") - .intercept({ path: "/" }) - .reply(200, "body"); - - let response = await fetch("https://example.com"); - expect(response.url).toEqual("https://example.com/"); - expect(await response.text()).toBe("body"); - - response = await fetch("https://example.com/bad"); - expect(response.url).toEqual("https://example.com/bad"); - expect(await response.text()).toBe("fallthrough:GET https://example.com/bad"); -}); - -it("intercepts URLs with query parameters with repeated keys", async ({ - expect, -}) => { - fetchMock - .get("https://example.com") - .intercept({ path: "/foo", query: { key: "value" } }) - .reply(200, "foo"); - - fetchMock - .get("https://example.com") - .intercept({ path: "/bar?a=1&a=2" }) - .reply(200, "bar"); - - fetchMock - .get("https://example.com") - .intercept({ path: "/baz", query: { key1: ["a", "b"], key2: "c" } }) - .reply(200, "baz"); - - let response1 = await fetch("https://example.com/foo?key=value"); - expect(response1.url).toEqual("https://example.com/foo?key=value"); - expect(await response1.text()).toBe("foo"); - - let response2 = await fetch("https://example.com/bar?a=1&a=2"); - expect(response2.url).toEqual("https://example.com/bar?a=1&a=2"); - expect(await response2.text()).toBe("bar"); - - let response3 = await fetch("https://example.com/baz?key1=a&key2=c&key1=b"); - expect(response3.url).toEqual("https://example.com/baz?key1=a&key2=c&key1=b"); - expect(await response3.text()).toBe("baz"); -}); - -it("throws if you try to mutate the headers", async ({ expect }) => { - fetchMock - .get("https://example.com") - .intercept({ path: "/" }) - .reply(200, "body"); - - let response = await fetch("https://example.com"); - - expect(() => response.headers.set("foo", "bar")).toThrowError(); - expect(() => response.headers.append("foo", "baz")).toThrowError(); - expect(() => response.headers.delete("foo")).toThrowError(); -}); - -describe("AbortSignal", () => { - let abortSignalTimeoutMock: MockInstance; - - beforeAll(() => { - // Fake Timers does not mock AbortSignal.timeout - abortSignalTimeoutMock = vi - .spyOn(AbortSignal, "timeout") - .mockImplementation((ms: number) => { - const controller = new AbortController(); - setTimeout(() => { - controller.abort(); - }, ms); - return controller.signal; - }); - }); - - afterAll(() => abortSignalTimeoutMock.mockRestore()); - - beforeEach(() => vi.useFakeTimers()); - - afterEach(() => vi.useRealTimers()); - - it("aborts if an AbortSignal timeout is exceeded", async ({ expect }) => { - fetchMock - .get("https://example.com") - .intercept({ path: "/" }) - .reply(200, async () => { - await new Promise((resolve) => setTimeout(resolve, 5000)); - return "Delayed response"; - }); - - const fetchPromise = fetch("https://example.com", { - signal: AbortSignal.timeout(2000), - }); - - vi.advanceTimersByTime(10_000); - await expect(fetchPromise).rejects.toThrowErrorMatchingInlineSnapshot( - `[AbortError: The operation was aborted]` - ); - }); - - it("does not abort if an AbortSignal timeout is not exceeded", async ({ - expect, - }) => { - fetchMock - .get("https://example.com") - .intercept({ path: "/" }) - .reply(200, async () => { - await new Promise((resolve) => setTimeout(resolve, 1000)); - return "Delayed response"; - }); - - const fetchPromise = fetch("https://example.com", { - signal: AbortSignal.timeout(2000), - }); - - vi.advanceTimersByTime(1500); - const response = await fetchPromise; - expect(response.status).toStrictEqual(200); - expect(await response.text()).toMatchInlineSnapshot(`"Delayed response"`); - }); - - it("aborts if an AbortSignal is already aborted", async ({ expect }) => { - const controller = new AbortController(); - controller.abort(); - - fetchMock - .get("https://example.com") - .intercept({ path: "/" }) - .reply(200, async () => { - return "Delayed response"; - }); - - const fetchPromise = fetch("https://example.com", { - signal: controller.signal, - }); - - await expect(fetchPromise).rejects.toThrowErrorMatchingInlineSnapshot( - `[AbortError: The operation was aborted]` - ); - }); -}); diff --git a/fixtures/vitest-pool-workers-examples/misc/test/large-provide.test.ts b/fixtures/vitest-pool-workers-examples/misc/test/large-provide.test.ts new file mode 100644 index 000000000000..3e6998623077 --- /dev/null +++ b/fixtures/vitest-pool-workers-examples/misc/test/large-provide.test.ts @@ -0,0 +1,12 @@ +// Regression test for #9957: In v3, `provide` data was sent via HTTP headers +// (~8 KB limit). v4 sends provide data through WebSocket messages (32 MiB +// limit). This test injects a 50 KB string provided by global-setup.ts. +import { inject, it } from "vitest"; + +it("receives 50 KB of provided data without truncation", ({ expect }) => { + const data = inject("largePayload" as never); + expect(data).toBeDefined(); + expect(typeof data).toBe("string"); + expect((data as string).length).toBe(50_000); + expect(data).toBe("x".repeat(50_000)); +}); diff --git a/fixtures/vitest-pool-workers-examples/misc/test/msw.test.ts b/fixtures/vitest-pool-workers-examples/misc/test/msw.test.ts new file mode 100644 index 000000000000..aac8a34afff9 --- /dev/null +++ b/fixtures/vitest-pool-workers-examples/misc/test/msw.test.ts @@ -0,0 +1,61 @@ +import { http, HttpResponse } from "msw"; +import { expect, it } from "vitest"; +import { server } from "./server"; + +it("falls through to global fetch() if unmatched", async () => { + server.use( + http.get( + "https://example.com", + () => { + return HttpResponse.text("body"); + }, + { once: true } + ) + ); + + let response = await fetch("https://example.com"); + expect(response.url).toEqual("https://example.com/"); + expect(await response.text()).toBe("body"); + + response = await fetch("https://example.com/bad"); + expect(response.url).toEqual("https://example.com/bad"); + expect(await response.text()).toBe("fallthrough:GET https://example.com/bad"); +}); + +it("intercepts URLs with query parameters with repeated keys", async () => { + server.use( + http.get( + "https://example.com/foo?key=value", + () => { + return HttpResponse.text("foo"); + }, + { once: true } + ), + http.get( + "https://example.com/bar?a=1&a=2", + () => { + return HttpResponse.text("bar"); + }, + { once: true } + ), + http.get( + "https://example.com/baz?key1=a&key2=c&key1=b", + () => { + return HttpResponse.text("baz"); + }, + { once: true } + ) + ); + + let response1 = await fetch("https://example.com/foo?key=value"); + expect(response1.url).toEqual("https://example.com/foo?key=value"); + expect(await response1.text()).toBe("foo"); + + let response2 = await fetch("https://example.com/bar?a=1&a=2"); + expect(response2.url).toEqual("https://example.com/bar?a=1&a=2"); + expect(await response2.text()).toBe("bar"); + + let response3 = await fetch("https://example.com/baz?key1=a&key2=c&key1=b"); + expect(response3.url).toEqual("https://example.com/baz?key1=a&key2=c&key1=b"); + expect(await response3.text()).toBe("baz"); +}); diff --git a/fixtures/vitest-pool-workers-examples/misc/test/pages-functions.test.ts b/fixtures/vitest-pool-workers-examples/misc/test/pages-functions.test.ts index ea8af620fb35..817484d91dfb 100644 --- a/fixtures/vitest-pool-workers-examples/misc/test/pages-functions.test.ts +++ b/fixtures/vitest-pool-workers-examples/misc/test/pages-functions.test.ts @@ -1,7 +1,6 @@ import { createPagesEventContext, env, - ProvidedEnv, waitOnExecutionContext, } from "cloudflare:test"; import { it, onTestFinished } from "vitest"; @@ -11,7 +10,7 @@ import { it, onTestFinished } from "vitest"; // `Request` to pass to `createPagesEventContext()`. const IncomingRequest = Request; -type BareFunction = PagesFunction>; +type BareFunction = PagesFunction>; it("can consume body in middleware and in next request", async ({ expect }) => { const fn: BareFunction = async (ctx) => { @@ -94,7 +93,7 @@ it("requires ASSETS service binding", async ({ expect }) => { onTestFinished(() => { env.ASSETS = originalASSETS; }); - delete (env as Partial).ASSETS; + delete (env as Partial).ASSETS; const request = new IncomingRequest("https://example.com", { method: "POST", @@ -124,7 +123,7 @@ it("correctly types parameters", async ({ expect }) => { // Check no params and no data required { - type Fn = PagesFunction>; + type Fn = PagesFunction>; createPagesEventContext({ request }); createPagesEventContext({ request, params: {} }); // @ts-expect-error no params required @@ -136,7 +135,7 @@ it("correctly types parameters", async ({ expect }) => { // Check no params but data required { - type Fn = PagesFunction; + type Fn = PagesFunction; // @ts-expect-error data required createPagesEventContext({ request }); // @ts-expect-error data required @@ -150,7 +149,7 @@ it("correctly types parameters", async ({ expect }) => { // Check no data but params required { - type Fn = PagesFunction>; + type Fn = PagesFunction>; // @ts-expect-error params required createPagesEventContext({ request }); // @ts-expect-error params required @@ -164,7 +163,7 @@ it("correctly types parameters", async ({ expect }) => { // Check params and data required { - type Fn = PagesFunction; + type Fn = PagesFunction; // @ts-expect-error params required createPagesEventContext({ request }); // @ts-expect-error params required diff --git a/fixtures/vitest-pool-workers-examples/misc/test/server.ts b/fixtures/vitest-pool-workers-examples/misc/test/server.ts new file mode 100644 index 000000000000..bd0bda57d85c --- /dev/null +++ b/fixtures/vitest-pool-workers-examples/misc/test/server.ts @@ -0,0 +1,3 @@ +import { setupServer } from "msw/node"; + +export const server = setupServer(); diff --git a/fixtures/vitest-pool-workers-examples/misc/test/setup.ts b/fixtures/vitest-pool-workers-examples/misc/test/setup.ts new file mode 100644 index 000000000000..f37c96ebdd3e --- /dev/null +++ b/fixtures/vitest-pool-workers-examples/misc/test/setup.ts @@ -0,0 +1,10 @@ +import { afterAll, afterEach, beforeAll } from "vitest"; +import { server } from "./server"; + +beforeAll(() => + server.listen({ + onUnhandledRequest: "bypass", + }) +); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); diff --git a/fixtures/vitest-pool-workers-examples/misc/vitest.assets.config.ts b/fixtures/vitest-pool-workers-examples/misc/vitest.assets.config.ts index a7fa2cd31647..c0ec68a1ab66 100644 --- a/fixtures/vitest-pool-workers-examples/misc/vitest.assets.config.ts +++ b/fixtures/vitest-pool-workers-examples/misc/vitest.assets.config.ts @@ -1,22 +1,23 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + miniflare: { + assets: { + directory: "./public", + binding: "ASSETS", + }, + }, + wrangler: { + configPath: "./wrangler.assets.jsonc", + }, + }), + ], -export default defineWorkersProject({ test: { name: "misc-assets", include: ["test/assets.test.ts"], - poolOptions: { - workers: { - singleWorker: true, - miniflare: { - assets: { - directory: "./public", - binding: "ASSETS", - }, - }, - wrangler: { - configPath: "./wrangler.assets.jsonc", - }, - }, - }, }, }); diff --git a/fixtures/vitest-pool-workers-examples/misc/vitest.config.ts b/fixtures/vitest-pool-workers-examples/misc/vitest.config.ts index 8a953362eb10..901773eeb8d4 100644 --- a/fixtures/vitest-pool-workers-examples/misc/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/misc/vitest.config.ts @@ -1,38 +1,42 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; import { Response } from "miniflare"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + miniflare: { + kvNamespaces: ["KV_NAMESPACE"], + outboundService(request) { + return new Response(`fallthrough:${request.method} ${request.url}`); + }, + serviceBindings: { + ASSETS(request) { + return new Response(`assets:${request.method} ${request.url}`); + }, + }, + workers: [ + { + name: "other", + modules: true, + scriptPath: "./src/other-worker.mjs", + }, + ], + }, + wrangler: { + configPath: "./wrangler.jsonc", + }, + }), + ], -export default defineWorkersProject({ define: { CONFIG_DEFINED_THING: '"thing"', "CONFIG_NESTED.DEFINED.THING": "[1,2,3]", }, + test: { exclude: ["test/assets.test.ts", "test/nodejs.test.ts"], - poolOptions: { - workers: { - singleWorker: true, - miniflare: { - kvNamespaces: ["KV_NAMESPACE"], - outboundService(request) { - return new Response(`fallthrough:${request.method} ${request.url}`); - }, - serviceBindings: { - ASSETS(request) { - return new Response(`assets:${request.method} ${request.url}`); - }, - }, - workers: [ - { - name: "other", - modules: true, - scriptPath: "./src/other-worker.mjs", - }, - ], - }, - wrangler: { - configPath: "./wrangler.jsonc", - }, - }, - }, + globalSetup: ["./global-setup.ts"], + setupFiles: ["test/setup.ts"], }, }); diff --git a/fixtures/vitest-pool-workers-examples/misc/vitest.nodejs.config.ts b/fixtures/vitest-pool-workers-examples/misc/vitest.nodejs.config.ts index c34715977fce..6ca44fadb166 100644 --- a/fixtures/vitest-pool-workers-examples/misc/vitest.nodejs.config.ts +++ b/fixtures/vitest-pool-workers-examples/misc/vitest.nodejs.config.ts @@ -1,16 +1,17 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.nodejs.jsonc", + }, + }), + ], -export default defineWorkersProject({ test: { name: "misc-nodejs", include: ["test/nodejs.test.ts"], - poolOptions: { - workers: { - singleWorker: true, - wrangler: { - configPath: "./wrangler.nodejs.jsonc", - }, - }, - }, }, }); diff --git a/fixtures/vitest-pool-workers-examples/module-resolution/src/index.ts b/fixtures/vitest-pool-workers-examples/module-resolution/src/index.ts index e4bb52a21512..ba0c8cd47c6e 100644 --- a/fixtures/vitest-pool-workers-examples/module-resolution/src/index.ts +++ b/fixtures/vitest-pool-workers-examples/module-resolution/src/index.ts @@ -1,10 +1,15 @@ -import { Toucan } from "toucan-js"; -// Testing dependency without a main entrypoint -// @see https://github.com/cloudflare/workers-sdk/issues/6591 -import "discord-api-types/v10"; +import * as bAuthStripe from "@better-auth/stripe"; // Testing dependency with browser field mapping // @see https://github.com/cloudflare/workers-sdk/issues/6581 -import "@microlabs/otel-cf-workers"; +import * as otel from "@microlabs/otel-cf-workers"; +import * as bAuth from "better-auth"; +// Testing dependency without a main entrypoint +// @see https://github.com/cloudflare/workers-sdk/issues/6591 +import * as discord from "discord-api-types/v10"; +import * as jose from "jose"; +import nunjucks from "nunjucks"; +import Stripe from "stripe"; +import { Toucan } from "toucan-js"; export default { async fetch(): Promise { @@ -12,7 +17,25 @@ export default { const _ = new Toucan({ dsn: "https://foo@sentry.com/123456", }); + const a = Stripe; + + nunjucks.configure({ autoescape: true }); + + console.log( + nunjucks.renderString("Hello {{ username }}", { username: "James" }) + ); - return new Response("Hello World!"); + // Make sure none of the imports are tree shaken + return new Response( + JSON.stringify({ + discord, + otel, + jose, + bAuth, + bAuthStripe, + a, + n: nunjucks.renderString("Hello {{ username }}", { username: "James" }), + }) + ); }, }; diff --git a/fixtures/vitest-pool-workers-examples/module-resolution/test/index.spec.ts b/fixtures/vitest-pool-workers-examples/module-resolution/test/index.spec.ts index b92b2c56f4e0..cb3c9762d25c 100644 --- a/fixtures/vitest-pool-workers-examples/module-resolution/test/index.spec.ts +++ b/fixtures/vitest-pool-workers-examples/module-resolution/test/index.spec.ts @@ -1,8 +1,10 @@ import { instrument } from "@microlabs/otel-cf-workers"; +import { SELF } from "cloudflare:test"; import { Utils } from "discord-api-types/v10"; import dep from "ext-dep"; import mime from "mime-types"; -import { assert, describe, test } from "vitest"; +import { assert, describe, expect, test } from "vitest"; +import worker from "../src/index"; import sqlPlain from "../src/test.sql"; import sqlRaw from "../src/test.sql?raw"; @@ -11,16 +13,23 @@ describe("test", () => { assert.equal(dep, 123); }); - // This requires the `deps.optimizer` option to be set in the vitest config test("resolves dependency without a default entrypoint", async () => { assert.isFunction(Utils.isDMInteraction); }); - // This requires the `deps.optimizer` option to be set in the vitest config test("resolves dependency with mapping on the browser field", async () => { assert.isFunction(instrument); }); + test("can use toucan-js (integration)", async () => { + expect((await SELF.fetch("http://example.com")).status).toBe(200); + }); + + test("can use toucan-js (unit)", async () => { + const response = await worker.fetch(); + expect(response.status).toBe(200); + }); + // Regression test for https://github.com/cloudflare/workers-sdk/issues/12049 // Vite query parameters like ?raw should be handled by Vite, not module rules test("resolves file with ?raw query parameter", async () => { diff --git a/fixtures/vitest-pool-workers-examples/module-resolution/vitest.config.ts b/fixtures/vitest-pool-workers-examples/module-resolution/vitest.config.ts index f47effc9d612..9b79788eddd6 100644 --- a/fixtures/vitest-pool-workers-examples/module-resolution/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/module-resolution/vitest.config.ts @@ -1,19 +1,10 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - deps: { - optimizer: { - ssr: { - enabled: true, - include: ["discord-api-types/v10", "@microlabs/otel-cf-workers"], - }, - }, - }, - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], }); diff --git a/fixtures/vitest-pool-workers-examples/module-resolution/wrangler.jsonc b/fixtures/vitest-pool-workers-examples/module-resolution/wrangler.jsonc index 5bac18d5ec57..2c5be6a6ee07 100644 --- a/fixtures/vitest-pool-workers-examples/module-resolution/wrangler.jsonc +++ b/fixtures/vitest-pool-workers-examples/module-resolution/wrangler.jsonc @@ -1,5 +1,7 @@ { "name": "module-resolution", "main": "src/index.ts", + "compatibility_date": "2025-12-13", + "compatibility_flags": ["nodejs_compat"], // don't provide compatibility_date so that vitest will infer the latest one } diff --git a/fixtures/vitest-pool-workers-examples/multiple-workers/test/env.d.ts b/fixtures/vitest-pool-workers-examples/multiple-workers/test/env.d.ts index 4b8e62a89196..b428c0569ead 100644 --- a/fixtures/vitest-pool-workers-examples/multiple-workers/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/multiple-workers/test/env.d.ts @@ -1,6 +1,6 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env { +declare namespace Cloudflare { + interface Env { + DATABASE_SERVICE: Fetcher; TEST_AUTH_PUBLIC_KEY: string; // Defined in `vitest.config.mts` } } diff --git a/fixtures/vitest-pool-workers-examples/multiple-workers/vitest.config.ts b/fixtures/vitest-pool-workers-examples/multiple-workers/vitest.config.ts index e8df87f1b75d..80910868baaf 100644 --- a/fixtures/vitest-pool-workers-examples/multiple-workers/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/multiple-workers/vitest.config.ts @@ -1,7 +1,8 @@ import crypto from "node:crypto"; -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; import { importPKCS8, SignJWT } from "jose"; import { Request, Response } from "miniflare"; +import { defineConfig } from "vitest/config"; // Generate RSA keypair for signing/verifying JWTs const authKeypair = crypto.generateKeyPairSync("rsa", { @@ -56,64 +57,64 @@ async function handleAuthServiceOutbound(request: Request): Promise { return new Response("Not Found", { status: 404 }); } -export default defineWorkersProject({ - test: { - globalSetup: ["./global-setup.ts"], - poolOptions: { - workers: { - singleWorker: true, - // Configuration for the test runner and "API service" Worker - wrangler: { - configPath: "./api-service/wrangler.jsonc", +export default defineConfig({ + plugins: [ + cloudflareTest({ + // Configuration for the test runner and "API service" Worker + wrangler: { + configPath: "./api-service/wrangler.jsonc", + }, + miniflare: { + bindings: { + TEST_AUTH_PUBLIC_KEY: authKeypair.publicKey, }, - miniflare: { - bindings: { - TEST_AUTH_PUBLIC_KEY: authKeypair.publicKey, - }, - workers: [ - // Configuration for "auxiliary" Worker dependencies. - // Unfortunately, auxiliary Workers cannot load their configuration - // from `wrangler.toml` files, and must be configured with Miniflare - // `WorkerOptions`. - { - name: "auth-service", - modules: true, - scriptPath: "./auth-service/dist/index.js", // Built by `global-setup.ts` - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - bindings: { AUTH_PUBLIC_KEY: authKeypair.publicKey }, - // Mock outbound `fetch()`es from the `auth-service` - outboundService: handleAuthServiceOutbound, - }, - { - name: "database-service", - modules: true, - scriptPath: "./database-service/dist/index.js", // Built by `global-setup.ts` - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - kvNamespaces: ["KV_NAMESPACE"], - }, - { - name: "tail-consumer", - modules: [ - { - path: "index.js", - type: "ESModule", - contents: /* javascript */ ` - export default { - tail(event) { - console.log("tail event received") - } - } - `, - }, - ], - compatibilityDate: "2024-01-01", - }, - ], - }, + workers: [ + // Configuration for "auxiliary" Worker dependencies. + // Unfortunately, auxiliary Workers cannot load their configuration + // from `wrangler.toml` files, and must be configured with Miniflare + // `WorkerOptions`. + { + name: "auth-service", + modules: true, + scriptPath: "./auth-service/dist/index.js", // Built by `global-setup.ts` + compatibilityDate: "2024-01-01", + compatibilityFlags: ["nodejs_compat"], + bindings: { AUTH_PUBLIC_KEY: authKeypair.publicKey }, + // Mock outbound `fetch()`es from the `auth-service` + outboundService: handleAuthServiceOutbound, + }, + { + name: "database-service", + modules: true, + scriptPath: "./database-service/dist/index.js", // Built by `global-setup.ts` + compatibilityDate: "2024-01-01", + compatibilityFlags: ["nodejs_compat"], + kvNamespaces: ["KV_NAMESPACE"], + }, + { + name: "tail-consumer", + modules: [ + { + path: "index.js", + type: "ESModule", + contents: /* javascript */ ` + export default { + tail(event) { + console.log("tail event received") + } + } + `, + }, + ], + compatibilityDate: "2024-01-01", + }, + ], }, - }, + }), + ], + + test: { + globalSetup: ["./global-setup.ts"], }, }); diff --git a/fixtures/vitest-pool-workers-examples/package.json b/fixtures/vitest-pool-workers-examples/package.json index 23e6c5504652..3e540d8bc60e 100644 --- a/fixtures/vitest-pool-workers-examples/package.json +++ b/fixtures/vitest-pool-workers-examples/package.json @@ -6,11 +6,10 @@ "check:type": "node tsc-all.mjs", "list": "vitest list", "test": "vitest", - "test:ci": "run-script-os", - "test:ci:default": "vitest run", - "test:ci:win32": "vitest run --exclude test/sqlite-in-do.test.ts" + "test:ci": "vitest run" }, "devDependencies": { + "@better-auth/stripe": "^1.4.6", "@cloudflare/containers": "^0.0.25", "@cloudflare/eslint-config-shared": "workspace:*", "@cloudflare/vitest-pool-workers": "workspace:*", @@ -18,12 +17,16 @@ "@microlabs/otel-cf-workers": "1.0.0-rc.45", "@types/mime-types": "^3.0.1", "@types/node": "catalog:default", + "@types/nunjucks": "^3.2.6", + "better-auth": "^1.4.6", "discord-api-types": "0.37.98", "ext-dep": "file:./module-resolution/vendor/ext-dep", - "jose": "^5.2.2", + "jose": "^5.9.3", "mime-types": "^2.1.35", "miniflare": "workspace:*", - "run-script-os": "^1.1.6", + "msw": "catalog:default", + "nunjucks": "^3.2.4", + "stripe": "^20.0.0", "toucan-js": "4.0.0", "typescript": "catalog:default", "vite": "catalog:default", diff --git a/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/env.d.ts b/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/env.d.ts index bd9eb32999df..bd6d1d9b67f2 100644 --- a/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/env.d.ts @@ -1,4 +1,6 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env { + KV_NAMESPACE: KVNamespace; + ASSETS: Fetcher; + } } diff --git a/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/integration-self.test.ts b/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/integration-self.test.ts index 5e37f987eae4..cda9a204ccf0 100644 --- a/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/integration-self.test.ts +++ b/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/integration-self.test.ts @@ -22,12 +22,6 @@ describe("functions", () => { expect(response.status).toBe(200); expect(await response.text()).toBe("VALUE"); }); - - it("uses isolated storage for each test", async ({ expect }) => { - // Check write in previous test undone - const response = await SELF.fetch("https://example.com/api/kv/key"); - expect(response.status).toBe(204); - }); }); describe("assets", () => { diff --git a/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/unit.test.ts b/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/unit.test.ts index 69ef0a081891..e70873fb2dee 100644 --- a/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/unit.test.ts +++ b/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/test/unit.test.ts @@ -50,19 +50,6 @@ describe("functions", () => { expect(await response.text()).toBe("value"); }); - it("uses isolated storage for each test", async ({ expect }) => { - // Check write in previous test undone - const request = new IncomingRequest("http://example.com/api/kv/key"); - const ctx = createPagesEventContext({ - request, - data: { user: "test" }, - params: { key: "key" }, - }); - const response = await apiKVKeyFunction.onRequestGet(ctx); - await waitOnExecutionContext(ctx); - expect(response.status).toBe(204); - }); - it("calls middleware", async ({ expect }) => { const request = new IncomingRequest("http://example.com/api/ping"); const ctx = createPagesEventContext({ diff --git a/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/vitest.config.ts b/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/vitest.config.ts index b07237640b5f..99738defeaf4 100644 --- a/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self/vitest.config.ts @@ -1,27 +1,29 @@ import path from "node:path"; import { buildPagesASSETSBinding, - defineWorkersProject, -} from "@cloudflare/vitest-pool-workers/config"; + cloudflareTest, +} from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; const assetsPath = path.join(__dirname, "public"); -export default defineWorkersProject(async () => ({ - test: { - globalSetup: ["./global-setup.ts"], // Only required for integration tests - poolOptions: { - workers: { - main: "./dist-functions/index.js", // Built by `global-setup.ts` - singleWorker: true, - miniflare: { - compatibilityFlags: ["nodejs_compat"], - compatibilityDate: "2024-01-01", - kvNamespaces: ["KV_NAMESPACE"], - serviceBindings: { - ASSETS: await buildPagesASSETSBinding(assetsPath), - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + main: "./dist-functions/index.js", // Built by `global-setup.ts` + miniflare: { + compatibilityFlags: ["nodejs_compat"], + compatibilityDate: "2024-01-01", + kvNamespaces: ["KV_NAMESPACE"], + serviceBindings: { + ASSETS: await buildPagesASSETSBinding(assetsPath), }, }, - }, + }), + ], + + test: { + // Only required for integration tests + globalSetup: ["./global-setup.ts"], }, -})); +}); diff --git a/fixtures/vitest-pool-workers-examples/pages-with-config/vitest.config.ts b/fixtures/vitest-pool-workers-examples/pages-with-config/vitest.config.ts index 80dc7d029de4..ef0e590042db 100644 --- a/fixtures/vitest-pool-workers-examples/pages-with-config/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/pages-with-config/vitest.config.ts @@ -1,11 +1,12 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], + + test: {}, }); diff --git a/fixtures/vitest-pool-workers-examples/pipelines/test/env.d.ts b/fixtures/vitest-pool-workers-examples/pipelines/test/env.d.ts index bd9eb32999df..508751ce4ac9 100644 --- a/fixtures/vitest-pool-workers-examples/pipelines/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/pipelines/test/env.d.ts @@ -1,4 +1,3 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env {} } diff --git a/fixtures/vitest-pool-workers-examples/pipelines/vitest.config.ts b/fixtures/vitest-pool-workers-examples/pipelines/vitest.config.ts index 42e8f9ffd97f..b90cb174ce09 100644 --- a/fixtures/vitest-pool-workers-examples/pipelines/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/pipelines/vitest.config.ts @@ -1,14 +1,14 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - singleWorker: true, - wrangler: { - configPath: "./wrangler.jsonc", - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", }, - }, - }, + }), + ], + + test: {}, }); diff --git a/fixtures/vitest-pool-workers-examples/queues/test/env.d.ts b/fixtures/vitest-pool-workers-examples/queues/test/env.d.ts index bd9eb32999df..8298f33604a6 100644 --- a/fixtures/vitest-pool-workers-examples/queues/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/queues/test/env.d.ts @@ -1,4 +1,10 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} +interface QueueJob { + key: string; + value: string; +} +declare namespace Cloudflare { + interface Env { + QUEUE_PRODUCER: Queue; + QUEUE_RESULTS: KVNamespace; + } } diff --git a/fixtures/vitest-pool-workers-examples/queues/vitest.config.ts b/fixtures/vitest-pool-workers-examples/queues/vitest.config.ts index 7d743d6d2d3b..d74e76331ce9 100644 --- a/fixtures/vitest-pool-workers-examples/queues/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/queues/vitest.config.ts @@ -1,23 +1,22 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - singleWorker: true, - miniflare: { - // Required to use `SELF.queue()`. This is an experimental - // compatibility flag, and cannot be enabled in production. - compatibilityFlags: ["service_binding_extra_handlers"], - // Use a shorter `max_batch_timeout` in tests - queueConsumers: { - queue: { maxBatchTimeout: 0.05 /* 50ms */ }, - }, - }, - wrangler: { - configPath: "./wrangler.jsonc", +export default defineConfig({ + plugins: [ + cloudflareTest({ + miniflare: { + // Required to use `SELF.queue()`. This is an experimental + // compatibility flag, and cannot be enabled in production. + compatibilityFlags: ["service_binding_extra_handlers"], + // Use a shorter `max_batch_timeout` in tests + queueConsumers: { + queue: { maxBatchTimeout: 0.05 /* 50ms */ }, }, }, - }, - }, + wrangler: { + configPath: "./wrangler.jsonc", + }, + }), + ], + test: {}, }); diff --git a/fixtures/vitest-pool-workers-examples/request-mocking/test/declarative.test.ts b/fixtures/vitest-pool-workers-examples/request-mocking/test/declarative.test.ts index e789db615247..b5d022e629b6 100644 --- a/fixtures/vitest-pool-workers-examples/request-mocking/test/declarative.test.ts +++ b/fixtures/vitest-pool-workers-examples/request-mocking/test/declarative.test.ts @@ -1,36 +1,31 @@ -import { fetchMock, SELF } from "cloudflare:test"; -import { afterEach, beforeAll, it } from "vitest"; +import { SELF } from "cloudflare:test"; +import { http, HttpResponse } from "msw"; +import { expect, it } from "vitest"; +import { server } from "./server"; -beforeAll(() => { - // Enable outbound request mocking... - fetchMock.activate(); - // ...and throw errors if an outbound request isn't mocked - fetchMock.disableNetConnect(); -}); - -// Ensure we matched every mock we defined -afterEach(() => fetchMock.assertNoPendingInterceptors()); - -it("mocks GET requests", async ({ expect }) => { - fetchMock - .get("https://cloudflare.com") - .intercept({ path: "/once" }) - .reply(200, "😉"); - fetchMock - .get("https://cloudflare.com") - .intercept({ path: "/persistent" }) - .reply(200, "📌") - .persist(); +it("mocks GET requests", async () => { + server.use( + http.get( + "https://cloudflare.com/once", + () => { + return HttpResponse.text("😉"); + }, + { once: true } + ), + http.get("https://cloudflare.com/persistent", () => { + return HttpResponse.text("📌"); + }) + ); // Host `example.com` will be rewritten to `cloudflare.com` by the Worker let response = await SELF.fetch("https://example.com/once"); expect(response.status).toBe(200); expect(await response.text()).toBe("😉"); - // By default, each mock only matches once, so subsequent `fetch()`es fail... + // Subsequent `fetch()`es fail... response = await SELF.fetch("https://example.com/once"); expect(response.status).toBe(500); - expect(await response.text()).toMatch("MockNotMatchedError"); + expect(await response.text()).toMatch("Cannot bypass"); // ...but calling `.persist()` will match forever, with `.times(n)` matching // `n` times @@ -41,21 +36,26 @@ it("mocks GET requests", async ({ expect }) => { } }); -it("mocks POST requests", async ({ expect }) => { - fetchMock - .get("https://cloudflare.com") - .intercept({ method: "POST", path: "/path", body: "✨" }) - .reply(200, "✅"); +it("mocks POST requests", async () => { + server.use( + http.post("https://cloudflare.com/path", async ({ request }) => { + const text = await request.text(); + if (text !== "✨") { + return HttpResponse.text("Bad request body", { status: 400 }); + } + return HttpResponse.text("✅"); + }) + ); - // Sending a request without the expected body shouldn't match... + // Sending a request without the expected body returns an error response... let response = await SELF.fetch("https://example.com/path", { method: "POST", body: "🙃", }); - expect(response.status).toBe(500); - expect(await response.text()).toMatch("MockNotMatchedError"); + expect(response.status).toBe(400); + expect(await response.text()).toBe("Bad request body"); - // ...but the correct body should + // ...but the correct body should succeed response = await SELF.fetch("https://example.com/path", { method: "POST", body: "✨", diff --git a/fixtures/vitest-pool-workers-examples/request-mocking/test/server.ts b/fixtures/vitest-pool-workers-examples/request-mocking/test/server.ts new file mode 100644 index 000000000000..bd0bda57d85c --- /dev/null +++ b/fixtures/vitest-pool-workers-examples/request-mocking/test/server.ts @@ -0,0 +1,3 @@ +import { setupServer } from "msw/node"; + +export const server = setupServer(); diff --git a/fixtures/vitest-pool-workers-examples/request-mocking/test/setup.ts b/fixtures/vitest-pool-workers-examples/request-mocking/test/setup.ts new file mode 100644 index 000000000000..891a47989654 --- /dev/null +++ b/fixtures/vitest-pool-workers-examples/request-mocking/test/setup.ts @@ -0,0 +1,10 @@ +import { afterAll, afterEach, beforeAll } from "vitest"; +import { server } from "./server"; + +beforeAll(() => + server.listen({ + onUnhandledRequest: "error", + }) +); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); diff --git a/fixtures/vitest-pool-workers-examples/request-mocking/vitest.config.ts b/fixtures/vitest-pool-workers-examples/request-mocking/vitest.config.ts index 42e8f9ffd97f..8dd404945e9b 100644 --- a/fixtures/vitest-pool-workers-examples/request-mocking/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/request-mocking/vitest.config.ts @@ -1,14 +1,16 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - singleWorker: true, - wrangler: { - configPath: "./wrangler.jsonc", - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", }, - }, + }), + ], + + test: { + setupFiles: ["test/setup.ts"], }, }); diff --git a/fixtures/vitest-pool-workers-examples/rpc/src/env.d.ts b/fixtures/vitest-pool-workers-examples/rpc/src/env.d.ts index a2478db08271..7824ee1f7696 100644 --- a/fixtures/vitest-pool-workers-examples/rpc/src/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/rpc/src/env.d.ts @@ -1,6 +1,6 @@ interface Env { KV_NAMESPACE: KVNamespace; - TEST_OBJECT: DurableObjectNamespace; + TEST_OBJECT: DurableObjectNamespace; TEST_NAMED_HANDLER: Service; - TEST_NAMED_ENTRYPOINT: Service; + TEST_NAMED_ENTRYPOINT: Service; } diff --git a/fixtures/vitest-pool-workers-examples/rpc/test/env.d.ts b/fixtures/vitest-pool-workers-examples/rpc/test/env.d.ts index 09cb05965aa1..1c623224effd 100644 --- a/fixtures/vitest-pool-workers-examples/rpc/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/rpc/test/env.d.ts @@ -1,7 +1,11 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} - - // Ensure RPC properties and methods can be accessed with `SELF` - export const SELF: Service; +declare namespace Cloudflare { + interface Env { + KV_NAMESPACE: KVNamespace; + TEST_OBJECT: DurableObjectNamespace; + TEST_NAMED_HANDLER: Service; + TEST_NAMED_ENTRYPOINT: Service; + } + interface GlobalProps { + mainModule: typeof import("../src/index"); + } } diff --git a/fixtures/vitest-pool-workers-examples/rpc/test/integration-self.test.ts b/fixtures/vitest-pool-workers-examples/rpc/test/integration-self.test.ts index 6bc63c11358d..b53a26073aa7 100644 --- a/fixtures/vitest-pool-workers-examples/rpc/test/integration-self.test.ts +++ b/fixtures/vitest-pool-workers-examples/rpc/test/integration-self.test.ts @@ -1,40 +1,33 @@ -import { env, SELF } from "cloudflare:test"; -import { it, vi } from "vitest"; +import { env } from "cloudflare:test"; +import { exports } from "cloudflare:workers"; +import { expect, it, vi } from "vitest"; it("dispatches fetch event", async ({ expect }) => { - const response = await SELF.fetch("https://example.com/"); - expect(await response.json()).toMatchInlineSnapshot(` - { - "ctxWaitUntil": "function", - "envKeys": [ - "KV_NAMESPACE", - "TEST_NAMED_ENTRYPOINT", - "TEST_NAMED_HANDLER", - "TEST_OBJECT", - ], - "method": "GET", - "source": "TestDefaultEntrypoint", - "url": "https://example.com/", - } - `); + const response = await exports.default.fetch("https://example.com/"); + expect(await response.json()).toMatchObject({ + ctxWaitUntil: "function", + method: "GET", + source: "TestDefaultEntrypoint", + url: "https://example.com/", + }); }); it("dispatches scheduled event and accesses property with rpc", async ({ expect, }) => { - await SELF.scheduled({ cron: "* * * * 30" }); - const lastControllerCron = await SELF.lastControllerCron; + await exports.default.scheduled({ cron: "* * * * 30" }); + const lastControllerCron = await exports.default.lastControllerCron; expect(lastControllerCron).toBe("* * * * 30"); }); it("calls multi-argument methods with rpc", async ({ expect }) => { - const result = await SELF.sum(1, 2, 3); + const result = await exports.default.sum(1, 2, 3); expect(result).toBe(6); }); it("calls methods using ctx and env with rpc", async ({ expect }) => { expect(await env.KV_NAMESPACE.get("key")).toBe(null); - await SELF.backgroundWrite("key", "value"); + await exports.default.backgroundWrite("key", "value"); await vi.waitUntil( async () => (await env.KV_NAMESPACE.get("key")) === "value" ); @@ -42,27 +35,27 @@ it("calls methods using ctx and env with rpc", async ({ expect }) => { it("calls async methods with rpc", async ({ expect }) => { await env.KV_NAMESPACE.put("key", "value"); - expect(await SELF.read("key")).toBe("value"); + expect(await exports.default.read("key")).toBe("value"); }); it("calls methods with rpc and pipelining", async ({ expect }) => { - const result = await SELF.createCounter(5).clone().increment(3); + const result = await exports.default.createCounter(5).clone().increment(3); expect(result).toBe(8); }); it("can access methods from superclass", async ({ expect }) => { - const result = await SELF.superMethod(); + const result = await exports.default.superMethod(); expect(result).toBe("🦸"); }); it("cannot access instance properties or methods", async ({ expect }) => { - await expect(async () => await SELF.instanceProperty).rejects + await expect(async () => await exports.default.instanceProperty).rejects .toThrowErrorMatchingInlineSnapshot(` [TypeError: The RPC receiver's prototype does not implement "instanceProperty", but the receiver instance does. Only properties and methods defined on the prototype can be accessed over RPC. Ensure properties are declared like \`get instanceProperty() { ... }\` instead of \`instanceProperty = ...\`, and methods are declared like \`instanceProperty() { ... }\` instead of \`instanceProperty = () => { ... }\`.] `); - await expect(async () => await SELF.instanceMethod()).rejects + await expect(async () => await exports.default.instanceMethod()).rejects .toThrowErrorMatchingInlineSnapshot(` [TypeError: The RPC receiver's prototype does not implement "instanceMethod", but the receiver instance does. Only properties and methods defined on the prototype can be accessed over RPC. @@ -73,13 +66,13 @@ it("cannot access instance properties or methods", async ({ expect }) => { it("cannot access non-existent properties or methods", async ({ expect }) => { await expect( // @ts-expect-error intentionally testing incorrect types - async () => await SELF.nonExistentProperty + async () => await exports.default.nonExistentProperty ).rejects.toThrowErrorMatchingInlineSnapshot( `[TypeError: The RPC receiver does not implement "nonExistentProperty".]` ); await expect( // @ts-expect-error intentionally testing incorrect types - async () => await SELF.nonExistentMethod() + async () => await exports.default.nonExistentMethod() ).rejects.toThrowErrorMatchingInlineSnapshot( `[TypeError: The RPC receiver does not implement "nonExistentMethod".]` ); diff --git a/fixtures/vitest-pool-workers-examples/rpc/test/unit.test.ts b/fixtures/vitest-pool-workers-examples/rpc/test/unit.test.ts index cf50e7d2fc38..02636130ef10 100644 --- a/fixtures/vitest-pool-workers-examples/rpc/test/unit.test.ts +++ b/fixtures/vitest-pool-workers-examples/rpc/test/unit.test.ts @@ -13,20 +13,12 @@ describe("named entrypoints", () => { expect, }) => { const response = await env.TEST_NAMED_HANDLER.fetch("https://example.com"); - expect(await response.json()).toMatchInlineSnapshot(` - { - "ctxWaitUntil": "function", - "envKeys": [ - "KV_NAMESPACE", - "TEST_NAMED_ENTRYPOINT", - "TEST_NAMED_HANDLER", - "TEST_OBJECT", - ], - "method": "GET", - "source": "testNamedHandler", - "url": "https://example.com/", - } - `); + expect(await response.json()).toMatchObject({ + ctxWaitUntil: "function", + method: "GET", + source: "testNamedHandler", + url: "https://example.com/", + }); }); it("dispatches fetch request to named WorkerEntrypoint", async ({ expect, @@ -34,20 +26,12 @@ describe("named entrypoints", () => { const response = await env.TEST_NAMED_ENTRYPOINT.fetch( "https://example.com" ); - expect(await response.json()).toMatchInlineSnapshot(` - { - "ctxWaitUntil": "function", - "envKeys": [ - "KV_NAMESPACE", - "TEST_NAMED_ENTRYPOINT", - "TEST_NAMED_HANDLER", - "TEST_OBJECT", - ], - "method": "GET", - "source": "TestNamedEntrypoint", - "url": "https://example.com/", - } - `); + expect(await response.json()).toMatchObject({ + ctxWaitUntil: "function", + method: "GET", + source: "TestNamedEntrypoint", + url: "https://example.com/", + }); }); it("calls method with rpc", async ({ expect }) => { const result = await env.TEST_NAMED_ENTRYPOINT.ping(); @@ -78,20 +62,12 @@ describe("Durable Object", () => { const id = env.TEST_OBJECT.newUniqueId(); const stub = env.TEST_OBJECT.get(id); const response = await stub.fetch("https://example.com"); - expect(await response.json()).toMatchInlineSnapshot(` - { - "ctxWaitUntil": "function", - "envKeys": [ - "KV_NAMESPACE", - "TEST_NAMED_ENTRYPOINT", - "TEST_NAMED_HANDLER", - "TEST_OBJECT", - ], - "method": "GET", - "source": "TestObject", - "url": "https://example.com/", - } - `); + expect(await response.json()).toMatchObject({ + ctxWaitUntil: "function", + method: "GET", + source: "TestObject", + url: "https://example.com/", + }); }); it("increments count and allows direct/rpc access to instance/storage", async ({ expect, diff --git a/fixtures/vitest-pool-workers-examples/rpc/vitest.config.ts b/fixtures/vitest-pool-workers-examples/rpc/vitest.config.ts index 8a6f5d2dc566..1208ae9ae511 100644 --- a/fixtures/vitest-pool-workers-examples/rpc/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/rpc/vitest.config.ts @@ -1,23 +1,17 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - esbuild: { - // Required for `using` support - target: "ES2022", - }, - test: { - poolOptions: { - workers: { - singleWorker: true, - miniflare: { - // Required to use `SELF.scheduled()`. This is an experimental - // compatibility flag, and cannot be enabled in production. - compatibilityFlags: ["service_binding_extra_handlers"], - }, - wrangler: { - configPath: "./wrangler.jsonc", - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + miniflare: { + // Required to use `SELF.scheduled()`. This is an experimental + // compatibility flag, and cannot be enabled in production. + compatibilityFlags: ["service_binding_extra_handlers", "nodejs_compat"], }, - }, - }, + wrangler: { + configPath: "./wrangler.jsonc", + }, + }), + ], }); diff --git a/fixtures/vitest-pool-workers-examples/tsconfig.workerd-test.json b/fixtures/vitest-pool-workers-examples/tsconfig.workerd-test.json index 61e1dbcf9d75..3aa2158e58aa 100644 --- a/fixtures/vitest-pool-workers-examples/tsconfig.workerd-test.json +++ b/fixtures/vitest-pool-workers-examples/tsconfig.workerd-test.json @@ -3,7 +3,8 @@ "compilerOptions": { "types": [ "@cloudflare/workers-types/experimental", - "@cloudflare/vitest-pool-workers", // For `cloudflare:test` types + "@cloudflare/vitest-pool-workers/types", + "@types/node", // For tests using Node.js compat APIs (process, Buffer, node:*) "vite/client" // For `?raw`, `?url`, etc. import types ] } diff --git a/fixtures/vitest-pool-workers-examples/vitest.config.ts b/fixtures/vitest-pool-workers-examples/vitest.config.ts index 65496f8fd3dc..164ab4e8df4a 100644 --- a/fixtures/vitest-pool-workers-examples/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/vitest.config.ts @@ -1,46 +1,18 @@ -// This file is named `vitest.workers.config.ts` so it doesn't get included -// in the monorepo's `vitest.workspace.ts`. +// Root vitest config for the vitest-pool-workers-examples fixture. import { defineConfig } from "vitest/config"; -class FilteredPushArray extends Array { - constructor(private readonly predicate: (item: T) => boolean) { - super(); - } - - push(...items: T[]) { - return super.push(...items.filter(this.predicate)); - } -} - export default defineConfig({ test: { teardownTimeout: 1_000, - projects: ["*/vitest.*config.*ts"], + projects: [ + "*/vitest.*config.*ts", + // workerd's Windows SQLite VFS uses kj::Path::toString() (Unix-style + // paths) with the win32 VFS, causing SQLITE_CANTOPEN for disk-backed + // SQLite DOs. Exclude until workerd ships the fix (cloudflare/workerd#6110). + ...(process.platform === "win32" + ? ["!durable-objects/vitest.*config.*ts"] + : []), + ], globalSetup: ["./vitest.global.ts"], - // Configure the `vite-node` server used by Vitest code to import configs, - // custom pools and tests. By default, Vitest effectively applies Vite - // transforms to all files outside `node_modules`. This means by default, - // our custom pool code is transformed by Vite during development, but not - // when published, leading to possible behaviour mismatches. To fix this, - // we ensure file paths containing `packages/vitest-pool-workers/dist` are - // always "externalised", meaning they're imported directly by Node. - server: { - deps: { - // Vitest automatically adds `/^(?!.*node_modules).*\.mjs$/` as an - // `inline` RegExp: https://github.com/vitest-dev/vitest/blob/v2.1.1/packages/vitest/src/constants.ts#L9 - // We'd like `packages/vitest-pool-workers/dist/pool/index.mjs` to be - // externalised though. Unfortunately, `inline`s are checked before - // `external`s, so there's no nice way we can override this. Instead, - // we prevent the extra `inline` being added in the first place. - inline: new FilteredPushArray((item) => { - const str = item.toString(); - return str !== "/^(?!.*node_modules).*\\.mjs$/"; - }), - external: [ - /packages\/vitest-pool-workers\/dist/, - /packages\/wrangler\//, - ], - }, - }, }, }); diff --git a/fixtures/vitest-pool-workers-examples/web-assembly/test/env.d.ts b/fixtures/vitest-pool-workers-examples/web-assembly/test/env.d.ts index bd9eb32999df..508751ce4ac9 100644 --- a/fixtures/vitest-pool-workers-examples/web-assembly/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/web-assembly/test/env.d.ts @@ -1,4 +1,3 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env {} } diff --git a/fixtures/vitest-pool-workers-examples/web-assembly/vitest.config.ts b/fixtures/vitest-pool-workers-examples/web-assembly/vitest.config.ts index f3ee41341f45..38be25e8b0d6 100644 --- a/fixtures/vitest-pool-workers-examples/web-assembly/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/web-assembly/vitest.config.ts @@ -1,18 +1,18 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - singleWorker: true, - // Specifying a `wrangler.configPath` will enable Wrangler's default - // module rules including support for `.wasm` files. Refer to - // https://developers.cloudflare.com/workers/wrangler/bundling/#files-which-will-not-be-bundled - // for more information. - wrangler: { - configPath: "./wrangler.jsonc", - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + // Specifying a `wrangler.configPath` will enable Wrangler's default + // module rules including support for `.wasm` files. Refer to + // https://developers.cloudflare.com/workers/wrangler/bundling/#files-which-will-not-be-bundled + // for more information. + wrangler: { + configPath: "./wrangler.jsonc", }, - }, - }, + }), + ], + + test: {}, }); diff --git a/fixtures/vitest-pool-workers-examples/workers-assets-only/README.md b/fixtures/vitest-pool-workers-examples/workers-assets-only/README.md deleted file mode 100644 index 979eb08bbefc..000000000000 --- a/fixtures/vitest-pool-workers-examples/workers-assets-only/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# ✅ workers-with-assets-only - -This example contains assets without a Worker script. - -An asset-only project can only be tested integration-style using the SELF binding, as there is no Worker to import and unit test. diff --git a/fixtures/vitest-pool-workers-examples/workers-assets-only/public/index.html b/fixtures/vitest-pool-workers-examples/workers-assets-only/public/index.html deleted file mode 100644 index 29df18baca97..000000000000 --- a/fixtures/vitest-pool-workers-examples/workers-assets-only/public/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - Hello, World! - - -

Asset index.html

- - diff --git a/fixtures/vitest-pool-workers-examples/workers-assets-only/test/assets-only.test.ts b/fixtures/vitest-pool-workers-examples/workers-assets-only/test/assets-only.test.ts deleted file mode 100644 index 6449d5ffe4e2..000000000000 --- a/fixtures/vitest-pool-workers-examples/workers-assets-only/test/assets-only.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { env, SELF } from "cloudflare:test"; -import { describe, it } from "vitest"; - -// There is no Worker so we can't import one and unit test -it("can test asset serving (integration style)", async ({ expect }) => { - let response = await SELF.fetch("http://example.com/index.html"); - expect(await response.text()).toContain("Asset index.html"); - - // no such asset - response = await SELF.fetch("http://example.com/message"); - expect(await response.text()).toBeFalsy(); - expect(response.status).toBe(404); -}); diff --git a/fixtures/vitest-pool-workers-examples/workers-assets-only/test/tsconfig.json b/fixtures/vitest-pool-workers-examples/workers-assets-only/test/tsconfig.json deleted file mode 100644 index 40d245572f5f..000000000000 --- a/fixtures/vitest-pool-workers-examples/workers-assets-only/test/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../../tsconfig.workerd-test.json", - "include": ["./**/*.ts"] -} diff --git a/fixtures/vitest-pool-workers-examples/workers-assets-only/tsconfig.json b/fixtures/vitest-pool-workers-examples/workers-assets-only/tsconfig.json deleted file mode 100644 index 90e58bf03ef0..000000000000 --- a/fixtures/vitest-pool-workers-examples/workers-assets-only/tsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "../tsconfig.node.json", - "include": ["./*.ts"] -} diff --git a/fixtures/vitest-pool-workers-examples/workers-assets-only/vitest.config.ts b/fixtures/vitest-pool-workers-examples/workers-assets-only/vitest.config.ts deleted file mode 100644 index 80dc7d029de4..000000000000 --- a/fixtures/vitest-pool-workers-examples/workers-assets-only/vitest.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; - -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - }, -}); diff --git a/fixtures/vitest-pool-workers-examples/workers-assets-only/wrangler.jsonc b/fixtures/vitest-pool-workers-examples/workers-assets-only/wrangler.jsonc deleted file mode 100644 index 260ed66ee91d..000000000000 --- a/fixtures/vitest-pool-workers-examples/workers-assets-only/wrangler.jsonc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "workers-static-assets-only", - // don't provide compatibility_date so that vitest will infer the latest one - "assets": { - "directory": "./public", - "html_handling": "none", - }, -} diff --git a/fixtures/vitest-pool-workers-examples/workers-assets-run-worker-first/test/env.d.ts b/fixtures/vitest-pool-workers-examples/workers-assets-run-worker-first/test/env.d.ts index 67b3610dbc7d..508751ce4ac9 100644 --- a/fixtures/vitest-pool-workers-examples/workers-assets-run-worker-first/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/workers-assets-run-worker-first/test/env.d.ts @@ -1,3 +1,3 @@ -declare module "cloudflare:test" { - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env {} } diff --git a/fixtures/vitest-pool-workers-examples/workers-assets-run-worker-first/vitest.config.ts b/fixtures/vitest-pool-workers-examples/workers-assets-run-worker-first/vitest.config.ts index 80dc7d029de4..9b79788eddd6 100644 --- a/fixtures/vitest-pool-workers-examples/workers-assets-run-worker-first/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/workers-assets-run-worker-first/vitest.config.ts @@ -1,11 +1,10 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], }); diff --git a/fixtures/vitest-pool-workers-examples/workers-assets/test/assets.test.ts b/fixtures/vitest-pool-workers-examples/workers-assets/test/assets.test.ts index 73c617cb309e..b037031dd5fe 100644 --- a/fixtures/vitest-pool-workers-examples/workers-assets/test/assets.test.ts +++ b/fixtures/vitest-pool-workers-examples/workers-assets/test/assets.test.ts @@ -49,10 +49,7 @@ describe("Hello World user worker", () => { const response = await SELF.fetch("http://example.com/message"); expect(await response.text()).toMatchInlineSnapshot(`"Hello, World!"`); }); - it("does get assets directly is using SELF", async ({ expect }) => { - const response = await SELF.fetch("http://example.com/"); - expect(await response.text()).toContain("Asset index.html"); - }); + it("can also get assets via binding", async ({ expect }) => { const response = await SELF.fetch("http://example.com/binding"); expect(await response.text()).toContain("binding.html"); diff --git a/fixtures/vitest-pool-workers-examples/workers-assets/test/env.d.ts b/fixtures/vitest-pool-workers-examples/workers-assets/test/env.d.ts index 67b3610dbc7d..c3290b581d48 100644 --- a/fixtures/vitest-pool-workers-examples/workers-assets/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/workers-assets/test/env.d.ts @@ -1,3 +1,5 @@ -declare module "cloudflare:test" { - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env { + ASSETS: Fetcher; + } } diff --git a/fixtures/vitest-pool-workers-examples/workers-assets/vitest.config.ts b/fixtures/vitest-pool-workers-examples/workers-assets/vitest.config.ts index 80dc7d029de4..9b79788eddd6 100644 --- a/fixtures/vitest-pool-workers-examples/workers-assets/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/workers-assets/vitest.config.ts @@ -1,11 +1,10 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -export default defineWorkersProject({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], }); diff --git a/fixtures/vitest-pool-workers-examples/workflows/test/env.d.ts b/fixtures/vitest-pool-workers-examples/workflows/test/env.d.ts index bd9eb32999df..508751ce4ac9 100644 --- a/fixtures/vitest-pool-workers-examples/workflows/test/env.d.ts +++ b/fixtures/vitest-pool-workers-examples/workflows/test/env.d.ts @@ -1,4 +1,3 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env {} } diff --git a/fixtures/vitest-pool-workers-examples/workflows/vitest.config.ts b/fixtures/vitest-pool-workers-examples/workflows/vitest.config.ts index 23119b5bc991..6b7167c0eb6d 100644 --- a/fixtures/vitest-pool-workers-examples/workflows/vitest.config.ts +++ b/fixtures/vitest-pool-workers-examples/workflows/vitest.config.ts @@ -1,18 +1,12 @@ -import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineProject } from "vitest/config"; -export default defineWorkersProject({ - esbuild: { - // Required for `using` support - target: "ES2022", - }, - test: { - poolOptions: { - workers: { - singleWorker: true, - wrangler: { - configPath: "./wrangler.jsonc", - }, +export default defineProject({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", }, - }, - }, + }), + ], }); diff --git a/fixtures/vitest-pool-workers-remote-bindings/env.d.ts b/fixtures/vitest-pool-workers-remote-bindings/env.d.ts index 5ca5d483e6ef..73f6dc57e487 100644 --- a/fixtures/vitest-pool-workers-remote-bindings/env.d.ts +++ b/fixtures/vitest-pool-workers-remote-bindings/env.d.ts @@ -1,7 +1,5 @@ -import { Fetcher } from "@cloudflare/workers-types/experimental"; - -declare module "cloudflare:test" { - interface ProvidedEnv { - MY_WORKER: Fetcher; +declare namespace Cloudflare { + interface Env { + MY_WORKER: import("@cloudflare/workers-types/experimental").Fetcher; } } diff --git a/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.staging.ts b/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.staging.ts index 597e89590521..b2be2630b95e 100644 --- a/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.staging.ts +++ b/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.staging.ts @@ -1,15 +1,5 @@ import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; -class FilteredPushArray extends Array { - constructor(private readonly predicate: (item: T) => boolean) { - super(); - } - - push(...items: T[]) { - return super.push(...items.filter(this.predicate)); - } -} - export default defineWorkersConfig({ test: { include: ["test-staging/**/*.spec.ts"], @@ -18,26 +8,8 @@ export default defineWorkersConfig({ wrangler: { configPath: "./wrangler.json", environment: "staging" }, }, }, - - // Configure the `vite-node` server used by Vitest code to import configs, - // custom pools and tests. By default, Vitest effectively applies Vite - // transforms to all files outside `node_modules`. This means by default, - // our custom pool code is transformed by Vite during development, but not - // when published, leading to possible behaviour mismatches. To fix this, - // we ensure file paths containing `packages/vitest-pool-workers/dist` are - // always "externalised", meaning they're imported directly by Node. server: { deps: { - // Vitest automatically adds `/^(?!.*node_modules).*\.mjs$/` as an - // `inline` RegExp: https://github.com/vitest-dev/vitest/blob/v2.1.1/packages/vitest/src/constants.ts#L9 - // We'd like `packages/vitest-pool-workers/dist/pool/index.mjs` to be - // externalised though. Unfortunately, `inline`s are checked before - // `external`s, so there's no nice way we can override this. Instead, - // we prevent the extra `inline` being added in the first place. - inline: new FilteredPushArray((item: any) => { - const str = item.toString(); - return str !== "/^(?!.*node_modules).*\\.mjs$/"; - }), external: [ /packages\/vitest-pool-workers\/dist/, /packages\/wrangler\//, diff --git a/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.ts b/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.ts index 0bd74ad25c15..c84e97c883ae 100644 --- a/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.ts +++ b/fixtures/vitest-pool-workers-remote-bindings/vitest.workers.config.ts @@ -1,15 +1,5 @@ import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; -class FilteredPushArray extends Array { - constructor(private readonly predicate: (item: T) => boolean) { - super(); - } - - push(...items: T[]) { - return super.push(...items.filter(this.predicate)); - } -} - export default defineWorkersConfig({ test: { include: ["test/**/*.spec.ts"], @@ -18,26 +8,8 @@ export default defineWorkersConfig({ wrangler: { configPath: "./wrangler.json" }, }, }, - - // Configure the `vite-node` server used by Vitest code to import configs, - // custom pools and tests. By default, Vitest effectively applies Vite - // transforms to all files outside `node_modules`. This means by default, - // our custom pool code is transformed by Vite during development, but not - // when published, leading to possible behaviour mismatches. To fix this, - // we ensure file paths containing `packages/vitest-pool-workers/dist` are - // always "externalised", meaning they're imported directly by Node. server: { deps: { - // Vitest automatically adds `/^(?!.*node_modules).*\.mjs$/` as an - // `inline` RegExp: https://github.com/vitest-dev/vitest/blob/v2.1.1/packages/vitest/src/constants.ts#L9 - // We'd like `packages/vitest-pool-workers/dist/pool/index.mjs` to be - // externalised though. Unfortunately, `inline`s are checked before - // `external`s, so there's no nice way we can override this. Instead, - // we prevent the extra `inline` being added in the first place. - inline: new FilteredPushArray((item: any) => { - const str = item.toString(); - return str !== "/^(?!.*node_modules).*\\.mjs$/"; - }), external: [ /packages\/vitest-pool-workers\/dist/, /packages\/wrangler\//, diff --git a/fixtures/worker-with-unsafe-external-plugin/package.json b/fixtures/worker-with-unsafe-external-plugin/package.json index 77142e95a964..bfad63e86307 100644 --- a/fixtures/worker-with-unsafe-external-plugin/package.json +++ b/fixtures/worker-with-unsafe-external-plugin/package.json @@ -1,6 +1,7 @@ { "name": "@fixture/worker-with-unsafe-external-plugin", "private": true, + "type": "module", "scripts": { "dev": "wrangler dev", "test:ci": "vitest run", diff --git a/fixtures/worker-with-unsafe-external-plugin/tests/env.d.ts b/fixtures/worker-with-unsafe-external-plugin/tests/env.d.ts index 67b3610dbc7d..508751ce4ac9 100644 --- a/fixtures/worker-with-unsafe-external-plugin/tests/env.d.ts +++ b/fixtures/worker-with-unsafe-external-plugin/tests/env.d.ts @@ -1,3 +1,3 @@ -declare module "cloudflare:test" { - interface ProvidedEnv extends Env {} +declare namespace Cloudflare { + interface Env {} } diff --git a/fixtures/worker-with-unsafe-external-plugin/vitest.config.ts b/fixtures/worker-with-unsafe-external-plugin/vitest.config.ts index 1a51585a9f94..9b79788eddd6 100644 --- a/fixtures/worker-with-unsafe-external-plugin/vitest.config.ts +++ b/fixtures/worker-with-unsafe-external-plugin/vitest.config.ts @@ -1,46 +1,10 @@ -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -class FilteredPushArray extends Array { - constructor(private readonly predicate: (item: T) => boolean) { - super(); - } - - push(...items: T[]) { - return super.push(...items.filter(this.predicate)); - } -} - -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - // Configure the `vite-node` server used by Vitest code to import configs, - // custom pools and tests. By default, Vitest effectively applies Vite - // transforms to all files outside `node_modules`. This means by default, - // our custom pool code is transformed by Vite during development, but not - // when published, leading to possible behaviour mismatches. To fix this, - // we ensure file paths containing `packages/vitest-pool-workers/dist` are - // always "externalised", meaning they're imported directly by Node. - server: { - deps: { - // Vitest automatically adds `/^(?!.*node_modules).*\.mjs$/` as an - // `inline` RegExp: https://github.com/vitest-dev/vitest/blob/v2.1.1/packages/vitest/src/constants.ts#L9 - // We'd like `packages/vitest-pool-workers/dist/pool/index.mjs` to be - // externalised though. Unfortunately, `inline`s are checked before - // `external`s, so there's no nice way we can override this. Instead, - // we prevent the extra `inline` being added in the first place. - inline: new FilteredPushArray((item: any) => { - const str = item.toString(); - return str !== "/^(?!.*node_modules).*\\.mjs$/"; - }), - external: [ - /packages\/vitest-pool-workers\/dist/, - /packages\/wrangler\//, - ], - }, - }, - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), + ], }); diff --git a/fixtures/workers-shared-asset-config/env.d.ts b/fixtures/workers-shared-asset-config/env.d.ts index ec168ef521c9..c54cdfe3fcf2 100644 --- a/fixtures/workers-shared-asset-config/env.d.ts +++ b/fixtures/workers-shared-asset-config/env.d.ts @@ -1,6 +1,5 @@ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv { +declare namespace Cloudflare { + interface Env { CONFIG: Record; ASSETS_MANIFEST: ArrayBuffer; ASSETS_KV_NAMESPACE: KVNamespace; diff --git a/fixtures/workers-shared-asset-config/package.json b/fixtures/workers-shared-asset-config/package.json index 3fbea3b15046..ef6c95db4f2b 100644 --- a/fixtures/workers-shared-asset-config/package.json +++ b/fixtures/workers-shared-asset-config/package.json @@ -1,6 +1,7 @@ { "name": "@fixture/workers-shared-assets-config", "private": true, + "type": "module", "scripts": { "dev": "wrangler dev", "test:ci": "run-script-os", diff --git a/fixtures/workers-shared-asset-config/tsconfig.json b/fixtures/workers-shared-asset-config/tsconfig.json index 168323a45c08..b562c4033bf9 100644 --- a/fixtures/workers-shared-asset-config/tsconfig.json +++ b/fixtures/workers-shared-asset-config/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "types": [ "@cloudflare/workers-types/experimental", - "@cloudflare/vitest-pool-workers" + "@cloudflare/vitest-pool-workers/types" ], "moduleResolution": "bundler" }, diff --git a/fixtures/workers-shared-asset-config/vitest.config.ts b/fixtures/workers-shared-asset-config/vitest.config.ts index 4e8996d0c17b..0e3fc0185f92 100644 --- a/fixtures/workers-shared-asset-config/vitest.config.ts +++ b/fixtures/workers-shared-asset-config/vitest.config.ts @@ -1,52 +1,17 @@ -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; -class FilteredPushArray extends Array { - constructor(private readonly predicate: (item: T) => boolean) { - super(); - } - - push(...items: T[]) { - return super.push(...items.filter(this.predicate)); - } -} - -export default defineWorkersConfig({ +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "../../packages/workers-shared/asset-worker/wrangler.jsonc", + }, + }), + ], test: { chaiConfig: { truncateThreshold: 80, }, - poolOptions: { - workers: { - wrangler: { - configPath: - "../../packages/workers-shared/asset-worker/wrangler.jsonc", - }, - }, - }, - // Configure the `vite-node` server used by Vitest code to import configs, - // custom pools and tests. By default, Vitest effectively applies Vite - // transforms to all files outside `node_modules`. This means by default, - // our custom pool code is transformed by Vite during development, but not - // when published, leading to possible behaviour mismatches. To fix this, - // we ensure file paths containing `packages/vitest-pool-workers/dist` are - // always "externalised", meaning they're imported directly by Node. - server: { - deps: { - // Vitest automatically adds `/^(?!.*node_modules).*\.mjs$/` as an - // `inline` RegExp: https://github.com/vitest-dev/vitest/blob/v3.0.5/packages/vitest/src/constants.ts#L9 - // We'd like `packages/vitest-pool-workers/dist/pool/index.mjs` to be - // externalised though. Unfortunately, `inline`s are checked before - // `external`s, so there's no nice way we can override this. Instead, - // we prevent the extra `inline` being added in the first place. - inline: new FilteredPushArray((item) => { - const str = `${item}`; - return str !== "/^(?!.*node_modules).*\\.mjs$/"; - }), - external: [ - /packages\/vitest-pool-workers\/dist/, - /packages\/wrangler\//, - ], - }, - }, }, }); diff --git a/fixtures/workers-with-assets-static-routing/package.json b/fixtures/workers-with-assets-static-routing/package.json index 23153289c862..e624a54c8ef8 100644 --- a/fixtures/workers-with-assets-static-routing/package.json +++ b/fixtures/workers-with-assets-static-routing/package.json @@ -14,6 +14,7 @@ "@cloudflare/eslint-config-shared": "workspace:*", "@cloudflare/workers-tsconfig": "workspace:*", "@cloudflare/workers-types": "catalog:default", + "@types/node": "catalog:default", "playwright-chromium": "catalog:default", "typescript": "catalog:default", "undici": "catalog:default", diff --git a/fixtures/workers-with-assets-static-routing/tsconfig.json b/fixtures/workers-with-assets-static-routing/tsconfig.json index ea70fcd94df4..6ec35310ea18 100644 --- a/fixtures/workers-with-assets-static-routing/tsconfig.json +++ b/fixtures/workers-with-assets-static-routing/tsconfig.json @@ -5,10 +5,9 @@ "lib": ["ES2020"], "types": ["@cloudflare/workers-types"], "moduleResolution": "node", - "esModuleInterop": true, "noEmit": true, "skipLibCheck": true }, "include": ["**/*.ts"], - "exclude": ["tests"] + "exclude": ["test"] } diff --git a/package.json b/package.json index 1c93ca4bed7c..edfa3a820761 100644 --- a/package.json +++ b/package.json @@ -74,8 +74,9 @@ "@types/react-transition-group>@types/react": "^18", "@cloudflare/elements>@types/react": "^18", "@types/node": "$@types/node", - "vitest>vite": "^5.0.0", - "@types/node>undici-types": "catalog:default" + "@types/node>undici-types": "catalog:default", + "vitest@4>vite": "catalog:vite-plugin", + "vitest@3>vite": "^5.0.0" }, "patchedDependencies": { "@cloudflare/component-listbox@1.10.6": "patches/@cloudflare__component-listbox@1.10.6.patch", diff --git a/packages/create-cloudflare/package.json b/packages/create-cloudflare/package.json index 0d2e33f47c37..4d927c0a8ee0 100644 --- a/packages/create-cloudflare/package.json +++ b/packages/create-cloudflare/package.json @@ -83,9 +83,9 @@ "tree-kill": "catalog:default", "typescript": "catalog:default", "undici": "catalog:default", - "vite": "catalog:default", + "vite": "catalog:vitest-3", "vite-tsconfig-paths": "^4.0.8", - "vitest": "catalog:default", + "vitest": "catalog:vitest-3", "which-pm-runs": "^1.1.0", "wrangler": "workspace:*", "wrap-ansi": "^9.0.0", diff --git a/packages/edge-preview-authenticated-proxy/package.json b/packages/edge-preview-authenticated-proxy/package.json index 2b96493011f1..a276ec4f5c73 100644 --- a/packages/edge-preview-authenticated-proxy/package.json +++ b/packages/edge-preview-authenticated-proxy/package.json @@ -12,7 +12,7 @@ }, "devDependencies": { "@cloudflare/eslint-config-shared": "workspace:*", - "@cloudflare/vitest-pool-workers": "catalog:default", + "@cloudflare/vitest-pool-workers": "workspace:*", "@cloudflare/workers-types": "catalog:default", "@types/cookie": "^0.6.0", "cookie": "^0.7.2", diff --git a/packages/edge-preview-authenticated-proxy/src/index.ts b/packages/edge-preview-authenticated-proxy/src/index.ts index a3e6bc7baa73..49935ac06df1 100644 --- a/packages/edge-preview-authenticated-proxy/src/index.ts +++ b/packages/edge-preview-authenticated-proxy/src/index.ts @@ -129,15 +129,15 @@ async function handleRequest( const url = new URL(request.url); if (isTokenExchangeRequest(request, url, env)) { - return handleTokenExchange(url); + return await handleTokenExchange(url); } if (isPreviewUpdateRequest(request, url, env)) { - return updatePreviewToken(url, env, ctx); + return await updatePreviewToken(url, env, ctx); } if (isRawHttpRequest(url, env)) { - return handleRawHttp(request, url); + return await handleRawHttp(request, url); } /** diff --git a/packages/edge-preview-authenticated-proxy/tests/tsconfig.json b/packages/edge-preview-authenticated-proxy/tests/tsconfig.json index 1ba476ce3ea9..588b1e21c815 100644 --- a/packages/edge-preview-authenticated-proxy/tests/tsconfig.json +++ b/packages/edge-preview-authenticated-proxy/tests/tsconfig.json @@ -4,7 +4,11 @@ "lib": ["esnext"], "module": "esnext", "moduleResolution": "bundler", - "types": ["@cloudflare/vitest-pool-workers"], + "types": [ + "@types/node", + "@cloudflare/workers-types", + "@cloudflare/vitest-pool-workers/types" + ], "allowJs": true, "checkJs": false, "noEmit": true, diff --git a/packages/edge-preview-authenticated-proxy/vitest.config.mts b/packages/edge-preview-authenticated-proxy/vitest.config.mts index d5e9dbec6b78..22c6c7801b78 100644 --- a/packages/edge-preview-authenticated-proxy/vitest.config.mts +++ b/packages/edge-preview-authenticated-proxy/vitest.config.mts @@ -1,21 +1,19 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - singleWorker: true, - isolatedStorage: false, - wrangler: { - configPath: "./wrangler.jsonc", - }, +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { + configPath: "./wrangler.jsonc", }, - }, - }, + }), + ], + test: {}, resolve: { // promjs has broken package.json (main points to lib/index.js but files are at root) alias: { diff --git a/packages/kv-asset-handler/package.json b/packages/kv-asset-handler/package.json index 871d0cc80ade..99e83a440984 100644 --- a/packages/kv-asset-handler/package.json +++ b/packages/kv-asset-handler/package.json @@ -38,14 +38,14 @@ }, "devDependencies": { "@cloudflare/eslint-config-shared": "workspace:*", - "@cloudflare/vitest-pool-workers": "catalog:default", + "@cloudflare/vitest-pool-workers": "catalog:vitest-3", "@cloudflare/workers-types": "catalog:default", "@types/mime": "^3.0.4", "@types/node": "catalog:default", "eslint": "catalog:default", "mime": "^3.0.0", "tsup": "8.3.0", - "vitest": "~2.1.0" + "vitest": "catalog:vitest-3" }, "engines": { "node": ">=18.0.0" diff --git a/packages/kv-asset-handler/vitest.config.mts b/packages/kv-asset-handler/vitest.config.mts index f60be1b809a2..f89a7b51913b 100644 --- a/packages/kv-asset-handler/vitest.config.mts +++ b/packages/kv-asset-handler/vitest.config.mts @@ -4,8 +4,6 @@ export default defineWorkersConfig({ test: { poolOptions: { workers: { - singleWorker: false, - isolatedStorage: true, miniflare: { compatibilityDate: "2025-02-04", }, diff --git a/packages/miniflare/src/plugins/shared/index.ts b/packages/miniflare/src/plugins/shared/index.ts index a2aca7b3d926..4757793e31fb 100644 --- a/packages/miniflare/src/plugins/shared/index.ts +++ b/packages/miniflare/src/plugins/shared/index.ts @@ -231,35 +231,45 @@ export function getPersistPath( // keep Miniflare 2's behaviour, so persist to a temporary path which we // destroy on `dispose()`. const memoryishPath = path.join(tmpPath, pluginName); - if (persist === false) { - return memoryishPath; - } - // If `persist` is undefined, use either the default path or fallback to the tmpPath - if (persist === undefined) { - return defaultPersistRoot === undefined - ? memoryishPath - : path.join(defaultPersistRoot, pluginName); - } - - // Try parse `persist` as a URL - const url = maybeParseURL(persist); - if (url !== undefined) { - if (url.protocol === "memory:") { - return memoryishPath; - } else if (url.protocol === "file:") { - return fileURLToPath(url); + let result: string; + if (persist === false) { + result = memoryishPath; + } else if (persist === undefined) { + // If `persist` is undefined, use either the default path or fallback to the tmpPath + result = + defaultPersistRoot === undefined + ? memoryishPath + : path.join(defaultPersistRoot, pluginName); + } else { + // Try parse `persist` as a URL + const url = maybeParseURL(persist); + if (url !== undefined) { + if (url.protocol === "memory:") { + result = memoryishPath; + } else if (url.protocol === "file:") { + result = fileURLToPath(url); + } else { + throw new MiniflareCoreError( + "ERR_PERSIST_UNSUPPORTED", + `Unsupported "${url.protocol}" persistence protocol for storage: ${url.href}` + ); + } + } else { + // Otherwise, fallback to file storage + result = + persist === true + ? path.join(defaultPersistRoot ?? DEFAULT_PERSIST_ROOT, pluginName) + : persist; } - throw new MiniflareCoreError( - "ERR_PERSIST_UNSUPPORTED", - `Unsupported "${url.protocol}" persistence protocol for storage: ${url.href}` - ); } - // Otherwise, fallback to file storage - return persist === true - ? path.join(defaultPersistRoot ?? DEFAULT_PERSIST_ROOT, pluginName) - : persist; + // Normalize to forward slashes for workerd's disk service compatibility on + // Windows. workerd is a Unix-oriented C++ program and its disk service does + // not handle Windows backslash paths correctly, resulting in SQLITE_CANTOPEN + // errors. Forward slashes work for both Node.js fs APIs and workerd on all + // platforms. + return result.replaceAll("\\", "/"); } // https://github.com/cloudflare/workerd/blob/81d97010e44f848bb95d0083e2677bca8d1658b7/src/workerd/server/workerd-api.c%2B%2B#L436 diff --git a/packages/pages-shared/package.json b/packages/pages-shared/package.json index 531edccb2479..f755152d5485 100644 --- a/packages/pages-shared/package.json +++ b/packages/pages-shared/package.json @@ -26,7 +26,7 @@ }, "devDependencies": { "@cloudflare/eslint-config-shared": "workspace:*", - "@cloudflare/vitest-pool-workers": "catalog:default", + "@cloudflare/vitest-pool-workers": "catalog:vitest-3", "@cloudflare/workers-shared": "workspace:*", "@cloudflare/workers-tsconfig": "workspace:*", "@cloudflare/workers-types": "catalog:default", @@ -36,7 +36,7 @@ "glob": "^10.4.5", "html-rewriter-wasm": "^0.4.1", "typescript": "catalog:default", - "vitest": "catalog:default" + "vitest": "catalog:vitest-3" }, "volta": { "extends": "../../package.json" diff --git a/packages/vite-plugin-cloudflare/package.json b/packages/vite-plugin-cloudflare/package.json index aa2527114a23..5fc4f254c444 100644 --- a/packages/vite-plugin-cloudflare/package.json +++ b/packages/vite-plugin-cloudflare/package.json @@ -71,7 +71,7 @@ "tsdown": "0.16.3", "typescript": "catalog:default", "vite": "catalog:vite-plugin", - "vitest": "catalog:default" + "vitest": "catalog:vitest-3" }, "peerDependencies": { "vite": "^6.1.0 || ^7.0.0", diff --git a/packages/vite-plugin-cloudflare/playground/package.json b/packages/vite-plugin-cloudflare/playground/package.json index 3c2304932094..05e4f463f795 100644 --- a/packages/vite-plugin-cloudflare/playground/package.json +++ b/packages/vite-plugin-cloudflare/playground/package.json @@ -19,6 +19,7 @@ "playwright-chromium": "catalog:default", "semver": "^7.7.1", "ts-dedent": "^2.2.0", - "typescript": "catalog:default" + "typescript": "catalog:default", + "vitest": "catalog:vitest-3" } } diff --git a/packages/vitest-pool-workers/package.json b/packages/vitest-pool-workers/package.json index 1ad76e4b01ac..4694d16f8334 100644 --- a/packages/vitest-pool-workers/package.json +++ b/packages/vitest-pool-workers/package.json @@ -28,27 +28,25 @@ "type": "module", "exports": { ".": { - "types": "./types/cloudflare-test.d.ts", + "types": "./dist/pool/index.d.mts", "import": "./dist/pool/index.mjs" }, - "./config": { - "types": "./dist/config/index.d.ts", - "import": "./dist/config/index.cjs", - "require": "./dist/config/index.cjs" + "./types": { + "types": "./types/cloudflare-test.d.ts" } }, "main": "dist/pool/index.mjs", - "types": "types/cloudflare-test.d.ts", + "types": "dist/pool/index.d.mts", "files": [ "dist", "types/cloudflare-test.d.ts" ], "scripts": { - "build": "node scripts/bundle.mjs && tsc -p tsconfig.emit.json", + "build": "tsdown", "capnp:rtti": "capnp-es scripts/rtti/rtti.capnp -ojs", "check:lint": "eslint . --max-warnings=0 --cache", "check:type": "tsc && tsc -p src/worker/tsconfig.json && tsc -p types/tsconfig.json", - "dev": "node scripts/bundle.mjs watch", + "dev": "tsdown --watch", "test": "vitest run", "test:ci": "vitest run" }, @@ -56,7 +54,8 @@ "cjs-module-lexer": "^1.2.3", "esbuild": "catalog:default", "miniflare": "workspace:*", - "wrangler": "workspace:*" + "wrangler": "workspace:*", + "zod": "^3.25.76" }, "devDependencies": { "@cloudflare/eslint-config-shared": "workspace:*", @@ -77,15 +76,15 @@ "semver": "^7.7.1", "tree-kill": "catalog:default", "ts-dedent": "^2.2.0", + "tsdown": "0.16.3", "typescript": "catalog:default", "undici": "catalog:default", - "vitest": "catalog:default", - "zod": "^3.25.76" + "vitest": "catalog:default" }, "peerDependencies": { - "@vitest/runner": "2.0.x - 3.2.x", - "@vitest/snapshot": "2.0.x - 3.2.x", - "vitest": "2.0.x - 3.2.x" + "@vitest/runner": "4.1.0-beta.4", + "@vitest/snapshot": "4.1.0-beta.4", + "vitest": "4.1.0-beta.4" }, "volta": { "extends": "../../package.json" diff --git a/packages/vitest-pool-workers/scripts/bundle.mjs b/packages/vitest-pool-workers/scripts/bundle.mjs deleted file mode 100644 index cf1434c17dc7..000000000000 --- a/packages/vitest-pool-workers/scripts/bundle.mjs +++ /dev/null @@ -1,152 +0,0 @@ -import fs from "node:fs"; -import module from "node:module"; -import path from "node:path"; -import url from "node:url"; -import esbuild from "esbuild"; -import { builtinModules } from "./rtti/query.mjs"; - -const argv = process.argv.slice(2); -const watch = argv[0] === "watch"; - -const __filename = url.fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const pkgRoot = path.resolve(__dirname, ".."); - -/** - * @param {string} rootPath - * @returns {Generator} - */ -function* walk(rootPath) { - for (const entry of fs.readdirSync(rootPath, { withFileTypes: true })) { - const filePath = path.join(rootPath, entry.name); - if (entry.isDirectory()) yield* walk(filePath); - else yield filePath; - } -} - -// Build a stripped down version of `undici` with just the `MockAgent` and some -// useful helpers exposed. In particular, code for actually sending network -// requests is removed, and replaced with a hook for providing this -// functionality at runtime. -const require = module.createRequire(import.meta.url); -function map(specifier, target) { - const filePath = require.resolve(specifier); - const targetPath = path.join(pkgRoot, target); - return { [filePath]: targetPath }; -} -const fetchMockPathMap = { - ...map("undici/lib/dispatcher/client.js", "src/mock-agent/client.cjs"), - ...map("undici/lib/dispatcher/pool.js", "src/mock-agent/pool.cjs"), - ...map( - "undici/lib/mock/pending-interceptors-formatter.js", - "src/mock-agent/pending-interceptor-formatter.cjs" - ), -}; -await esbuild.build({ - platform: "node", - target: "esnext", - format: "cjs", - outExtension: { ".js": ".cjs" }, - bundle: true, - sourcemap: true, - sourcesContent: false, - logLevel: "warning", - outdir: path.join(pkgRoot, "dist/worker/lib/cloudflare"), - entryNames: "mock-agent", - entryPoints: [path.join(pkgRoot, "src/mock-agent/index.cjs")], - plugins: [ - { - name: "path-map", - setup(build) { - build.onResolve( - { filter: /^\..+$/, namespace: "file" }, - async (args) => { - const result = await build.resolve(args.path, { - namespace: "resolve", // `args.path` would lead to cycles - importer: args.importer, - resolveDir: args.resolveDir, - kind: args.kind, - }); - const maybePath = fetchMockPathMap[result.path]; - if (maybePath === undefined) return; - return { path: maybePath }; - } - ); - }, - }, - ], -}); - -// Build pool, worker and libs -const libPaths = [ - ...walk(path.join(pkgRoot, "src/worker/lib")), - ...walk(path.join(pkgRoot, "src/worker/node")), -]; - -/** @type {import("esbuild").BuildOptions} */ -const commonOptions = { - platform: "node", - target: "esnext", - bundle: true, - external: [ - // Node.js built-ins (handled automatically by esbuild but listed for completeness) - "node:*", - // Cloudflare/workerd built-ins - "cloudflare:*", - "workerd:*", - // Virtual/runtime modules - "__VITEST_POOL_WORKERS_DEFINES", - "__VITEST_POOL_WORKERS_USER_OBJECT", - // External dependencies (see scripts/deps.ts for rationale) - "cjs-module-lexer", - "esbuild", - // Workspace dependencies - "miniflare", - "wrangler", - // Peer dependencies - "vitest", - "vitest/*", - "@vitest/runner", - "@vitest/snapshot", - "@vitest/snapshot/*", - ], - sourcemap: true, - sourcesContent: false, - logLevel: watch ? "info" : "warning", - outdir: path.join(pkgRoot, "dist"), - outbase: path.join(pkgRoot, "src"), - define: { - VITEST_POOL_WORKERS_DEFINE_BUILTIN_MODULES: JSON.stringify(builtinModules), - }, -}; - -const esmOptions = { - ...commonOptions, - format: "esm", - outExtension: { ".js": ".mjs" }, - entryPoints: [ - path.join(pkgRoot, "src", "pool", "index.ts"), - path.join(pkgRoot, "src", "worker", "index.ts"), - ...libPaths.filter((libPath) => /\.m?ts$/.test(libPath)), - ], -}; - -const cjsOptions = { - ...commonOptions, - format: "cjs", - outExtension: { ".js": ".cjs" }, - entryPoints: [ - path.join(pkgRoot, "src", "config", "index.ts"), - ...libPaths.filter((libPath) => /\.cts$/.test(libPath)), - ], -}; - -if (watch) { - const esmCtx = await esbuild.context(esmOptions); - const cjsCtx = await esbuild.context(cjsOptions); - await esmCtx.watch(); - await cjsCtx.watch(); -} else { - await esbuild.build(esmOptions); - await esbuild.build(cjsOptions); -} diff --git a/packages/vitest-pool-workers/scripts/deps.ts b/packages/vitest-pool-workers/scripts/deps.ts index 6b71448498c3..712bd38ce6e4 100644 --- a/packages/vitest-pool-workers/scripts/deps.ts +++ b/packages/vitest-pool-workers/scripts/deps.ts @@ -10,4 +10,7 @@ export const EXTERNAL_DEPENDENCIES = [ // Native binary - cannot be bundled, used to bundle test files at runtime "esbuild", + + // Used for config validation at runtime - must be available when package is installed + "zod", ]; diff --git a/packages/vitest-pool-workers/scripts/rtti/query.mjs b/packages/vitest-pool-workers/scripts/rtti/query.mjs index 9dd2983c2100..b43134a50e42 100644 --- a/packages/vitest-pool-workers/scripts/rtti/query.mjs +++ b/packages/vitest-pool-workers/scripts/rtti/query.mjs @@ -2,12 +2,13 @@ import { Message } from "capnp-es"; import { Miniflare } from "miniflare"; import { StructureGroups } from "./rtti.js"; -// Extract RTTI from `workerd` -const mf = new Miniflare({ - compatibilityFlags: ["rtti_api"], - modules: true, - scriptPath: "query-worker.mjs", - script: ` +export async function getBuiltinModules() { + // Extract RTTI from `workerd` + const mf = new Miniflare({ + compatibilityFlags: ["rtti_api"], + modules: true, + scriptPath: "query-worker.mjs", + script: ` import rtti from "workerd:rtti"; export default { fetch() { @@ -15,30 +16,31 @@ const mf = new Miniflare({ } } `, -}); -const res = await mf.dispatchFetch("http://localhost"); -if (!res.ok) throw new Error(await res.text()); -const buffer = await res.arrayBuffer(); -await mf.dispose(); + }); + const res = await mf.dispatchFetch("http://localhost"); + if (!res.ok) throw new Error(await res.text()); + const buffer = await res.arrayBuffer(); + await mf.dispose(); -// Parse RTTI -const message = new Message(buffer, /* packed */ false); + // Parse RTTI + const message = new Message(buffer, /* packed */ false); -const root = message.getRoot(StructureGroups); -const structures = new Map(); -root.groups.forEach((group) => { - group.structures.forEach((structure) => { - structures.set(structure.fullyQualifiedName, structure); + const root = message.getRoot(StructureGroups); + const structures = new Map(); + root.groups.forEach((group) => { + group.structures.forEach((structure) => { + structures.set(structure.fullyQualifiedName, structure); + }); }); -}); -// Get built-in modules list -const builtinModuleNames = new Set(); -root.modules.forEach((module) => { - builtinModuleNames.add(module.specifier); -}); -// TODO(soon): remove this line once `exportTypes()` supports compatibility -// flags that require `--experimental` (e.g. "unsafe_module") -builtinModuleNames.add("workerd:unsafe"); -/** @type {string[]} */ -export const builtinModules = Array.from(builtinModuleNames); + // Get built-in modules list + const builtinModuleNames = new Set(); + root.modules.forEach((module) => { + builtinModuleNames.add(module.specifier); + }); + // TODO(soon): remove this line once `exportTypes()` supports compatibility + // flags that require `--experimental` (e.g. "unsafe_module") + builtinModuleNames.add("workerd:unsafe"); + /** @type {string[]} */ + return Array.from(builtinModuleNames); +} diff --git a/packages/vitest-pool-workers/src/config/index.ts b/packages/vitest-pool-workers/src/config/index.ts deleted file mode 100644 index 8a7267ff0fc0..000000000000 --- a/packages/vitest-pool-workers/src/config/index.ts +++ /dev/null @@ -1,240 +0,0 @@ -import assert from "node:assert"; -import crypto from "node:crypto"; -import fs from "node:fs/promises"; -import { builtinModules } from "node:module"; -import path from "node:path"; -import { MessageChannel, receiveMessageOnPort } from "node:worker_threads"; -import { workerdBuiltinModules } from "../shared/builtin-modules"; -import type { - WorkersConfigPluginAPI, - WorkersPoolOptions, -} from "../pool/config"; -import type { Plugin } from "vite"; -import type { Awaitable, inject } from "vitest"; -import type { ConfigEnv, UserConfig, UserWorkspaceConfig } from "vitest/config"; - -const cloudflareTestPath = path.resolve( - __dirname, - "../worker/lib/cloudflare/test.mjs" -); - -// Vitest will call `structuredClone()` to verify data is serialisable. -// `structuredClone()` was only added to the global scope in Node 17. -// TODO(now): make Node 18 the minimum supported version -let channel: MessageChannel; -globalThis.structuredClone ??= function (value, options) { - // https://github.com/nodejs/node/blob/71951a0e86da9253d7c422fa2520ee9143e557fa/lib/internal/structured_clone.js - channel ??= new MessageChannel(); - channel.port1.unref(); - channel.port2.unref(); - channel.port1.postMessage(value, options?.transfer); - const message = receiveMessageOnPort(channel.port2); - assert(message !== undefined); - return message.message; -}; - -type ConfigFn = (env: ConfigEnv) => T | Promise; - -export type AnyConfigExport = - | T - | Promise - | ConfigFn; - -function mapAnyConfigExport( - f: (t: T) => U, - config: T -): U; -function mapAnyConfigExport( - f: (t: T) => U, - config: Promise -): Promise; -function mapAnyConfigExport( - f: (t: T) => U, - config: ConfigFn -): ConfigFn; -function mapAnyConfigExport( - f: (t: T) => U, - config: AnyConfigExport -): AnyConfigExport { - if (typeof config === "function") { - return (env) => { - const t = config(env); - if (t instanceof Promise) { - return t.then(f); - } else { - return f(t); - } - }; - } else if (config instanceof Promise) { - return config.then(f); - } else { - return f(config); - } -} - -export interface WorkerPoolOptionsContext { - // For accessing values from `globalSetup()` (e.g. ports servers started on) - // in Miniflare options (e.g. bindings, upstream, hyperdrives, ...) - inject: typeof inject; -} -export type WorkersUserConfig = T & { - test?: { - pool?: "@cloudflare/vitest-pool-workers"; - poolMatchGlobs?: never; - poolOptions?: { - workers?: - | WorkersPoolOptions - | ((ctx: WorkerPoolOptionsContext) => Awaitable); - }; - }; -}; - -export type WorkersUserConfigExport = WorkersUserConfig; -export type WorkersProjectConfigExport = WorkersUserConfig; - -function ensureArrayIncludes(array: T[], items: T[]) { - for (const item of items) { - if (!array.includes(item)) { - array.push(item); - } - } -} - -function ensureArrayExcludes(array: T[], items: T[]) { - for (let i = 0; i < array.length; i++) { - if (items.includes(array[i])) { - array.splice(i, 1); - i--; - } - } -} - -const requiredConditions = ["workerd", "worker", "browser"]; -const requiredMainFields = ["browser", "module", "jsnext:main", "jsnext"]; - -function createConfigPlugin(): Plugin { - // Use a unique ID for each `cloudflare:test` module so updates in one `main` - // don't trigger re-runs in all other projects, just the one that changed. - const uuid = crypto.randomUUID(); - let main: string | undefined; - return { - name: "@cloudflare/vitest-pool-workers:config", - api: { - setMain(newMain) { - main = newMain; - }, - }, - // Run after `vitest:project` plugin: - // https://github.com/vitest-dev/vitest/blob/v3.0.5/packages/vitest/src/node/plugins/workspace.ts#L37 - config(config) { - config.resolve ??= {}; - config.resolve.conditions ??= []; - config.resolve.mainFields ??= []; - config.ssr ??= {}; - config.test ??= {}; - - // Remove "node" condition added by the `vitest:project` plugin. We're - // running tests inside `workerd`, not Node.js, so "node" isn't needed. - ensureArrayExcludes(config.resolve.conditions, ["node"]); - - // Use the same resolve conditions as `wrangler`, minus "import" as this - // breaks Vite's `require()` resolve - ensureArrayIncludes(config.resolve.conditions, requiredConditions); - - // Vitest sets this to an empty array if unset, so restore Vite defaults: - // https://github.com/vitest-dev/vitest/blob/v3.0.5/packages/vitest/src/node/plugins/utils.ts#L156 - ensureArrayIncludes(config.resolve.mainFields, requiredMainFields); - - // Apply `package.json` `browser` field remapping in SSR mode: - // https://github.com/vitejs/vite/blob/v5.1.4/packages/vite/src/node/plugins/resolve.ts#L175 - config.ssr.target = "webworker"; - - // Pre-bundling dependencies with vite - config.test.deps ??= {}; - config.test.deps.optimizer ??= {}; - config.test.deps.optimizer.ssr ??= {}; - config.test.deps.optimizer.ssr.enabled ??= true; - config.test.deps.optimizer.ssr.include ??= []; - ensureArrayIncludes(config.test.deps.optimizer.ssr.include, [ - "vitest > @vitest/snapshot > magic-string", - ]); - ensureArrayIncludes(config.test.deps.optimizer.ssr.include, [ - "vitest > @vitest/expect > chai", - ]); - config.test.deps.optimizer.ssr.exclude ??= []; - ensureArrayIncludes(config.test.deps.optimizer.ssr.exclude, [ - ...workerdBuiltinModules, - ...builtinModules.concat(builtinModules.map((m) => `node:${m}`)), - ]); - - // Ideally, we would force `pool` to be @cloudflare/vitest-pool-workers here, - // but the tests in `packages/vitest-pool-workers` define `pool` as "../..". - config.test.pool ??= "@cloudflare/vitest-pool-workers"; - }, - resolveId(id) { - if (id === "cloudflare:test") { - return `\0cloudflare:test-${uuid}`; - } - }, - async load(id) { - if (id === `\0cloudflare:test-${uuid}`) { - let contents = await fs.readFile(cloudflareTestPath, "utf8"); - if (main !== undefined) { - // Inject a side-effect only import of the main entry-point into the test so that Vitest - // knows to re-run tests when the Worker is modified. - contents += `import ${JSON.stringify(main)};`; - } - return contents; - } - }, - }; -} - -function ensureWorkersConfig(config: T): T { - config.plugins ??= []; - config.plugins.push(createConfigPlugin()); - return config; -} - -export function defineWorkersConfig( - config: WorkersUserConfigExport -): WorkersUserConfigExport; -export function defineWorkersConfig( - config: Promise -): Promise; -export function defineWorkersConfig( - config: ConfigFn -): ConfigFn; -export function defineWorkersConfig( - config: AnyConfigExport -): AnyConfigExport { - if (typeof config === "function") { - return mapAnyConfigExport(ensureWorkersConfig, config); - } else if (config instanceof Promise) { - return mapAnyConfigExport(ensureWorkersConfig, config); - } - return mapAnyConfigExport(ensureWorkersConfig, config); -} - -export function defineWorkersProject( - config: WorkersProjectConfigExport -): WorkersProjectConfigExport; -export function defineWorkersProject( - config: Promise -): Promise; -export function defineWorkersProject( - config: ConfigFn -): ConfigFn; -export function defineWorkersProject( - config: AnyConfigExport -): AnyConfigExport { - if (typeof config === "function") { - return mapAnyConfigExport(ensureWorkersConfig, config); - } else if (config instanceof Promise) { - return mapAnyConfigExport(ensureWorkersConfig, config); - } - return mapAnyConfigExport(ensureWorkersConfig, config); -} - -export * from "./d1"; -export * from "./pages"; diff --git a/packages/vitest-pool-workers/src/config/pages.ts b/packages/vitest-pool-workers/src/config/pages.ts deleted file mode 100644 index f383d60de7f6..000000000000 --- a/packages/vitest-pool-workers/src/config/pages.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Request, Response } from "miniflare"; -import type { Unstable_ASSETSBindingsOptions } from "wrangler"; - -export async function buildPagesASSETSBinding( - assetsPath: string -): Promise<(request: Request) => Promise> { - // noinspection SuspiciousTypeOfGuard - if (typeof assetsPath !== "string") { - throw new TypeError( - "Failed to execute 'buildPagesASSETSBinding': parameter 1 is not of type 'string'." - ); - } - - const { unstable_generateASSETSBinding } = await import("wrangler"); // (lazy) - const log = { - ...console, - debugWithSanitization: console.debug, - loggerLevel: "info", - columns: process.stdout.columns, - } as unknown as Unstable_ASSETSBindingsOptions["log"]; - return unstable_generateASSETSBinding({ log, directory: assetsPath }); -} diff --git a/packages/vitest-pool-workers/src/mock-agent/client.cjs b/packages/vitest-pool-workers/src/mock-agent/client.cjs deleted file mode 100644 index 6b8daba2a088..000000000000 --- a/packages/vitest-pool-workers/src/mock-agent/client.cjs +++ /dev/null @@ -1,18 +0,0 @@ -const assert = require("node:assert"); -const DispatcherBase = require("undici/lib/dispatcher/dispatcher-base"); -const { kDispatch, kClose, kDestroy } = require("undici/lib/core/symbols"); -const { getDispatcher } = require("./dispatcher.cjs"); - -module.exports = class Client extends DispatcherBase { - [kDispatch](opts, handler) { - const dispatcher = getDispatcher(); - if (dispatcher === undefined) { - assert.fail("setDispatcher() must be called before Client#[kDispatch]()"); - } - dispatcher(opts, handler); - return true; - } - - async [kClose]() {} - async [kDestroy](_err) {} -}; diff --git a/packages/vitest-pool-workers/src/mock-agent/dispatcher.cjs b/packages/vitest-pool-workers/src/mock-agent/dispatcher.cjs deleted file mode 100644 index 86384a8f0640..000000000000 --- a/packages/vitest-pool-workers/src/mock-agent/dispatcher.cjs +++ /dev/null @@ -1,9 +0,0 @@ -let dispatcher; -module.exports = { - getDispatcher() { - return dispatcher; - }, - setDispatcher(newDispatcher) { - dispatcher = newDispatcher; - }, -}; diff --git a/packages/vitest-pool-workers/src/mock-agent/index.cjs b/packages/vitest-pool-workers/src/mock-agent/index.cjs deleted file mode 100644 index 09a3076757d2..000000000000 --- a/packages/vitest-pool-workers/src/mock-agent/index.cjs +++ /dev/null @@ -1,29 +0,0 @@ -globalThis.global = globalThis; -process.versions = { node: "18.0.0" }; - -const MockAgent = require("undici/lib/mock/mock-agent"); -const { kClients } = require("undici/lib/core/symbols"); -const { kIsMockActive, kDispatches } = require("undici/lib/mock/mock-symbols"); -const { setDispatcher } = require("./dispatcher.cjs"); - -function isMockActive(agent) { - return agent[kIsMockActive]; -} - -function resetMockAgent(agent) { - agent.deactivate(); - agent.enableNetConnect(); - - // Remove all pending interceptors - for (const mockClient of agent[kClients].values()) { - mockClient.dispatcher?.[kDispatches].splice(0); - } - agent.assertNoPendingInterceptors(); -} - -module.exports = { - MockAgent, - setDispatcher, - isMockActive, - resetMockAgent, -}; diff --git a/packages/vitest-pool-workers/src/mock-agent/pending-interceptor-formatter.cjs b/packages/vitest-pool-workers/src/mock-agent/pending-interceptor-formatter.cjs deleted file mode 100644 index eea87b5fcbdd..000000000000 --- a/packages/vitest-pool-workers/src/mock-agent/pending-interceptor-formatter.cjs +++ /dev/null @@ -1,34 +0,0 @@ -const util = require("node:util"); - -// `PendingInterceptorsFormatter` without dependency on `Console#table()` -module.exports = class PendingInterceptorsFormatter { - constructor({ disableColors } = {}) { - this.inspectOptions = { - breakLength: Infinity, - colors: !disableColors && !process.env.CI, - }; - } - - format(pendingInterceptors) { - const formatted = pendingInterceptors.map( - ({ - method, - path, - data: { statusCode }, - persist, - times, - timesInvoked, - origin, - }) => { - const meta = { - persist: Boolean(persist), - invoked: timesInvoked, - remaining: persist ? Infinity : times - timesInvoked, - }; - const inspectedMeta = util.inspect(meta, this.inspectOptions); - return `- ${method} ${origin}${path} ${statusCode} ${inspectedMeta}`; - } - ); - return formatted.join("\n"); - } -}; diff --git a/packages/vitest-pool-workers/src/mock-agent/pool.cjs b/packages/vitest-pool-workers/src/mock-agent/pool.cjs deleted file mode 100644 index 9a6b4463e5f4..000000000000 --- a/packages/vitest-pool-workers/src/mock-agent/pool.cjs +++ /dev/null @@ -1,9 +0,0 @@ -const assert = require("node:assert"); - -module.exports = class Pool { - constructor() { - // We always construct the `MockAgent` with `{ connections: 1 }` which - // constructs `MockClient`s directly, rather than using `MockPool`s - assert.fail("Pool is not implemented in worker"); - } -}; diff --git a/packages/vitest-pool-workers/src/pool/cloudflare-pool-worker.ts b/packages/vitest-pool-workers/src/pool/cloudflare-pool-worker.ts new file mode 100644 index 000000000000..3ed330821c34 --- /dev/null +++ b/packages/vitest-pool-workers/src/pool/cloudflare-pool-worker.ts @@ -0,0 +1,290 @@ +import assert from "node:assert"; +import { compileModuleRules, testRegExps } from "miniflare"; +import { type ProvidedContext } from "vitest"; +import { workerdBuiltinModules } from "../shared/builtin-modules"; +import { parseProjectOptions } from "./config"; +import { poolWorkerStarted, poolWorkerStopped } from "./pages"; +import { type WorkerPoolOptionsContext } from "./plugin"; +import { + assertCompatibleVitestVersion, + connectToMiniflareSocket, + getDurableObjectDesignators, + getProjectMiniflare, + getRunnerName, + maybeGetResolvedMainPath, + structuredSerializableParse, + structuredSerializableStringify, +} from "."; +import type { + WorkersConfigPluginAPI, + WorkersPoolOptions, + WorkersPoolOptionsWithDefines, +} from "./config"; +import type { + Miniflare, + MessageEvent as MiniflareMessageEvent, + WebSocket, +} from "miniflare"; +import type { + PoolOptions, + PoolWorker, + WorkerRequest, + WorkerResponse, +} from "vitest/node"; + +export class CloudflarePoolWorker implements PoolWorker { + name = "cloudflare-pool"; + private mf: Miniflare | undefined; + private socket: WebSocket | undefined; + private parsedPoolOptions: WorkersPoolOptionsWithDefines | undefined; + private main: string | undefined; + // Store wrapped listeners so off() can remove them correctly. + // Vitest registers at most one listener per event type. + private messageListener?: (event: MiniflareMessageEvent) => void; + private errorListener?: (event: Event) => void; + private closeListener?: () => void; + + constructor( + private options: PoolOptions, + private poolOptions: + | WorkersPoolOptions + | (( + ctx: WorkerPoolOptionsContext + ) => Promise | WorkersPoolOptions) + ) { + assertCompatibleVitestVersion(options.project.vitest); + } + + async start(): Promise { + poolWorkerStarted(); + + let resolvedPoolOptions: WorkersPoolOptions; + if (typeof this.poolOptions === "function") { + // https://github.com/vitest-dev/vitest/blob/v4.0.18/packages/vitest/src/integrations/inject.ts + const inject = ( + key: K + ): ProvidedContext[K] => { + return this.options.project.getProvidedContext()[key]; + }; + resolvedPoolOptions = await this.poolOptions({ inject }); + } else { + resolvedPoolOptions = this.poolOptions; + } + + this.parsedPoolOptions = await parseProjectOptions( + this.options.project, + resolvedPoolOptions + ); + this.main = maybeGetResolvedMainPath( + this.options.project, + this.parsedPoolOptions + ); + + // Find the vitest-pool-workers plugin and give it the path to the main file. + // This allows that plugin to inject a virtual dependency on main so that vitest + // will automatically re-run tests when that gets updated, avoiding the user having + // to manually add such an import in their tests. + const configPlugin = this.options.project.vite.config.plugins.find( + ({ name }) => name === "@cloudflare/vitest-pool-workers" + ); + if (configPlugin !== undefined) { + const api = configPlugin.api as WorkersConfigPluginAPI; + api.setMain(this.main); + } + + this.mf = await getProjectMiniflare( + this.options.project.vitest, + this.options.project, + this.parsedPoolOptions, + this.main + ); + + this.socket = await connectToMiniflareSocket( + this.mf, + getRunnerName(this.options.project) + ); + } + + async stop(): Promise { + this.socket?.close(); + this.socket = undefined; + await this.mf?.dispose(); + this.mf = undefined; + + // Decrement the active worker count. When the last worker stops, this + // closes file watchers created by buildPagesASSETSBinding() during config + // evaluation — they're registered globally because vitest evaluates all + // project configs at startup, even for projects that won't run. + poolWorkerStopped(); + } + + send(message: WorkerRequest): void { + // Vitest will always call `start()` before calling `send()` + assert(this.socket, "Message sent to Worker before initialisation"); + assert( + this.parsedPoolOptions, + "Message sent to Worker before initialisation" + ); + + // Avoid mutating Vitest's message objects — shallow-copy the parts we modify + let toSend: WorkerRequest = message; + if (message.type === "start") { + // Users can write `vitest --inspect` to start an inspector connection for their tests + // We intercept that option and use it to enable inspection of the Workers running in workerd + // We need to stop it passing through into Vitest's in-Worker code, or Vitest will try and import + // and run `inspector.open()` from `node:inspector` + toSend = { + ...message, + context: { + ...message.context, + config: { + ...message.context.config, + inspector: { + ...message.context.config.inspector, + enabled: false, + }, + }, + }, + }; + } else if (message.type === "run") { + // For some reason providing this using the Vitest `project.provide` API + // doesn't work in Vitest Projects, and so we just provide the context directly + toSend = { + ...message, + context: { + ...message.context, + providedContext: { + ...message.context.providedContext, + cloudflarePoolOptions: JSON.stringify({ + // Include resolved `main` if defined + main: this.main, + // Include designators of all Durable Object namespaces bound in the + // runner worker. We'll use this to list IDs in a namespace. We'll + // also use this to check Durable Object test runner helpers are + // only used with classes defined in the current worker, as these + // helpers rely on wrapping the object. + durableObjectBindingDesignators: [ + ...getDurableObjectDesignators( + this.parsedPoolOptions + ).entries(), + ], + selfName: getRunnerName(this.options.project), + }), + }, + }, + }; + } + this.socket.send(structuredSerializableStringify(toSend)); + } + + on( + event: string, + callback: + | ((maybeError: unknown) => void) + | (() => void) + | ((response: WorkerResponse) => void) + ): void { + // Vitest will always call `start()` before calling `on()` + assert(this.socket, "Message received from Worker before initialisation"); + assert( + this.parsedPoolOptions, + "Message received from Worker before initialisation" + ); + + const rules = this.parsedPoolOptions.miniflare?.modulesRules; + const compiledRules = compileModuleRules(rules ?? []); + + if (event === "message") { + const messageWrapper = (m: { data: string | ArrayBuffer }) => { + const d = structuredSerializableParse( + m.data as string + ) as WorkerResponse; + + // This is a birpc serialised message before it's been parsed, which is why the properties are so unintelligible + // We're looking for a `fetch()` RPC call: https://github.com/vitest-dev/vitest/blob/v4.0.18/packages/vitest/src/types/rpc.ts#L8 + if ( + d && + typeof d === "object" && + "m" in d && + "a" in d && + "i" in d && + Array.isArray(d.a) && + d.m === "fetch" + ) { + assert( + this.socket, + "Message received from Worker before initialisation" + ); + const specifier = d.a[0]; + + if ( + // `cloudflare:test` imports are handled by the `@cloudflare/vitest-pool-workers` plugin, and so should be ignored here + specifier !== "cloudflare:test" && + (/^(cloudflare|workerd):/.test(specifier) || + workerdBuiltinModules.has(specifier)) + ) { + return this.socket.send( + // Tell Vitest to treat this module as "external" and load it using a workerd module import + structuredSerializableStringify({ + t: "s", + i: d.i, + r: { externalize: specifier }, + }) + ); + } + + const maybeRule = compiledRules.find((rule) => + testRegExps(rule.include, specifier) + ); + // Skip if specifier already has query params (e.g. `?raw`), letting Vite handle it. + if (maybeRule !== undefined && !specifier.includes("?")) { + const externalize = + this.options.project.config.root + + specifier + + `?mf_vitest_force=${maybeRule.type}`; + + return this.socket.send( + structuredSerializableStringify({ + t: "s", + i: d.i, + r: { externalize }, + }) + ); + } + } + (callback as (response: WorkerResponse) => void)(d); + }; + this.messageListener = messageWrapper as ( + event: MiniflareMessageEvent + ) => void; + this.socket.addEventListener("message", this.messageListener); + } else if (event === "error") { + this.errorListener = (e: Event) => { + (callback as (maybeError: unknown) => void)("error" in e ? e.error : e); + }; + this.socket.addEventListener("error", this.errorListener); + } else if (event === "exit") { + this.closeListener = callback as () => void; + this.socket.addEventListener("close", this.closeListener); + } + } + + off(event: string, _callback: (_arg: unknown) => void): void { + if (event === "message" && this.messageListener) { + this.socket?.removeEventListener("message", this.messageListener); + this.messageListener = undefined; + } else if (event === "error" && this.errorListener) { + this.socket?.removeEventListener("error", this.errorListener); + this.errorListener = undefined; + } else if (event === "exit" && this.closeListener) { + this.socket?.removeEventListener("close", this.closeListener); + this.closeListener = undefined; + } + } + + // Vitest does not have a corresponding `serialize()` option, so we can't actually use this for serialisation + // Instead, we serialize/deserialize in the `send()` and `on()` methods. + deserialize(data: unknown) { + return data; + } +} diff --git a/packages/vitest-pool-workers/src/pool/compatibility-flag-assertions.ts b/packages/vitest-pool-workers/src/pool/compatibility-flag-assertions.ts index 22e92faf1224..aaa9574393c6 100644 --- a/packages/vitest-pool-workers/src/pool/compatibility-flag-assertions.ts +++ b/packages/vitest-pool-workers/src/pool/compatibility-flag-assertions.ts @@ -62,8 +62,10 @@ export class CompatibilityFlagAssertions { disableFlag: string, defaultOnDate?: string ): boolean { + if (this.#flagExists(disableFlag)) { + return false; + } return ( - !this.#flagExists(disableFlag) || this.#flagExists(enableFlag) || isDateSufficient(this.#compatibilityDate, defaultOnDate) ); diff --git a/packages/vitest-pool-workers/src/pool/config.ts b/packages/vitest-pool-workers/src/pool/config.ts index ca42a70ff8fb..2baab1005a9a 100644 --- a/packages/vitest-pool-workers/src/pool/config.ts +++ b/packages/vitest-pool-workers/src/pool/config.ts @@ -9,10 +9,13 @@ import { PLUGINS, } from "miniflare"; import { z } from "zod"; -import { getProjectPath, getRelativeProjectPath } from "./helpers"; +import { + getProjectPath, + getRelativeProjectConfigPath, + getRelativeProjectPath, +} from "./helpers"; import type { ModuleRule, WorkerOptions } from "miniflare"; -import type { ProvidedContext } from "vitest"; -import type { WorkspaceProject } from "vitest/node"; +import type { TestProject } from "vitest/node"; import type { Binding, RemoteProxySession } from "wrangler"; import type { ParseParams, ZodError } from "zod"; @@ -22,13 +25,10 @@ export interface WorkersConfigPluginAPI { const PLUGIN_VALUES = Object.values(PLUGINS); -const OPTIONS_PATH_ARRAY = ["test", "poolOptions", "workers"]; -export const OPTIONS_PATH = OPTIONS_PATH_ARRAY.join("."); - const WorkersPoolOptionsSchema = z.object({ /** * Entrypoint to Worker run in the same isolate/context as tests. This is - * required to use `import { SELF } from "cloudflare:test"`, or Durable + * required to use `import { exports } from "cloudflare:workers"`, or Durable * Objects without an explicit `scriptName`. Note this goes through Vite * transforms and can be a TypeScript file. Note also * `import module from ""` inside tests gives exactly the same @@ -36,14 +36,6 @@ const WorkersPoolOptionsSchema = z.object({ * bindings. */ main: z.ostring(), - /** - * Enables per-test isolated storage. If enabled, any writes to storage - * performed in a test will be undone at the end of the test. The test storage - * environment is copied from the containing suite, meaning `beforeAll()` - * hooks can be used to seed data. If this is disabled, all tests will share - * the same storage. - */ - isolatedStorage: z.boolean().default(true), /** * Enables remote bindings to access remote resources configured * with `remote: true` in the wrangler configuration file. @@ -67,15 +59,6 @@ const WorkersPoolOptionsSchema = z.object({ ]) ) .default({}), - /** - * If enabled, Workers will be run in a single shared worker instance. - */ - /** - * Runs all tests in this project serially in the same worker, using the same - * module cache. This can significantly speed up tests if you've got lots of - * small test files. - */ - singleWorker: z.boolean().default(false), miniflare: z .object({ workers: z.array(z.object({}).passthrough()).optional(), @@ -86,6 +69,7 @@ const WorkersPoolOptionsSchema = z.object({ .object({ configPath: z.ostring(), environment: z.ostring() }) .optional(), }); + export type SourcelessWorkerOptions = Omit< WorkerOptions, "script" | "scriptPath" | "modules" | "modulesRoot" @@ -94,6 +78,7 @@ export type SourcelessWorkerOptions = Omit< // from which `WorkerOptions` is derived. Therefore, we manually include it. modulesRules?: ModuleRule[]; }; + export type WorkersPoolOptions = z.input & { miniflare?: SourcelessWorkerOptions & { workers?: WorkerOptions[]; @@ -209,13 +194,11 @@ const remoteProxySessionsDataMap = new Map< async function parseCustomPoolOptions( rootPath: string, - value: unknown, - opts: PathParseParams + value: unknown ): Promise { // Try to parse pool specific options const options = WorkersPoolOptionsSchema.parse( - value, - opts + value ) as WorkersPoolOptionsWithDefines; options.miniflare ??= {}; @@ -229,7 +212,7 @@ async function parseCustomPoolOptions( rootPath, options.miniflare, /* withoutScript */ true, // (script provided by runner) - { path: [...opts.path, "miniflare"] } + { path: ["miniflare"] } ); } catch (e) { coalesceZodErrors(errorRef, e); @@ -247,7 +230,7 @@ async function parseCustomPoolOptions( worker, /* withoutScript */ false, { - path: [...opts.path, "miniflare", "workers", i], + path: ["miniflare", "workers", i], } ); } catch (e) { @@ -275,16 +258,15 @@ async function parseCustomPoolOptions( ? remoteProxySessionsDataMap.get(options.wrangler.configPath) : undefined; - const remoteProxySessionData = - options.remoteBindings ?? true - ? await wrangler.maybeStartOrUpdateRemoteProxySession( - { - path: options.wrangler.configPath, - environment: options.wrangler.environment, - }, - preExistingRemoteProxySessionData ?? null - ) - : null; + const remoteProxySessionData = options.remoteBindings + ? await wrangler.maybeStartOrUpdateRemoteProxySession( + { + path: options.wrangler.configPath, + environment: options.wrangler.environment, + }, + preExistingRemoteProxySessionData ?? null + ) + : null; if (options.wrangler?.configPath && remoteProxySessionData) { remoteProxySessionsDataMap.set( @@ -308,22 +290,6 @@ async function parseCustomPoolOptions( } ); - const wrappedBindings = Object.values(workerOptions.wrappedBindings ?? {}); - - const hasAIOrVectorizeBindings = wrappedBindings.some((binding) => { - return ( - typeof binding === "object" && - (binding.scriptName.includes("__WRANGLER_EXTERNAL_VECTORIZE_WORKER") || - binding.scriptName.includes("__WRANGLER_EXTERNAL_AI_WORKER")) - ); - }); - - if (hasAIOrVectorizeBindings) { - log.warn( - "Workers AI and Vectorize bindings will access your Cloudflare account and incur usage charges even in testing. We recommend mocking any usage of these bindings in your tests." - ); - } - // If `main` wasn't explicitly configured, fall back to Wrangler config's options.main ??= main; @@ -361,7 +327,8 @@ async function parseCustomPoolOptions( } export async function parseProjectOptions( - project: WorkspaceProject + project: TestProject, + poolOptions: unknown ): Promise { // Make sure the user hasn't specified a custom environment. This was how // users enabled Miniflare 2's Vitest environment, so it's likely users will @@ -381,44 +348,27 @@ export async function parseProjectOptions( `Unexpected custom \`environment\` ${quotedEnvironment} in project ${relativePath}.`, "The Workers pool always runs your tests inside of an environment providing Workers runtime APIs.", `Please remove the \`environment\` configuration${migrationGuide}`, - "Use `poolMatchGlobs`/`environmentMatchGlobs` to run a subset of your tests in a different pool/environment.", ].join("\n"); throw new TypeError(message); } const projectPath = getProjectPath(project); - const rootPath = - typeof projectPath === "string" ? path.dirname(projectPath) : ""; - const poolOptions = project.config.poolOptions; - let workersPoolOptions = poolOptions?.workers ?? {}; + try { - if (typeof workersPoolOptions === "function") { - // https://github.com/vitest-dev/vitest/blob/v2.1.1/packages/vitest/src/integrations/inject.ts - const inject = ( - key: K - ): ProvidedContext[K] => { - return project.getProvidedContext()[key]; - }; - workersPoolOptions = await workersPoolOptions({ inject }); - } - return await parseCustomPoolOptions(rootPath, workersPoolOptions, { - path: OPTIONS_PATH_ARRAY, - }); + return await parseCustomPoolOptions(projectPath, poolOptions); } catch (e) { if (!isZodErrorLike(e)) { throw e; } let formatted: string; try { - formatted = formatZodError(e, { - test: { poolOptions: { workers: workersPoolOptions } }, - }); + formatted = formatZodError(e, poolOptions); } catch { throw e; } - const relativePath = getRelativeProjectPath(project); + const relativePath = getRelativeProjectConfigPath(project); throw new TypeError( - `Unexpected pool options in project ${relativePath}:\n${formatted}` + `Unexpected options in project ${relativePath}:\n${formatted}` ); } } diff --git a/packages/vitest-pool-workers/src/config/d1.ts b/packages/vitest-pool-workers/src/pool/d1.ts similarity index 100% rename from packages/vitest-pool-workers/src/config/d1.ts rename to packages/vitest-pool-workers/src/pool/d1.ts diff --git a/packages/vitest-pool-workers/src/pool/helpers.ts b/packages/vitest-pool-workers/src/pool/helpers.ts index ed56afe43ef9..d6f67e03f66c 100644 --- a/packages/vitest-pool-workers/src/pool/helpers.ts +++ b/packages/vitest-pool-workers/src/pool/helpers.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import type { WorkspaceProject } from "vitest/node"; +import type { TestProject } from "vitest/node"; // User worker names must not start with this export const WORKER_NAME_PREFIX = "vitest-pool-workers-"; @@ -10,17 +10,17 @@ export function isFileNotFoundError(e: unknown): boolean { ); } -export function getProjectPath(project: WorkspaceProject): string | number { - return project.config.config ?? project.path; +export function getProjectPath(project: TestProject): string { + return project.config.root; } -export function getRelativeProjectPath( - project: WorkspaceProject -): string | number { +export function getRelativeProjectPath(project: TestProject): string { const projectPath = getProjectPath(project); - if (typeof projectPath === "number") { - return projectPath; - } else { - return path.relative("", projectPath); - } + return path.relative("", projectPath); +} + +export function getRelativeProjectConfigPath(project: TestProject): string { + return project.config.config + ? path.relative("", project.config.config) + : getRelativeProjectPath(project); } diff --git a/packages/vitest-pool-workers/src/pool/index.ts b/packages/vitest-pool-workers/src/pool/index.ts index e4ecf1ac68d2..64aa759b7398 100644 --- a/packages/vitest-pool-workers/src/pool/index.ts +++ b/packages/vitest-pool-workers/src/pool/index.ts @@ -1,15 +1,11 @@ import assert from "node:assert"; import crypto from "node:crypto"; -import events from "node:events"; import fs from "node:fs"; import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; -import util from "node:util"; -import { createBirpc } from "birpc"; +import { fileURLToPath } from "node:url"; import * as devalue from "devalue"; import getPort, { portNumbers } from "get-port"; import { - compileModuleRules, getNodeCompat, kCurrentWorker, kUnsafeEphemeralUniqueKey, @@ -20,16 +16,10 @@ import { structuredSerializableReducers, structuredSerializableRevivers, supportedCompatibilityDate, - testRegExps, - WebSocket, } from "miniflare"; import semverSatisfies from "semver/functions/satisfies.js"; -import { createMethodsRPC } from "vitest/node"; import { experimental_readRawConfig } from "wrangler"; -import { workerdBuiltinModules } from "../shared/builtin-modules"; -import { createChunkingSocket } from "../shared/chunking-socket"; import { CompatibilityFlagAssertions } from "./compatibility-flag-assertions"; -import { OPTIONS_PATH, parseProjectOptions } from "./config"; import { guessWorkerExports } from "./guess-exports"; import { getProjectPath, @@ -37,60 +27,21 @@ import { isFileNotFoundError, WORKER_NAME_PREFIX, } from "./helpers"; -import { - ABORT_ALL_WORKER, - handleLoopbackRequest, - scheduleStorageReset, - waitForStorageReset, -} from "./loopback"; +import { handleLoopbackRequest } from "./loopback"; import { handleModuleFallbackRequest } from "./module-fallback"; import type { SourcelessWorkerOptions, - WorkersConfigPluginAPI, WorkersPoolOptions, WorkersPoolOptionsWithDefines, } from "./config"; -import type { - CloseEvent, - MiniflareOptions, - SharedOptions, - WorkerOptions, -} from "miniflare"; +import type { MiniflareOptions, SharedOptions, WorkerOptions } from "miniflare"; import type { Readable } from "node:stream"; -import type { MessagePort } from "node:worker_threads"; -import type { - RunnerRPC, - RuntimeRPC, - SerializedConfig, - WorkerContext, -} from "vitest"; -import type { - ProcessPool, - TestSpecification, - Vitest, - WorkspaceProject, -} from "vitest/node"; - -interface SerializedOptions { - main?: string; - durableObjectBindingDesignators?: Map< - string /* bound name */, - DurableObjectDesignator - >; - isolatedStorage?: boolean; -} +import type { TestProject, Vitest } from "vitest/node"; -// https://github.com/vitest-dev/vitest/blob/v2.1.1/packages/vite-node/src/client.ts#L468 -declare const __vite_ssr_import__: unknown; -assert( - typeof __vite_ssr_import__ === "undefined", - "Expected `@cloudflare/vitest-pool-workers` not to be transformed by Vite" -); - -function structuredSerializableStringify(value: unknown): string { - // Vitest v2+ sends a sourcemap to it's runner, which we can't serialise currently - // Deleting it doesn't seem to cause any problems, and error stack traces etc... - // still seem to work +export function structuredSerializableStringify(value: unknown): string { + // Vitest v2+ sends a sourcemap to its runner, which we can't serialise currently. + // Stripping it doesn't seem to cause any problems, and error stack traces etc. + // still seem to work. // TODO: Figure out how to serialise SourceMap instances if ( value && @@ -101,19 +52,16 @@ function structuredSerializableStringify(value: unknown): string { "map" in value.r && value.r.map ) { - delete value.r.map; + // Shallow-copy to avoid mutating the caller's object + value = { ...value, r: { ...value.r, map: undefined } }; } return devalue.stringify(value, structuredSerializableReducers); } -function structuredSerializableParse(value: string): unknown { + +export function structuredSerializableParse(value: string): unknown { return devalue.parse(value, structuredSerializableRevivers); } -// Log for verbose debug messages (e.g. RPC messages) -let debuglog: util.DebugLoggerFunction = util.debuglog( - "vitest-pool-workers:index", - (fn) => (debuglog = fn) -); // Log for informational pool messages const log = new Log(LogLevel.VERBOSE, { prefix: "vpw" }); // Log for Miniflare instances, used for user code warnings/errors @@ -151,36 +99,8 @@ function handleRuntimeStdio(stdout: Readable, stderr: Readable): void { }); } -type SingleOrPerTestFileMiniflare = - | Miniflare // Single instance - | Map; // Instance per test file -function forEachMiniflare( - mfs: SingleOrPerTestFileMiniflare, - callback: (mf: Miniflare) => Promise -): Promise { - if (mfs instanceof Miniflare) { - return callback(mfs); - } - - const promises: Promise[] = []; - for (const mf of mfs.values()) { - promises.push(callback(mf)); - } - return Promise.all(promises); -} - -interface Project { - project: WorkspaceProject; - options: WorkersPoolOptionsWithDefines; - testFiles: Set; - relativePath: string | number; - mf?: SingleOrPerTestFileMiniflare; - previousMfOptions?: MiniflareOptions; -} -const allProjects = new Map(); - -function getRunnerName(project: WorkspaceProject, testFile?: string) { - const name = `${WORKER_NAME_PREFIX}runner-${project.getName().replace(/[^a-z0-9-]/gi, "_")}`; +export function getRunnerName(project: TestProject, testFile?: string) { + const name = `${WORKER_NAME_PREFIX}runner-${project.name.replace(/[^a-z0-9-]/gi, "_")}`; if (testFile === undefined) { return name; } @@ -232,7 +152,7 @@ interface DurableObjectDesignator { * Returns a map of Durable Objects bindings' bound names to the designators of * the objects they point to. */ -function getDurableObjectDesignators( +export function getDurableObjectDesignators( options: WorkersPoolOptions ): Map { const result = new Map(); @@ -262,17 +182,6 @@ function getDurableObjectDesignators( return result; } -const POOL_WORKER_DIR = path.dirname(POOL_WORKER_PATH); -const USER_OBJECT_MODULE_NAME = "__VITEST_POOL_WORKERS_USER_OBJECT"; -const USER_OBJECT_MODULE_PATH = path.join( - POOL_WORKER_DIR, - USER_OBJECT_MODULE_NAME -); -const DEFINES_MODULE_PATH = path.join( - POOL_WORKER_DIR, - "__VITEST_POOL_WORKERS_DEFINES" -); - /** * Gets a set of Durable Object class names for the SELF Worker. * @@ -314,21 +223,6 @@ function getWranglerWorkerName( return wranglerConfigObject.rawConfig.name; } -function updateWorkflowsScriptNames( - runnerWorker: WorkerOptions, - wranglerWorkerName: string | undefined -): void { - const workflows = runnerWorker.workflows; - if (!workflows || wranglerWorkerName === undefined) { - return; - } - for (const workflow of Object.values(workflows)) { - if (workflow.scriptName === wranglerWorkerName) { - delete workflow.scriptName; - } - } -} - /** * Gets a set of class names for Workflows defined in the SELF Worker. */ @@ -373,24 +267,22 @@ type ProjectWorkers = [ ...auxiliaryWorkers: WorkerOptions[], ]; -const SELF_NAME_BINDING = "__VITEST_POOL_WORKERS_SELF_NAME"; const SELF_SERVICE_BINDING = "__VITEST_POOL_WORKERS_SELF_SERVICE"; const LOOPBACK_SERVICE_BINDING = "__VITEST_POOL_WORKERS_LOOPBACK_SERVICE"; const RUNNER_OBJECT_BINDING = "__VITEST_POOL_WORKERS_RUNNER_OBJECT"; async function buildProjectWorkerOptions( - project: Omit + project: TestProject, + customOptions: WorkersPoolOptionsWithDefines, + main: string | undefined ): Promise { const relativeWranglerConfigPath = maybeApply( (v) => path.relative("", v), - project.options.wrangler?.configPath + customOptions.wrangler?.configPath ); - const runnerWorker = project.options.miniflare ?? {}; + const runnerWorker = customOptions.miniflare ?? {}; - // Make sure the worker has a well-known name, and share it with the runner - runnerWorker.name = getRunnerName(project.project); - runnerWorker.bindings ??= {}; - runnerWorker.bindings[SELF_NAME_BINDING] = runnerWorker.name; + runnerWorker.name = getRunnerName(project); // Make sure the worker has the `nodejs_compat` and `export_commonjs_default` // compatibility flags enabled. Vitest makes heavy use of Node APIs, and many @@ -398,19 +290,23 @@ async function buildProjectWorkerOptions( // `module.exports` directly, rather than `{ default: module.exports }`. runnerWorker.compatibilityFlags ??= []; + runnerWorker.compatibilityFlags.push( + "no_handle_cross_request_promise_resolution" + ); + if (runnerWorker.compatibilityDate === undefined) { // No compatibility date was provided, so infer the latest supported date runnerWorker.compatibilityDate ??= supportedCompatibilityDate; log.info( - `No compatibility date was provided for project ${project.relativePath}, defaulting to latest supported date ${runnerWorker.compatibilityDate}.` + `No compatibility date was provided for project ${getRelativeProjectPath(project)}, defaulting to latest supported date ${runnerWorker.compatibilityDate}.` ); } const flagAssertions = new CompatibilityFlagAssertions({ compatibilityDate: runnerWorker.compatibilityDate, compatibilityFlags: runnerWorker.compatibilityFlags, - optionsPath: `${OPTIONS_PATH}.miniflare`, - relativeProjectPath: project.relativePath.toString(), + optionsPath: `miniflare`, + relativeProjectPath: getRelativeProjectPath(project), relativeWranglerConfigPath, }); @@ -457,6 +353,8 @@ async function buildProjectWorkerOptions( ensureFeature(runnerWorker.compatibilityFlags, "nodejs_fs_module"); ensureFeature(runnerWorker.compatibilityFlags, "nodejs_http_modules"); ensureFeature(runnerWorker.compatibilityFlags, "nodejs_perf_hooks_module"); + ensureFeature(runnerWorker.compatibilityFlags, "nodejs_v8_module"); + ensureFeature(runnerWorker.compatibilityFlags, "nodejs_process_v2"); // Make sure we define an unsafe eval binding and enable the fallback service runnerWorker.unsafeEvalBinding = "__VITEST_POOL_WORKERS_UNSAFE_EVAL"; @@ -486,10 +384,9 @@ async function buildProjectWorkerOptions( ) ) { try { - const resolvedMain = maybeGetResolvedMainPath(project); const guessedExports = await guessWorkerExports( - resolvedMain, - project.options.additionalExports + main, + customOptions.additionalExports ); for (const [exportName, exportType] of guessedExports) { switch (exportType) { @@ -505,7 +402,7 @@ async function buildProjectWorkerOptions( } } } catch (e) { - const message = `Failed to statically analyze the exports of the main Worker entry-point "${project.options.main}"\nMore details: ${e}`; + const message = `Failed to statically analyze the exports of the main Worker entry-point "${customOptions.main}"\nMore details: ${e}`; for (const line of message.split("\n")) { log.warn(line); } @@ -542,13 +439,14 @@ async function buildProjectWorkerOptions( } // Make sure we define the `__VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__` Durable Object, - // which is the singleton host for running tests. + // which is the singleton host for running tests. It's ephemeral (in-memory) + // because the runner doesn't need persistent state, and all disk-backed DOs + // hit a workerd bug on Windows where SQLite paths use Unix-style forward + // slashes (cloudflare/workerd#6110). runnerWorker.durableObjects[RUNNER_OBJECT_BINDING] = { className: "__VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__", - // Make the runner object ephemeral, so it doesn't write any `.sqlite` files - // that would disrupt stacked storage because we prevent eviction - unsafeUniqueKey: kUnsafeEphemeralUniqueKey, unsafePreventEviction: true, + unsafeUniqueKey: kUnsafeEphemeralUniqueKey, }; // Vite has its own define mechanism, but we can't control it from custom @@ -557,7 +455,7 @@ async function buildProjectWorkerOptions( // define script similar to Vite's. When defines change, Miniflare will be // restarted as the input options will be different. const defines = `export default { - ${Object.entries(project.options.defines ?? {}) + ${Object.entries(customOptions.defines ?? {}) .map(([key, value]) => `${JSON.stringify(key)}: ${value}`) .join(",\n")} }; @@ -578,23 +476,24 @@ async function buildProjectWorkerOptions( // module names. Setting `modulesRoot` to a drive letter and prepending this // to paths ensures correct names. This requires us to specify `contents` // with module definitions though, as the new paths don't exist. - // TODO(now): need to add source URL comments here to ensure those are correct + // TODO: add source URL comments to injected modules for better stack traces const modulesRoot = process.platform === "win32" ? "Z:\\" : "/"; runnerWorker.modulesRoot = modulesRoot; + runnerWorker.modules = [ { type: "ESModule", - path: path.join(modulesRoot, POOL_WORKER_PATH), + path: path.join(modulesRoot, "index.mjs"), contents: fs.readFileSync(POOL_WORKER_PATH), }, { type: "ESModule", - path: path.join(modulesRoot, USER_OBJECT_MODULE_PATH), + path: path.join(modulesRoot, "__VITEST_POOL_WORKERS_USER_OBJECT"), contents: wrappers.join("\n"), }, { type: "ESModule", - path: path.join(modulesRoot, DEFINES_MODULE_PATH), + path: path.join(modulesRoot, "__VITEST_POOL_WORKERS_DEFINES"), contents: defines, }, // The native workerd provided nodejs modules don't always support everything Vitest needs. @@ -628,13 +527,13 @@ async function buildProjectWorkerOptions( worker.name === "" ) { throw new Error( - `In project ${project.relativePath}, \`${OPTIONS_PATH}.miniflare.workers[${i}].name\` must be non-empty` + `In project ${getRelativeProjectPath(project)}, \`miniflare.workers[${i}].name\` must be non-empty` ); } // ...that doesn't start with our reserved prefix if (worker.name.startsWith(WORKER_NAME_PREFIX)) { throw new Error( - `In project ${project.relativePath}, \`${OPTIONS_PATH}.miniflare.workers[${i}].name\` must not start with "${WORKER_NAME_PREFIX}", got ${worker.name}` + `In project ${getRelativeProjectPath(project)}, \`miniflare.workers[${i}].name\` must not start with "${WORKER_NAME_PREFIX}", got ${worker.name}` ); } @@ -671,24 +570,27 @@ function getModuleFallbackService(ctx: Vitest): ModuleFallbackService { if (service !== undefined) { return service; } - // @ts-expect-error ctx.vitenode is marked as internal - service = handleModuleFallbackRequest.bind(undefined, ctx.vitenode.server); + service = handleModuleFallbackRequest.bind(undefined, ctx.vite); moduleFallbackServices.set(ctx, service); return service; } /** * Builds options for the Miniflare instance running tests for the given Vitest - * project. The first `runnerWorker` returned may be duplicated in the instance - * if `singleWorker` is disabled so tests can execute in-parallel and isolation. + * project. */ async function buildProjectMiniflareOptions( ctx: Vitest, - project: Project + project: TestProject, + customOptions: WorkersPoolOptions, + main: string | undefined ): Promise { const moduleFallbackService = getModuleFallbackService(ctx); - const [runnerWorker, ...auxiliaryWorkers] = - await buildProjectWorkerOptions(project); + const [runnerWorker, ...auxiliaryWorkers] = await buildProjectWorkerOptions( + project, + customOptions, + main + ); assert(runnerWorker.name !== undefined); assert(runnerWorker.name.startsWith(WORKER_NAME_PREFIX)); @@ -715,167 +617,60 @@ async function buildProjectMiniflareOptions( } } - if (inspectorPort !== undefined && !project.options.singleWorker) { - log.warn(`Tests run in singleWorker mode when the inspector is open.`); - - project.options.singleWorker = true; - } - - if (project.options.singleWorker || project.options.isolatedStorage) { - // Single Worker, Isolated or Shared Storage - // --> single instance with single runner worker - // Multiple Workers, Isolated Storage: - // --> multiple instances each with single runner worker - - // Set Workflows scriptName to the runner worker name if it matches the Wrangler worker name - const wranglerWorkerName = getWranglerWorkerName( - project.options.wrangler?.configPath - ); - updateWorkflowsScriptNames(runnerWorker, wranglerWorkerName); - - return { - ...SHARED_MINIFLARE_OPTIONS, - inspectorPort, - unsafeModuleFallbackService: moduleFallbackService, - workers: [runnerWorker, ABORT_ALL_WORKER, ...auxiliaryWorkers], - }; - } else { - // Multiple Workers, Shared Storage: - // --> single instance with multiple runner workers - const testWorkers: WorkerOptions[] = []; - for (const testFile of project.testFiles) { - const testWorker = { ...runnerWorker }; - testWorker.name = getRunnerName(project.project, testFile); - - // Update binding to own name - assert(testWorker.bindings !== undefined); - testWorker.bindings = { ...testWorker.bindings }; - testWorker.bindings[SELF_NAME_BINDING] = testWorker.name; - - // Set Workflows scriptName to the test worker name if it matches the Wrangler worker name - const wranglerWorkerName = getWranglerWorkerName( - project.options.wrangler?.configPath - ); - updateWorkflowsScriptNames(testWorker, wranglerWorkerName); - - testWorkers.push(testWorker); - } - return { - ...SHARED_MINIFLARE_OPTIONS, - unsafeModuleFallbackService: moduleFallbackService, - workers: [...testWorkers, ABORT_ALL_WORKER, ...auxiliaryWorkers], - }; - } + return { + ...SHARED_MINIFLARE_OPTIONS, + inspectorPort, + unsafeModuleFallbackService: moduleFallbackService, + workers: [runnerWorker, ...auxiliaryWorkers], + }; } -async function getProjectMiniflare( +export async function getProjectMiniflare( ctx: Vitest, - project: Project -): Promise { - const mfOptions = await buildProjectMiniflareOptions(ctx, project); - const changed = !util.isDeepStrictEqual(project.previousMfOptions, mfOptions); - project.previousMfOptions = mfOptions; - - const previousSingleInstance = project.mf instanceof Miniflare; - const singleInstance = - project.options.singleWorker || !project.options.isolatedStorage; - - if (project.mf !== undefined && previousSingleInstance !== singleInstance) { - // If isolated storage configuration has changed, reset project instances - log.info(`Isolation changed for ${project.relativePath}, resetting...`); - await forEachMiniflare(project.mf, (mf) => mf.dispose()); - project.mf = undefined; - } - - if (project.mf === undefined) { - // If `mf` is now `undefined`, create new instances - if (singleInstance) { - log.info( - `Starting single runtime for ${project.relativePath}` + - `${mfOptions.inspectorPort !== undefined ? ` with inspector on port ${mfOptions.inspectorPort}` : ""}` + - `...` - ); - project.mf = new Miniflare(mfOptions); - } else { - log.info(`Starting isolated runtimes for ${project.relativePath}...`); - project.mf = new Map(); - for (const testFile of project.testFiles) { - project.mf.set(testFile, new Miniflare(mfOptions)); - } - } - await forEachMiniflare(project.mf, (mf) => mf.ready); - } else if (changed) { - // Otherwise, update the existing instances if options have changed - log.info(`Options changed for ${project.relativePath}, updating...`); - await forEachMiniflare(project.mf, (mf) => mf.setOptions(mfOptions)); - } else { - log.debug(`Reusing runtime for ${project.relativePath}...`); - } - - return project.mf; + project: TestProject, + poolOptions: WorkersPoolOptionsWithDefines, + main: string | undefined +): Promise { + const mfOptions = await buildProjectMiniflareOptions( + ctx, + project, + poolOptions, + main + ); + log.info( + `Starting runtime for ${getRelativeProjectPath(project)}` + + `${mfOptions.inspectorPort !== undefined ? ` with inspector on port ${mfOptions.inspectorPort}` : ""}` + + `...` + ); + const mf = new Miniflare(mfOptions); + await mf.ready; + return mf; } -function maybeGetResolvedMainPath( - project: Omit +export function maybeGetResolvedMainPath( + project: TestProject, + options: WorkersPoolOptionsWithDefines ): string | undefined { - const projectPath = getProjectPath(project.project); - const main = project.options.main; + const projectPath = getProjectPath(project); + const main = options.main; if (main === undefined) { return; } if (typeof projectPath === "string") { - return path.resolve(path.dirname(projectPath), main); + return path.resolve(projectPath, main); } else { return path.resolve(main); } } -async function runTests( - ctx: Vitest, +export async function connectToMiniflareSocket( mf: Miniflare, - workerName: string, - project: Project, - config: SerializedConfig, - files: string[], - invalidates: string[] = [], - method: "run" | "collect" + workerName: string ) { - const workerPath = path.join(ctx.distPath, "worker.js"); - const threadsWorkerPath = path.join(ctx.distPath, "workers", "threads.js"); - - ctx.state.clearFiles(project.project, files); - const data: WorkerContext = { - pool: "threads", - worker: pathToFileURL(threadsWorkerPath).href, - port: undefined as unknown as MessagePort, - config, - files, - invalidates, - environment: { name: "node", options: null }, - workerId: 0, - projectName: project.project.getName(), - providedContext: project.project.getProvidedContext(), - }; - - // Find the vitest-pool-workers:config plugin and give it the path to the main file. - // This allows that plugin to inject a virtual dependency on main so that vitest - // will automatically re-run tests when that gets updated, avoiding the user having - // to manually add such an import in their tests. - const configPlugin = project.project.server.config.plugins.find( - ({ name }) => name === "@cloudflare/vitest-pool-workers:config" - ); - if (configPlugin !== undefined) { - const api = configPlugin.api as WorkersConfigPluginAPI; - api.setMain(project.options.main); - } - - // We reset storage at the end of tests when the user is presumably looking at - // results. We don't need to reset storage on the first run as instances were - // just created. - await waitForStorageReset(mf); const ns = await mf.getDurableObjectNamespace( RUNNER_OBJECT_BINDING, workerName ); + // @ts-expect-error `ColoLocalActorNamespace`s are not included in types const stub = ns.get("singleton"); @@ -883,113 +678,22 @@ async function runTests( headers: { Upgrade: "websocket", "MF-Vitest-Worker-Data": structuredSerializableStringify({ - filePath: pathToFileURL(workerPath).href, - name: method, - data, cwd: process.cwd(), }), }, }); - const webSocket = res.webSocket; - assert(webSocket !== null); - - const chunkingSocket = createChunkingSocket({ - post(message) { - webSocket.send(message); - }, - on(listener) { - webSocket.addEventListener("message", (event) => { - listener(event.data); - }); - }, - }); - - // Compile module rules for matching against - const rules = project.options.miniflare?.modulesRules; - const compiledRules = compileModuleRules(rules ?? []); - - const localRpcFunctions = createMethodsRPC(project.project, { - cacheFs: false, - }); - const patchedLocalRpcFunctions: RuntimeRPC = { - ...localRpcFunctions, - async fetch(...args) { - const specifier = args[0]; - - // Mark built-in modules (e.g. `cloudflare:test-runner`) as external. - // Note we explicitly don't mark `cloudflare:test` as external here, as - // this is handled by a Vite plugin injected by `defineWorkersConfig()`. - // The virtual `cloudflare:test` module will define a dependency on the - // specific `main` entrypoint, ensuring tests reload when it changes. - // Note Vite's module graph is constructed using static analysis, so the - // dynamic import of `main` won't add an imported-by edge to the graph. - if ( - specifier !== "cloudflare:test" && - (/^(cloudflare|workerd):/.test(specifier) || - workerdBuiltinModules.has(specifier)) - ) { - return { externalize: specifier }; - } - // If the specifier matches any module rules, force it to be loaded as - // that type. This will be handled by the module fallback service. - const maybeRule = compiledRules.find((rule) => - testRegExps(rule.include, specifier) - ); - - // Skip if specifier already has query params (e.g. `?raw`), letting Vite handle it. - if (maybeRule !== undefined && !specifier.includes("?")) { - const externalize = specifier + `?mf_vitest_force=${maybeRule.type}`; - return { externalize }; - } - - return localRpcFunctions.fetch(...args); - }, - }; + const webSocket = res.webSocket; + if (webSocket === null) { + const body = await res.text().catch(() => ""); + throw new Error( + `Failed to establish WebSocket to runner (status ${res.status}): ${body}` + ); + } - let startupError: unknown; - const rpc = createBirpc(patchedLocalRpcFunctions, { - eventNames: ["onCancel"], - post(value) { - if (webSocket.readyState === WebSocket.READY_STATE_OPEN) { - debuglog("POOL-->WORKER", value); - chunkingSocket.post(structuredSerializableStringify(value)); - } else { - debuglog("POOL--* ", value); - } - }, - on(listener) { - chunkingSocket.on((message) => { - const value = structuredSerializableParse(message); - debuglog("POOL<--WORKER", value); - if ( - typeof value === "object" && - value !== null && - "vitestPoolWorkersError" in value - ) { - startupError = value.vitestPoolWorkersError; - } else { - listener(value); - } - }); - }, - }); - project.project.ctx.onCancel((reason) => rpc.onCancel(reason)); webSocket.accept(); - const [event] = (await events.once(webSocket, "close")) as [CloseEvent]; - if (webSocket.readyState === WebSocket.READY_STATE_CLOSING) { - if (event.code === 1005 /* No Status Received */) { - webSocket.close(); - } else { - webSocket.close(event.code, event.reason); - } - } - if (event.code !== 1000) { - throw startupError ?? new Error("Failed to run tests"); - } - - debuglog("DONE", files); + return webSocket; } interface PackageJson { @@ -1016,7 +720,7 @@ function getPackageJson(dirPath: string): PackageJson | undefined { } } -function assertCompatibleVitestVersion(ctx: Vitest) { +export function assertCompatibleVitestVersion(ctx: Vitest) { // Some package managers don't enforce `peerDependencies` requirements, // so add a runtime sanity check to ensure things don't break in strange ways. const poolPkgJson = getPackageJson(__dirname); @@ -1041,6 +745,12 @@ function assertCompatibleVitestVersion(ctx: Vitest) { "Expected to find `vitest`'s version" ); + // Hard error on Vitest v3, which definitely won't work + if (semverSatisfies(actualVitestVersion, "3.x")) { + const message = `You're running \`vitest@${actualVitestVersion}\`, but this version of \`@cloudflare/vitest-pool-workers\` only supports \`vitest ${expectedVitestVersion}\`.`; + throw new Error(message); + } + if (!semverSatisfies(actualVitestVersion, expectedVitestVersion)) { const message = [ `You're running \`vitest@${actualVitestVersion}\`, but this version of \`@cloudflare/vitest-pool-workers\` only officially supports \`vitest ${expectedVitestVersion}\`.`, @@ -1051,244 +761,6 @@ function assertCompatibleVitestVersion(ctx: Vitest) { } } -let warnedUnsupportedInspectorOptions = false; - -function validateInspectorConfig(config: SerializedConfig) { - if (config.inspector.host) { - throw new TypeError( - "Customizing inspector host is not supported with vitest-pool-workers." - ); - } - - if (config.inspector.enabled && !warnedUnsupportedInspectorOptions) { - if (config.inspectBrk) { - log.warn( - `The "--inspect-brk" flag is not supported. Use "--inspect" instead.` - ); - } else if (config.inspector.waitForDebugger) { - log.warn( - `The "inspector.waitForDebugger" option is not supported. Insert a debugger statement if you need to pause execution.` - ); - } - - warnedUnsupportedInspectorOptions = true; - } -} - -async function executeMethod( - ctx: Vitest, - specs: TestSpecification[], - invalidates: string[] | undefined, - method: "run" | "collect" -) { - // Vitest waits for the previous `runTests()` to complete before calling - // `runTests()` again: - // https://github.com/vitest-dev/vitest/blob/v1.0.4/packages/vitest/src/node/core.ts#L458-L459 - // This behaviour is required for stacked storage to work correctly. - // If we had concurrent runs, stack pushes/pops would interfere. We should - // always have an empty, fully-popped stacked at the end of a run. - - // 1. Collect new specs - const parsedProjectOptions = new Set(); - for (const [project, testFile] of specs) { - // Vitest validates all project names are unique - const projectName = project.getName(); - let workersProject = allProjects.get(projectName); - // Parse project options once per project per re-run - if (workersProject === undefined) { - workersProject = { - project, - options: await parseProjectOptions(project), - testFiles: new Set(), - relativePath: getRelativeProjectPath(project), - }; - allProjects.set(projectName, workersProject); - } else if (!parsedProjectOptions.has(project)) { - workersProject.project = project; - workersProject.options = await parseProjectOptions(project); - workersProject.relativePath = getRelativeProjectPath(project); - } - workersProject.testFiles.add(testFile); - - parsedProjectOptions.add(project); - } - - // 2. Run just the required tests - const resultPromises: Promise[] = []; - const filesByProject = new Map(); - for (const [project, file] of specs) { - let group = filesByProject.get(project); - if (group === undefined) { - filesByProject.set(project, (group = [])); - } - group.push(file); - } - for (const [workspaceProject, files] of filesByProject) { - const project = allProjects.get(workspaceProject.getName()); - assert(project !== undefined); // Defined earlier in this function - const options = project.options; - - const config = workspaceProject.getSerializableConfig(); - - // Use our custom test runner. We don't currently support custom - // runners, since we need our own for isolated storage/fetch mock resets - // to work properly. There aren't many use cases where a user would need - // to control this. - config.runner = "cloudflare:test-runner"; - - // Make sure `setImmediate` and `clearImmediate` are never faked as they - // don't exist on the workers global scope - config.fakeTimers.toFake = config.fakeTimers.toFake?.filter( - (timerMethod) => - timerMethod !== "setImmediate" && timerMethod !== "clearImmediate" - ); - - validateInspectorConfig(config); - - // We don't want it to call `node:inspector` inside Workerd - config.inspector = { - enabled: false, - }; - - // We don't need all pool options from the config at runtime. - // Additionally, users may set symbols in the config which aren't - // serialisable. `getSerializableConfig()` may also return references to - // the same objects, so override it with a new object. - config.poolOptions = { - // @ts-expect-error Vitest provides no way to extend this type - threads: { - // Allow workers to be re-used by removing the isolation requirement - isolate: false, - }, - workers: { - // Include resolved `main` if defined, and the names of Durable Object - // bindings that point to classes in the current isolate in the - // serialized config - main: maybeGetResolvedMainPath(project), - // Include designators of all Durable Object namespaces bound in the - // runner worker. We'll use this to list IDs in a namespace. We'll - // also use this to check Durable Object test runner helpers are - // only used with classes defined in the current worker, as these - // helpers rely on wrapping the object. - durableObjectBindingDesignators: getDurableObjectDesignators( - project.options - ), - // Include whether isolated storage has been enabled for this - // project, so we know whether to call out to the loopback service - // to push/pop the storage stack between tests. - isolatedStorage: project.options.isolatedStorage, - } satisfies SerializedOptions, - }; - - const mf = await getProjectMiniflare(ctx, project); - if (options.singleWorker) { - // Single Worker, Isolated or Shared Storage - // --> single instance with single runner worker - assert(mf instanceof Miniflare, "Expected single instance"); - const name = getRunnerName(workspaceProject); - resultPromises.push( - runTests(ctx, mf, name, project, config, files, invalidates, method) - ); - } else if (options.isolatedStorage) { - // Multiple Workers, Isolated Storage: - // --> multiple instances each with single runner worker - assert(mf instanceof Map, "Expected multiple isolated instances"); - const name = getRunnerName(workspaceProject); - for (const file of files) { - const fileMf = mf.get(file); - assert(fileMf !== undefined); - resultPromises.push( - runTests( - ctx, - fileMf, - name, - project, - config, - [file], - invalidates, - method - ) - ); - } - } else { - // Multiple Workers, Shared Storage: - // --> single instance with multiple runner workers - assert(mf instanceof Miniflare, "Expected single instance"); - for (const file of files) { - const name = getRunnerName(workspaceProject, file); - resultPromises.push( - runTests(ctx, mf, name, project, config, [file], invalidates, method) - ); - } - } - } - - // 3. Wait for all tests to complete, and throw if any failed - const results = await Promise.allSettled(resultPromises); - const errors = results - .filter((r): r is PromiseRejectedResult => r.status === "rejected") - .map((r) => r.reason); - - // 4. Clean up persistence directories. Note we do this in the background - // at the end of tests as opposed to before tests start, so re-runs - // start quickly, and results are displayed as soon as they're ready. - for (const project of allProjects.values()) { - if (project.mf !== undefined) { - void forEachMiniflare(project.mf, async (mf) => scheduleStorageReset(mf)); - } - } - - if (errors.length > 0) { - throw new AggregateError( - errors, - "Errors occurred while running tests. For more information, see serialized error." - ); - } - - // TODO(soon): something like this is required for watching non-statically imported deps, - // `vitest/dist/vendor/node.c-kzGvOB.js:handleFileChanged` is interesting, - // could also use `forceRerunTriggers` - // (Vite statically analyses imports here: https://github.com/vitejs/vite/blob/2649f40733bad131bc94b06d370bedc8f57853e2/packages/vite/src/node/plugins/importAnalysis.ts#L770) - // const project = specs[0][0]; - // const moduleGraph = project.server.moduleGraph; - // const testModule = moduleGraph.getModuleById(".../packages/vitest-pool-workers/test/kv/store.test.ts"); - // const thingModule = moduleGraph.getModuleById(".../packages/vitest-pool-workers/test/kv/thing.ts"); - // assert(testModule && thingModule); - // thingModule.importers.add(testModule); -} -export default function (ctx: Vitest): ProcessPool { - // This function is called when config changes and may be called on re-runs - assertCompatibleVitestVersion(ctx); - - return { - name: "vitest-pool-workers", - async runTests(specs, invalidates) { - await executeMethod(ctx, specs, invalidates, "run"); - }, - async collectTests(specs, invalidates) { - await executeMethod(ctx, specs, invalidates, "collect"); - }, - async close() { - // `close()` will be called when shutting down Vitest or updating config - log.debug("Shutting down runtimes..."); - const promises: Promise[] = []; - for (const project of allProjects.values()) { - if (project.mf !== undefined) { - promises.push( - forEachMiniflare(project.mf, async (mf) => { - // Finish in-progress storage resets before disposing - await waitForStorageReset(mf); - await mf.dispose(); - }) - ); - } - } - allProjects.clear(); - await Promise.all(promises); - }, - }; -} - /** * Ensures that the specified compatibility feature is enabled for Vitest to work. * @param compatibilityFlags The list of current compatibility flags. @@ -1310,3 +782,8 @@ function ensureFeature(compatibilityFlags: string[], feature: string) { compatibilityFlags.splice(compatibilityFlags.indexOf(flagToDisable), 1); } } + +export { cloudflarePool } from "./pool"; +export { cloudflareTest } from "./plugin"; +export * from "./d1"; +export * from "./pages"; diff --git a/packages/vitest-pool-workers/src/pool/loopback.ts b/packages/vitest-pool-workers/src/pool/loopback.ts index cc0d8042d1c9..5c380d574d9f 100644 --- a/packages/vitest-pool-workers/src/pool/loopback.ts +++ b/packages/vitest-pool-workers/src/pool/loopback.ts @@ -1,21 +1,11 @@ import assert from "node:assert"; -import { opendirSync, rmSync } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; -import { - CACHE_PLUGIN_NAME, - D1_PLUGIN_NAME, - DURABLE_OBJECTS_PLUGIN_NAME, - KV_PLUGIN_NAME, - Mutex, - R2_PLUGIN_NAME, - Response, - WORKFLOWS_PLUGIN_NAME, -} from "miniflare"; -import { isFileNotFoundError, WORKER_NAME_PREFIX } from "./helpers"; -import type { Awaitable, Miniflare, Request, WorkerOptions } from "miniflare"; +import { Response } from "miniflare"; +import { isFileNotFoundError } from "./helpers"; +import type { Awaitable, Miniflare, Request } from "miniflare"; -// Based on https://github.com/vitest-dev/vitest/blob/v1.0.0-beta.5/packages/snapshot/src/env/node.ts +// Based on https://github.com/vitest-dev/vitest/blob/v4.0.18/packages/snapshot/src/env/node.ts async function handleSnapshotRequest( request: Request, url: URL @@ -32,7 +22,7 @@ async function handleSnapshotRequest( if (request.method === "PUT" /* saveSnapshotFile */) { const snapshot = await request.arrayBuffer(); - await fs.mkdir(path.posix.dirname(filePath), { recursive: true }); + await fs.mkdir(path.dirname(filePath), { recursive: true }); await fs.writeFile(filePath, new Uint8Array(snapshot)); return new Response(null, { status: 204 }); } @@ -62,407 +52,7 @@ async function handleSnapshotRequest( return new Response(null, { status: 405 }); } -function emptyDir(dirPath: string) { - let dir; - try { - dir = opendirSync(dirPath); - } catch (e) { - if (isFileNotFoundError(e)) { - return; - } - throw e; - } - try { - let entry; - while ((entry = dir.readSync()) !== null) { - const fullPath = path.join(dirPath, entry.name); - - if (entry.isDirectory()) { - emptyDir(fullPath); - } else { - try { - rmSync(fullPath, { force: true }); - } catch (e) { - if (isEbusyError(e)) { - console.warn(`vitest-pool-worker: Unable to remove file: ${e}`); - } - } - } - } - } finally { - dir.closeSync(); - } -} - -function isEbusyError(e: unknown): boolean { - return e instanceof Error && "code" in e && e.code === "EBUSY"; -} - -/** - * ## Stacked/Isolated Storage System - * - * One of the features of `@cloudflare/vitest-pool-workers` is isolated per-test - * storage. If enabled, writes performed in a test are undone at the end of that - * test. Writes performed in `beforeAll()` hooks are not undone. As an example, - * this is the behaviour we're describing: - * - * ```js - * async function get() { return (await env.TEST_NAMESPACE.get("key")) ?? ""; } - * async function append(str) { await env.TEST_NAMESPACE.put("key", get() + str); } - * - * beforeAll(() => append("a")); - * beforeEach(() => append("b")); - * - * test("test 1", async () => { - * await append("c"); - * expect(await get()).toBe("abc"); - * }); - * test("test 2", async () => { - * await append("d"); - * expect(await get()).toBe("abd"); // append("c") undone - * }); - * - * describe("nested", () => { - * beforeAll(() => append("e")); - * beforeEach(() => append("f")); - * - * test("test 3", async () => { - * await append("g"); - * expect(await get()).toBe("aebfg"); // all `beforeAll()`s first - * }); - * test("test 4", async () => { - * await append("h"); - * expect(await get()).toBe("aebfh"); - * }); - * }); - * ``` - * - * Each `Miniflare` instance in the pool has its own directory for persistent - * state. If we wanted to update this mid-state, we'd need to restart the - * corresponding `workerd` instance, as the persistence path is encoded in a - * `diskDirectory` service. - * - * Instead, we implement this with an on-disk "stack" containing "backups" of - * the `.sqlite` files belonging to Miniflare's Durable Objects. Whenever Vitest - * starts a test attempt or enters a describe block, we "push" (copy) the - * current `.sqlite` files into the stack. Whenever Vitest finishes a test - * attempt or leaves a describe block, we "pop" (copy) the `.sqlite` files from - * the top of the stack to the persistence path. - * - * Notably, we don't copy the blobs referenced by the `.sqlite` databases. - * Instead, we enable Miniflare's "sticky" blobs feature which prevents blobs - * being garbage collected when they're no longer referenced. This means that if - * a user deletes or overwrites a value, the old value's blob will still be - * stored, meaning when we "pop" the stack, the blob references will be valid. - * At the end of a test run, we will empty the persistence directories, cleaning - * up all blobs. - */ -interface StackedStorageState { - // Only one stack operation per Miniflare instance may be in-progress at a - // given time - mutex: Mutex; - // Current size of the stack - depth: number; - // If we failed to push/pop stacks for any reason, mark the state as broken. - // In this case, any future operation will fail. - broken: boolean; - // All of our persistence paths will be using `Miniflare`'s temporary directory - // which is generated once when calling `new Miniflare()`. We never change any - // `*Persist` settings in `setOptions()` calls, so persistence paths for a given - // `Miniflare` instance will always be the same. - persistPaths: string[]; // (unique) - // We need this one specifically for listing Durable Object IDs. - durableObjectPersistPath: string; - // `Promise` that will resolve when the background persistence directory - // cleanup completes. We do this in the background at the end of tests as - // opposed to before tests start, so re-runs start quickly, and results are - // displayed as soon as they're ready. `waitForStorageReset(mf)` should be - // called before using `mf` for a new test run. - storageResetPromise?: Promise; -} -const stackStates = new WeakMap(); -function getState(mf: Miniflare) { - let state = stackStates.get(mf); - if (state === undefined) { - const persistPaths = mf.unsafeGetPersistPaths(); - const durableObjectPersistPath = persistPaths.get("do"); - assert( - durableObjectPersistPath !== undefined, - "Expected Durable Object persist path" - ); - state = { - mutex: new Mutex(), - depth: 0, - broken: false, - persistPaths: Array.from(new Set(persistPaths.values())), - durableObjectPersistPath, - }; - stackStates.set(mf, state); - } - return state; -} - -const ABORT_ALL_WORKER_NAME = `${WORKER_NAME_PREFIX}abort-all`; -// The `abortAllDurableObjects()` API is only accessible from a worker, so we -// add this extra worker to all `Miniflare` instances constructed by the pool, -// so we can this function from Node. -export const ABORT_ALL_WORKER: WorkerOptions = { - name: ABORT_ALL_WORKER_NAME, - compatibilityFlags: ["unsafe_module"], - modules: [ - { - type: "ESModule", - path: "index.mjs", - contents: ` - import workerdUnsafe from "workerd:unsafe"; - export default { - async fetch(request) { - if (request.method !== "DELETE") return new Response(null, { status: 405 }); - await workerdUnsafe.abortAllDurableObjects(); - return new Response(null, { status: 204 }); - } - }; - `, - }, - ], -}; -export function scheduleStorageReset(mf: Miniflare) { - const state = getState(mf); - assert(state.storageResetPromise === undefined); - state.storageResetPromise = state.mutex.runWith(async () => { - const abortAllWorker = await mf.getWorker(ABORT_ALL_WORKER_NAME); - await abortAllWorker.fetch("http://placeholder", { method: "DELETE" }); - for (const persistPath of state.persistPaths) { - // Clear directory rather than removing it so `workerd` can retain handle - emptyDir(persistPath); - } - state.depth = 0; - // If any of the code in this callback throws, the `storageResetPromise` - // won't be reset, and `await`ing it will throw the error. This is what we - // want, as failing to clean up means the persistence directory is in an - // invalid state. - state.storageResetPromise = undefined; - }); -} -export async function waitForStorageReset(mf: Miniflare): Promise { - await getState(mf).storageResetPromise; -} - -const BLOBS_DIR_NAME = "blobs"; -const STACK_DIR_NAME = "__vitest_pool_workers_stack"; -async function pushStackedStorage(intoDepth: number, persistPath: string) { - // Create directory for new stack frame - const stackFramePath = path.join( - persistPath, - STACK_DIR_NAME, - intoDepth.toString() - ); - await fs.mkdir(stackFramePath, { recursive: true }); - - // For each Durable Object unique key in the persistence path... - for (const key of await fs.readdir(persistPath, { withFileTypes: true })) { - // (skipping stack directory) - if (key.name === STACK_DIR_NAME) { - continue; - } - const keyPath = path.join(persistPath, key.name); - const stackFrameKeyPath = path.join(stackFramePath, key.name); - assert(key.isDirectory(), `Expected ${keyPath} to be a directory`); - // ...copy all `.sqlite` files to the stack frame - let createdStackFrameKeyPath = false; - for (const name of await fs.readdir(keyPath)) { - // If this is a blobs directory, it shouldn't contain any `.sqlite` files - if (name === BLOBS_DIR_NAME) { - break; - } - if (!createdStackFrameKeyPath) { - createdStackFrameKeyPath = true; - await fs.mkdir(stackFrameKeyPath); - } - const namePath = path.join(keyPath, name); - const stackFrameNamePath = path.join(stackFrameKeyPath, name); - assert(name.endsWith(".sqlite"), `Expected .sqlite, got ${namePath}`); - await fs.copyFile(namePath, stackFrameNamePath); - } - } -} -async function popStackedStorage(fromDepth: number, persistPath: string) { - // Delete every Durable Object unique key directory in the persistence path - for (const key of await fs.readdir(persistPath, { withFileTypes: true })) { - // (skipping stack directory) - if (key.name === STACK_DIR_NAME) { - continue; - } - const keyPath = path.join(persistPath, key.name); - for (const name of await fs.readdir(keyPath)) { - // If this is a blobs directory, it shouldn't contain any `.sqlite` files - if (name === BLOBS_DIR_NAME) { - break; - } - const namePath = path.join(keyPath, name); - assert(name.endsWith(".sqlite"), `Expected .sqlite, got ${namePath}`); - - await fs.unlink(namePath); - } - } - - // Copy the stack frame into the persistent path - const stackFramePath = path.join( - persistPath, - STACK_DIR_NAME, - fromDepth.toString() - ); - await fs.cp(stackFramePath, persistPath, { recursive: true }); - - // Remove the stack frame. - // - // Note: this is intentionally inlined rather than importing `removeDir` from - // `@cloudflare/workers-utils`. That package's barrel export pulls in CJS - // dependencies (e.g. `xdg-app-paths`) that break when esbuild bundles them - // into our ESM output — the shimmed `require()` calls throw - // "Dynamic require of 'path' is not supported" at runtime. - // If the bundling setup for this package changes in the future (e.g. - // tree-shaking improves or a sub-path export is added), this could be - // replaced with a direct import from `@cloudflare/workers-utils`. - // Keep aligned with `removeDir()` in `packages/workers-utils/src/fs-helpers.ts`. - // eslint-disable-next-line workers-sdk/no-direct-recursive-rm -- see comment above: barrel import breaks ESM bundle - await fs.rm(stackFramePath, { - recursive: true, - force: true, - maxRetries: 5, - retryDelay: 100, - }); -} - -const PLUGIN_PRODUCT_NAMES: Record = { - [CACHE_PLUGIN_NAME]: "Cache", - [D1_PLUGIN_NAME]: "D1", - [DURABLE_OBJECTS_PLUGIN_NAME]: "Durable Objects", - [KV_PLUGIN_NAME]: "KV", - [R2_PLUGIN_NAME]: "R2", - [WORKFLOWS_PLUGIN_NAME]: "Workflows", -}; -const LIST_FORMAT = new Intl.ListFormat("en-US"); - -function checkAllStorageOperationsResolved( - action: "push" | "pop", - source: string, - persistPaths: string[], - results: PromiseSettledResult[] -): boolean { - const failedProducts: string[] = []; - const lines: string[] = []; - for (let i = 0; i < results.length; i++) { - const result = results[i]; - if (result.status === "rejected") { - const pluginName = path.basename(persistPaths[i]); - const productName = PLUGIN_PRODUCT_NAMES[pluginName] ?? pluginName; - failedProducts.push(productName); - lines.push(`- ${result.reason}`); - } - } - if (failedProducts.length > 0) { - const separator = "=".repeat(80); - lines.unshift( - "", - separator, - `Failed to ${action} isolated storage stack frame in ${source}.`, - `In particular, we were unable to ${action} ${LIST_FORMAT.format(failedProducts)} storage.`, - "This usually means your Worker tried to access storage outside of a test, or some resources have not been disposed of properly.", - `Ensure you "await" all Promises that read or write to these services, and make sure you use the "using" keyword when passing data across JSRPC.`, - `See https://developers.cloudflare.com/workers/testing/vitest-integration/known-issues/#isolated-storage for more details.`, - "\x1b[2m" - ); - lines.push("\x1b[22m" + separator, ""); - - if ( - failedProducts.includes( - PLUGIN_PRODUCT_NAMES[WORKFLOWS_PLUGIN_NAME] ?? WORKFLOWS_PLUGIN_NAME - ) - ) { - console.warn( - [ - "", - separator, - `Workflows are being created in ${source}.`, - "Even with isolated storage, Workflows are required to be manually disposed at the end of each test.", - "See https://developers.cloudflare.com/workers/testing/vitest-integration/test-apis/ for more details.", - "", - ].join("\n") - ); - } - console.error(lines.join("\n")); - return false; - } - return true; -} - -async function handleStorageRequest( - request: Request, - mf: Miniflare -): Promise { - const state = getState(mf); - if (state.broken) { - return new Response( - "Isolated storage failed. There should be additional logs above.", - { status: 500 } - ); - } - - // Assuming all Durable Objects have been aborted at this point, so we can - // copy/delete `.sqlite` files as required - - const source = - request.headers.get("MF-Vitest-Source") ?? "an unknown location"; - - let success: boolean; - if (request.method === "POST" /* push */) { - success = await state.mutex.runWith(async () => { - state.depth++; - const results = await Promise.allSettled( - state.persistPaths.map((persistPath) => - pushStackedStorage(state.depth, persistPath) - ) - ); - return checkAllStorageOperationsResolved( - "push", - source, - state.persistPaths, - results - ); - }); - } else if (request.method === "DELETE" /* pop */) { - success = await state.mutex.runWith(async () => { - assert(state.depth > 0, "Stack underflow"); - const results = await Promise.allSettled( - state.persistPaths.map((persistPath) => - popStackedStorage(state.depth, persistPath) - ) - ); - state.depth--; - return checkAllStorageOperationsResolved( - "pop", - source, - state.persistPaths, - results - ); - }); - } else { - return new Response(null, { status: 405 }); - } - - if (success) { - return new Response(null, { status: 204 }); - } else { - state.broken = true; - return new Response( - "Isolated storage failed. There should be additional logs above.", - { status: 500 } - ); - } -} - -export async function handleDurableObjectsRequest( +export async function listDurableObjectIds( request: Request, mf: Miniflare, url: URL @@ -470,7 +60,13 @@ export async function handleDurableObjectsRequest( if (request.method !== "GET") { return new Response(null, { status: 405 }); } - const { durableObjectPersistPath } = getState(mf); + const persistPaths = mf.unsafeGetPersistPaths(); + const durableObjectPersistPath = persistPaths.get("do"); + assert( + durableObjectPersistPath !== undefined, + "Expected Durable Object persist path" + ); + const uniqueKey = url.searchParams.get("unique_key"); if (uniqueKey === null) { return new Response(null, { status: 400 }); @@ -501,11 +97,8 @@ export function handleLoopbackRequest( if (url.pathname === "/snapshot") { return handleSnapshotRequest(request, url); } - if (url.pathname === "/storage") { - return handleStorageRequest(request, mf); - } if (url.pathname === "/durable-objects") { - return handleDurableObjectsRequest(request, mf, url); + return listDurableObjectIds(request, mf, url); } return new Response(null, { status: 404 }); } diff --git a/packages/vitest-pool-workers/src/pool/module-fallback.ts b/packages/vitest-pool-workers/src/pool/module-fallback.ts index ae7faa831c8d..fc8dc242ec74 100644 --- a/packages/vitest-pool-workers/src/pool/module-fallback.ts +++ b/packages/vitest-pool-workers/src/pool/module-fallback.ts @@ -10,7 +10,7 @@ import { ModuleRuleTypeSchema, Response } from "miniflare"; import { workerdBuiltinModules } from "../shared/builtin-modules"; import { isFileNotFoundError } from "./helpers"; import type { ModuleRuleType, Request, Worker_Module } from "miniflare"; -import type { ViteDevServer } from "vite"; +import type { Vite } from "vitest/node"; let debuglog: util.DebugLoggerFunction = util.debuglog( "vitest-pool-workers:module-fallback", @@ -131,7 +131,7 @@ await cjsModuleLexer.init(); * using the same package as Node. */ async function getCjsNamedExports( - vite: ViteDevServer, + vite: Vite.ViteDevServer, filePath: string, contents: string, seen = new Set() @@ -150,14 +150,13 @@ async function getCjsNamedExports( } try { const resolvedContents = fs.readFileSync(resolved, "utf8"); - seen.add(filePath); + seen.add(resolved); const resolvedNames = await getCjsNamedExports( vite, resolved, resolvedContents, seen ); - seen.delete(filePath); for (const name of resolvedNames) { result.add(name); } @@ -240,7 +239,7 @@ function getApproximateSpecifier(target: string, referrerDir: string): string { } async function viteResolve( - vite: ViteDevServer, + vite: Vite.ViteDevServer, specifier: string, referrer: string, isRequire: boolean @@ -298,7 +297,7 @@ async function viteResolve( type ResolveMethod = "import" | "require"; async function resolve( - vite: ViteDevServer, + vite: Vite.ViteDevServer, method: ResolveMethod, target: string, specifier: string, @@ -403,7 +402,7 @@ function buildModuleResponse(target: string, contents: ModuleContents) { } async function load( - vite: ViteDevServer, + vite: Vite.ViteDevServer, logBase: string, method: ResolveMethod, target: string, @@ -494,7 +493,7 @@ async function load( } export async function handleModuleFallbackRequest( - vite: ViteDevServer, + vite: Vite.ViteDevServer, request: Request ): Promise { const method = request.headers.get("X-Resolve-Method"); diff --git a/packages/vitest-pool-workers/src/pool/pages.ts b/packages/vitest-pool-workers/src/pool/pages.ts new file mode 100644 index 000000000000..56b41df8acf8 --- /dev/null +++ b/packages/vitest-pool-workers/src/pool/pages.ts @@ -0,0 +1,59 @@ +import type { Request, Response } from "miniflare"; +import type { Unstable_ASSETSBindingsOptions } from "wrangler"; + +// Track all AbortControllers created by buildPagesASSETSBinding so they can +// be cleaned up when the last pool worker stops. This is necessary because +// vitest evaluates all project configs at startup (to discover the workspace), +// even for projects that won't run — so the watchers are created before any +// pool worker exists, and the creating project's pool worker may never start. +const registeredControllers = new Set(); + +// Reference count of active pool workers. Watchers are only closed when the +// last worker stops, so that early-finishing workers don't kill watchers that +// later workers (or other projects in the workspace) still need. +let activeWorkers = 0; + +export function poolWorkerStarted(): void { + activeWorkers++; +} + +export function poolWorkerStopped(): void { + activeWorkers--; + if (activeWorkers <= 0) { + activeWorkers = 0; + for (const ac of registeredControllers) { + ac.abort(); + } + registeredControllers.clear(); + } +} + +export async function buildPagesASSETSBinding( + assetsPath: string +): Promise<(request: Request) => Promise> { + // noinspection SuspiciousTypeOfGuard + if (typeof assetsPath !== "string") { + throw new TypeError( + "Failed to execute 'buildPagesASSETSBinding': parameter 1 is not of type 'string'." + ); + } + + const { unstable_generateASSETSBinding } = await import("wrangler"); // (lazy) + + // Create the AbortController after the import succeeds so we don't leak + // a registered controller if the import throws. + const ac = new AbortController(); + registeredControllers.add(ac); + + const log = { + ...console, + debugWithSanitization: console.debug, + loggerLevel: "info", + columns: process.stdout.columns, + } as unknown as Unstable_ASSETSBindingsOptions["log"]; + return unstable_generateASSETSBinding({ + log, + directory: assetsPath, + signal: ac.signal, + }); +} diff --git a/packages/vitest-pool-workers/src/pool/plugin.ts b/packages/vitest-pool-workers/src/pool/plugin.ts new file mode 100644 index 000000000000..ef120d6bebba --- /dev/null +++ b/packages/vitest-pool-workers/src/pool/plugin.ts @@ -0,0 +1,122 @@ +import crypto from "node:crypto"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { cloudflarePool } from "./pool"; +import type { WorkersPoolOptions } from "./config"; +import type { inject } from "vitest"; +import type { Vite, VitestPluginContext } from "vitest/node"; + +const cloudflareTestPath = path.resolve( + import.meta.dirname, + "../worker/lib/cloudflare/test.mjs" +); + +export interface WorkerPoolOptionsContext { + // For accessing values from `globalSetup()` (e.g. ports servers started on) + // in Miniflare options (e.g. bindings, upstream, hyperdrives, ...) + inject: typeof inject; +} + +function ensureArrayIncludes(array: T[], items: T[]) { + for (const item of items) { + if (!array.includes(item)) { + array.push(item); + } + } +} + +function ensureArrayExcludes(array: T[], items: T[]) { + for (let i = 0; i < array.length; i++) { + if (items.includes(array[i])) { + array.splice(i, 1); + i--; + } + } +} + +const requiredConditions = ["workerd", "worker", "module", "browser"]; +const requiredMainFields = ["browser", "module", "jsnext:main", "jsnext"]; + +export function cloudflareTest( + options: + | WorkersPoolOptions + | (( + ctx: WorkerPoolOptionsContext + ) => Promise | WorkersPoolOptions) +): Vite.Plugin { + // Use a unique ID for each `cloudflare:test` module so updates in one `main` + // don't trigger re-runs in all other projects, just the one that changed. + const uuid = crypto.randomUUID(); + let main: string | undefined; + return { + name: "@cloudflare/vitest-pool-workers", + api: { + setMain(newMain: string) { + main = newMain; + }, + }, + configureVitest(context: VitestPluginContext) { + context.project.config.poolRunner = cloudflarePool(options); + context.project.config.pool = "cloudflare-pool"; + context.project.config.snapshotEnvironment = "cloudflare:snapshot"; + }, + // Run after `vitest:project` plugin: + // https://github.com/vitest-dev/vitest/blob/v4.0.18/packages/vitest/src/node/plugins/workspace.ts#L122 + config(config) { + config.resolve ??= {}; + config.resolve.conditions ??= []; + config.resolve.mainFields ??= []; + config.ssr ??= {}; + + config.test ??= {}; + config.test.server ??= {}; + config.test.server.deps ??= {}; + // See https://vitest.dev/config/server.html#inline + // Without this Vitest delegates to native import() for external deps in node_modules + config.test.server.deps.inline = true; + + // Remove "node" condition added by the `vitest:project` plugin. We're + // running tests inside `workerd`, not Node.js, so "node" isn't needed. + ensureArrayExcludes(config.resolve.conditions, ["node"]); + + // Use the same resolve conditions as `wrangler`, minus "import" as this + // breaks Vite's `require()` resolve + ensureArrayIncludes(config.resolve.conditions, requiredConditions); + + // Vitest sets this to an empty array if unset, so restore Vite defaults: + // https://github.com/vitest-dev/vitest/blob/v4.0.18/packages/vitest/src/node/plugins/utils.ts#L121 + ensureArrayIncludes(config.resolve.mainFields, requiredMainFields); + + // Apply `package.json` `browser` field remapping in SSR mode: + // https://github.com/vitejs/vite/blob/v5.1.4/packages/vite/src/node/plugins/resolve.ts#L175 + config.ssr.target = "webworker"; + }, + resolveId(id) { + if (id === "cloudflare:test") { + return `\0cloudflare:test-${uuid}`; + } + }, + async load(id) { + if (id === `\0cloudflare:test-${uuid}`) { + let contents = await fs.readFile(cloudflareTestPath, "utf8"); + + if (main !== undefined) { + // Inject a side-effect only import of the main entry-point into the test so that Vitest + // knows to re-run tests when the Worker is modified. + contents += `import ${JSON.stringify(main)};`; + } + return contents; + } + if (id.endsWith("msw/lib/node/index.mjs")) { + // HACK: This is a temporary solution while MSW works on some changes to better support the Workers + // environment. In the meantime, this replaces the `msw/node` entrypoint with the `msw/native` + // entrypoint (which is designed for React Native and does work in Workers). Users can't use + // `msw/native` themselves directly as the export conditions are not compatible with the Vitest Pool + // export conditions. + // + // This is tracked by https://github.com/mswjs/msw/issues/2637 + return `export * from "../native/index.mjs"`; + } + }, + }; +} diff --git a/packages/vitest-pool-workers/src/pool/pool.ts b/packages/vitest-pool-workers/src/pool/pool.ts new file mode 100644 index 000000000000..b9dbdc310c9e --- /dev/null +++ b/packages/vitest-pool-workers/src/pool/pool.ts @@ -0,0 +1,18 @@ +import { CloudflarePoolWorker } from "./cloudflare-pool-worker"; +import type { WorkersPoolOptions } from "./config"; +import type { WorkerPoolOptionsContext } from "./plugin"; +import type { PoolRunnerInitializer } from "vitest/node"; + +export function cloudflarePool( + poolOptions: + | WorkersPoolOptions + | (( + ctx: WorkerPoolOptionsContext + ) => Promise | WorkersPoolOptions) +): PoolRunnerInitializer { + return { + name: "cloudflare-pool", + createPoolWorker: (options) => + new CloudflarePoolWorker(options, poolOptions), + }; +} diff --git a/packages/vitest-pool-workers/src/shared/chunking-socket.ts b/packages/vitest-pool-workers/src/shared/chunking-socket.ts deleted file mode 100644 index a07dd528669f..000000000000 --- a/packages/vitest-pool-workers/src/shared/chunking-socket.ts +++ /dev/null @@ -1,60 +0,0 @@ -import assert from "node:assert"; -import { Buffer } from "node:buffer"; - -export interface SocketLike { - post(message: Message): void; - on(listener: (message: Message) => void): void; -} - -/** - * Wraps a binary/string socket to produce a chunked-string socket. If a string - * exceeds the maximum size for a message, it is split into binary chunks - * followed by an empty string. If not, it is sent directly as a string. - * This assumes in-order delivery of chunks. - */ -export function createChunkingSocket( - socket: SocketLike | string>, - maxChunkByteLength = 1_048_576 /* 1 MiB */ -): SocketLike { - const listeners: ((message: string) => void)[] = []; - - const decoder = new TextDecoder(); - let chunks: string | undefined; - socket.on((message) => { - if (typeof message === "string") { - if (chunks !== undefined) { - // If we've been collecting chunks, this must be the end-of-chunks - // marker, so use all chunks as the message instead - assert.strictEqual(message, "", "Expected end-of-chunks"); - message = chunks + decoder.decode(); - chunks = undefined; - } - for (const listener of listeners) { - listener(message); - } - } else { - // If this isn't a `string` message, it must be a chunk - chunks ??= ""; - chunks += decoder.decode(message, { stream: true }); - } - }); - - return { - post(value) { - if (Buffer.byteLength(value) > maxChunkByteLength) { - // If the message is greater than the size limit, chunk it - const encoded = Buffer.from(value); - for (let i = 0; i < encoded.byteLength; i += maxChunkByteLength) { - socket.post(encoded.subarray(i, i + maxChunkByteLength)); - } - socket.post(""); - } else { - // Otherwise, just send it as a string - socket.post(value); - } - }, - on(listener) { - listeners.push(listener); - }, - }; -} diff --git a/packages/vitest-pool-workers/src/worker/durable-objects.ts b/packages/vitest-pool-workers/src/worker/durable-objects.ts index 8868bbe5bbab..9b83545c7191 100644 --- a/packages/vitest-pool-workers/src/worker/durable-objects.ts +++ b/packages/vitest-pool-workers/src/worker/durable-objects.ts @@ -1,6 +1,6 @@ import assert from "node:assert"; -import { exports } from "cloudflare:workers"; -import { getSerializedOptions, internalEnv } from "./env"; +import { env, exports } from "cloudflare:workers"; +import { getSerializedOptions } from "./env"; import type { __VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__ } from "./index"; const CF_KEY_ACTION = "vitestPoolWorkersDurableObjectAction"; @@ -60,8 +60,7 @@ function getSameIsolateNamespaces(): DurableObjectNamespace[] { continue; } - const namespace = - internalEnv[key] ?? (exports as Record)?.[key]; + const namespace = env[key] ?? (exports as Record)?.[key]; assert( isDurableObjectNamespace(namespace), `Expected ${key} to be a DurableObjectNamespace binding` @@ -103,10 +102,12 @@ async function runInStub( const response = await stub.fetch("http://x", { cf: { [CF_KEY_ACTION]: id }, }); + // `result` may be `undefined` assert(actionResults.has(id), `Expected action result for ${id}`); const result = actionResults.get(id); actionResults.delete(id); + if (result === kUseResponse) { return response as R; } else if (response.ok) { @@ -156,7 +157,7 @@ export async function runDurableObjectAlarm( "Failed to execute 'runDurableObjectAlarm': parameter 1 is not of type 'DurableObjectStub'." ); } - return runInDurableObject(stub, runAlarm); + return await runInDurableObject(stub, runAlarm); } /** @@ -172,12 +173,16 @@ export async function runDurableObjectAlarm( * behalf of a different request` error. */ export function runInRunnerObject( - env: Env, callback: ( instance: __VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__ ) => R | Promise ): Promise { - const stub = env.__VITEST_POOL_WORKERS_RUNNER_OBJECT.get("singleton"); + // Runner DO is ephemeral (ColoLocalActorNamespace), which has .get(name) + // instead of the standard idFromName()/get(id) API. + const ns = env["__VITEST_POOL_WORKERS_RUNNER_OBJECT"] as unknown as { + get(name: string): Fetcher; + }; + const stub = ns.get("singleton"); return runInStub(stub, callback); } @@ -190,6 +195,7 @@ export async function maybeHandleRunRequest( if (actionId === undefined) { return; } + assert(typeof actionId === "number", `Expected numeric ${CF_KEY_ACTION}`); try { const callback = actionResults.get(actionId); @@ -229,7 +235,7 @@ export async function listDurableObjectIds( // We can use this to find the bound name for this binding. We inject a // mapping between bound names and unique keys for namespaces. We then use // this to get a unique key and find all IDs on disk. - const boundName = Object.entries(internalEnv).find( + const boundName = Object.entries(env).find( (entry) => namespace === entry[1] )?.[0]; assert(boundName !== undefined, "Expected to find bound name for namespace"); @@ -240,8 +246,7 @@ export async function listDurableObjectIds( let uniqueKey = designator.unsafeUniqueKey; if (uniqueKey === undefined) { - const scriptName = - designator.scriptName ?? internalEnv.__VITEST_POOL_WORKERS_SELF_NAME; + const scriptName = designator.scriptName ?? options.selfName; const className = designator.className; uniqueKey = `${scriptName}-${className}`; } @@ -249,8 +254,7 @@ export async function listDurableObjectIds( const url = `http://placeholder/durable-objects?unique_key=${encodeURIComponent( uniqueKey )}`; - const res = - await internalEnv.__VITEST_POOL_WORKERS_LOOPBACK_SERVICE.fetch(url); + const res = await env.__VITEST_POOL_WORKERS_LOOPBACK_SERVICE.fetch(url); assert.strictEqual(res.status, 200); const ids = await res.json(); assert(Array.isArray(ids)); diff --git a/packages/vitest-pool-workers/src/worker/entrypoints.ts b/packages/vitest-pool-workers/src/worker/entrypoints.ts index 72416445f42d..cd38eb44aaa6 100644 --- a/packages/vitest-pool-workers/src/worker/entrypoints.ts +++ b/packages/vitest-pool-workers/src/worker/entrypoints.ts @@ -5,7 +5,7 @@ import { WorkflowEntrypoint, } from "cloudflare:workers"; import { maybeHandleRunRequest, runInRunnerObject } from "./durable-objects"; -import { getResolvedMainPath, stripInternalEnv } from "./env"; +import { getResolvedMainPath } from "./env"; import { patchAndRunWithHandlerContext } from "./patch-ctx"; // ============================================================================= @@ -17,19 +17,16 @@ import { patchAndRunWithHandlerContext } from "./patch-ctx"; * execution pipeline. Can be called from any I/O context, and will ensure the * request is run from within the `__VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__`. */ -function importModule( - env: Env, +async function importModule( specifier: string ): Promise> { - return runInRunnerObject(env, (instance) => { - if (instance.executor === undefined) { - const message = - "Expected Vitest to start running before importing modules.\n" + - "This usually means you have multiple `vitest` versions installed.\n" + - "Use your package manager's `why` command to list versions and why each is installed (e.g. `npm why vitest`)."; - throw new Error(message); - } - return instance.executor.executeId(specifier); + /** + * We need to run this import inside the Runner Object, or we get errors like: + * - The Workers runtime canceled this request because it detected that your Worker's code had hung and would never generate a response. Refer to: https://developers.cloudflare.com/workers/observability/errors/ + * - Cannot perform I/O on behalf of a different Durable Object. I/O objects (such as streams, request/response bodies, and others) created in the context of one Durable Object cannot be accessed from a different Durable Object in the same isolate. This is a limitation of Cloudflare Workers which allows us to improve overall performance. + */ + return runInRunnerObject(() => { + return __vitest_mocker__.moduleRunner.import(specifier); }); } @@ -88,7 +85,9 @@ function createProxyPrototypeClass< */ function getRPCProperty( ctor: WorkerEntrypointConstructor | DurableObjectConstructor, - instance: WorkerEntrypoint | DurableObjectClass, + instance: + | WorkerEntrypoint | Cloudflare.Env> + | DurableObjectClass | Cloudflare.Env>, key: string ): unknown { const prototypeHasKey = Reflect.has(ctor.prototype, key); @@ -156,22 +155,22 @@ function getRPCPropertyCallableThenable( * Instead, we define this function to extract these members, and provide type * safety for callers. */ -function getEntrypointState(instance: WorkerEntrypoint): { +function getEntrypointState(instance: WorkerEntrypoint): { ctx: ExecutionContext; - env: InternalUserEnv; + env: Cloudflare.Env; }; -function getEntrypointState(instance: DurableObjectClass): { +function getEntrypointState(instance: DurableObjectClass): { ctx: DurableObjectState; - env: InternalUserEnv; + env: Cloudflare.Env; }; function getEntrypointState( instance: - | WorkerEntrypoint - | DurableObjectClass + | WorkerEntrypoint + | DurableObjectClass ) { return instance as unknown as { ctx: ExecutionContext | DurableObjectState; - env: InternalUserEnv; + env: Cloudflare.Env; }; } @@ -183,7 +182,6 @@ const WORKER_ENTRYPOINT_KEYS = [ "scheduled", "queue", "test", - "tailStream", "email", ] as const; const DURABLE_OBJECT_KEYS = [ @@ -200,10 +198,10 @@ type UnbrandedKeys = Exclude; // Check that we've included all possible keys // noinspection JSUnusedLocalSymbols const _workerEntrypointExhaustive: (typeof WORKER_ENTRYPOINT_KEYS)[number] = - undefined as unknown as UnbrandedKeys>; + undefined as unknown as UnbrandedKeys>; // noinspection JSUnusedLocalSymbols const _durableObjectExhaustive: (typeof DURABLE_OBJECT_KEYS)[number] = - undefined as unknown as UnbrandedKeys>; + undefined as unknown as UnbrandedKeys>; // ============================================================================= // `WorkerEntrypoint` wrappers @@ -222,11 +220,11 @@ type WorkerEntrypointConstructor = { * This requires importing the `main` module with Vite. */ async function getWorkerEntrypointExport( - env: Env, + env: Cloudflare.Env, entrypoint: string ): Promise<{ mainPath: string; entrypointValue: unknown }> { const mainPath = getResolvedMainPath("service"); - const mainModule = await importModule(env, mainPath); + const mainModule = await importModule(mainPath); const entrypointValue = typeof mainModule === "object" && mainModule !== null && @@ -248,7 +246,7 @@ async function getWorkerEntrypointExport( * with Vite, so will always return a `Promise.` */ async function getWorkerEntrypointRPCProperty( - wrapper: WorkerEntrypoint, + wrapper: WorkerEntrypoint, entrypoint: string, key: string ): Promise { @@ -257,7 +255,6 @@ async function getWorkerEntrypointRPCProperty( env, entrypoint ); - const userEnv = stripInternalEnv(env); // Ensure constructor and properties execute with ctx `AsyncLocalStorage` set return patchAndRunWithHandlerContext(ctx, () => { const expectedWorkerEntrypointMessage = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkerEntrypoint\` for RPC`; @@ -265,7 +262,7 @@ async function getWorkerEntrypointRPCProperty( throw new TypeError(expectedWorkerEntrypointMessage); } const ctor = entrypointValue as WorkerEntrypointConstructor; - const instance = new ctor(ctx, userEnv); + const instance = new ctor(ctx, env); // noinspection SuspiciousTypeOfGuard if (!(instance instanceof WorkerEntrypoint)) { throw new TypeError(expectedWorkerEntrypointMessage); @@ -288,7 +285,7 @@ export function createWorkerEntrypointWrapper( ): typeof WorkerEntrypoint { const Wrapper = createProxyPrototypeClass( WorkerEntrypoint, - function (this: WorkerEntrypoint, key) { + function (this: WorkerEntrypoint, key) { // All `ExportedHandler` keys are reserved and cannot be called over RPC if ((DURABLE_OBJECT_KEYS as readonly string[]).includes(key)) { return; @@ -300,23 +297,22 @@ export function createWorkerEntrypointWrapper( ); // Add prototype methods for all default handlers - // const prototype = Entrypoint.prototype as unknown as Record; for (const key of WORKER_ENTRYPOINT_KEYS) { Wrapper.prototype[key] = async function ( - this: WorkerEntrypoint, + this: WorkerEntrypoint, thing: unknown ) { const { mainPath, entrypointValue } = await getWorkerEntrypointExport( this.env, entrypoint ); - const userEnv = stripInternalEnv(this.env); + return patchAndRunWithHandlerContext(this.ctx, () => { if (typeof entrypointValue === "object" && entrypointValue !== null) { // Assuming the user has defined an `ExportedHandler` const maybeFn = (entrypointValue as Record)[key]; if (typeof maybeFn === "function") { - return maybeFn.call(entrypointValue, thing, userEnv, this.ctx); + return maybeFn.call(entrypointValue, thing, this.env, this.ctx); } else { const message = `Expected ${entrypoint} export of ${mainPath} to define a \`${key}()\` function`; throw new TypeError(message); @@ -324,7 +320,7 @@ export function createWorkerEntrypointWrapper( } else if (typeof entrypointValue === "function") { // Assuming the user has defined a `WorkerEntrypoint` subclass const ctor = entrypointValue as WorkerEntrypointConstructor; - const instance = new ctor(this.ctx, userEnv); + const instance = new ctor(this.ctx, this.env); // noinspection SuspiciousTypeOfGuard if (!(instance instanceof WorkerEntrypoint)) { const message = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkerEntrypoint\``; @@ -339,13 +335,12 @@ export function createWorkerEntrypointWrapper( } } else { // Assuming the user has messed up - const message = `Expected ${entrypoint} export of ${mainPath}to be an object or a class, got ${entrypointValue}`; + const message = `Expected ${entrypoint} export of ${mainPath} to be an object or a class, got ${entrypointValue}`; throw new TypeError(message); } }); }; } - return Wrapper; } @@ -356,7 +351,7 @@ export function createWorkerEntrypointWrapper( type DurableObjectConstructor = { new ( ...args: ConstructorParameters - ): DurableObject | DurableObjectClass>; + ): DurableObject | DurableObjectClass; }; const kInstanceConstructor = Symbol("kInstanceConstructor"); @@ -364,14 +359,18 @@ const kInstance = Symbol("kInstance"); const kEnsureInstance = Symbol("kEnsureInstance"); type DurableObjectWrapperExtraPrototype = { [kInstanceConstructor]: DurableObjectConstructor; - [kInstance]: DurableObject | DurableObjectClass>; + [kInstance]: + | DurableObject + | DurableObjectClass | Cloudflare.Env>; [kEnsureInstance](): Promise<{ mainPath: string; instanceCtor: DurableObjectConstructor; - instance: DurableObject | DurableObjectClass>; + instance: + | DurableObject + | DurableObjectClass | Cloudflare.Env>; }>; }; -type DurableObjectWrapper = DurableObjectClass & +type DurableObjectWrapper = DurableObjectClass & DurableObjectWrapperExtraPrototype; async function getDurableObjectRPCProperty( @@ -416,7 +415,7 @@ export function createDurableObjectWrapper( const mainPath = getResolvedMainPath("Durable Object"); // `ensureInstance()` may be called multiple times concurrently. // We're assuming `importModule()` will only import the module once. - const mainModule = await importModule(env, mainPath); + const mainModule = await importModule(mainPath); const constructor = mainModule[className]; if (typeof constructor !== "function") { throw new TypeError( @@ -440,8 +439,7 @@ export function createDurableObjectWrapper( assert.fail("Unreachable"); } if (this[kInstance] === undefined) { - const userEnv = stripInternalEnv(env); - this[kInstance] = new this[kInstanceConstructor](ctx, userEnv); + this[kInstance] = new this[kInstanceConstructor](ctx, env); // Wait for any `blockConcurrencyWhile()`s in the constructor to complete await ctx.blockConcurrencyWhile(async () => {}); } @@ -512,14 +510,14 @@ type WorkflowEntrypointConstructor = { export function createWorkflowEntrypointWrapper(entrypoint: string) { const Wrapper = createProxyPrototypeClass( WorkflowEntrypoint, - function (this: WorkflowEntrypoint, key) { + function (this: WorkflowEntrypoint, key) { // only Workflow `run` should be exposed over RPC if (!["run"].includes(key)) { return; } const property = getWorkerEntrypointRPCProperty( - this as unknown as WorkerEntrypoint, + this as unknown as WorkerEntrypoint, entrypoint, key ); @@ -528,19 +526,18 @@ export function createWorkflowEntrypointWrapper(entrypoint: string) { ); Wrapper.prototype.run = async function ( - this: WorkflowEntrypoint, + this: WorkflowEntrypoint, ...args ) { const { mainPath, entrypointValue } = await getWorkerEntrypointExport( this.env, entrypoint ); - const userEnv = stripInternalEnv(this.env); // workflow entrypoint value should always be a constructor if (typeof entrypointValue === "function") { // Assuming the user has defined a `WorkflowEntrypoint` subclass const ctor = entrypointValue as WorkflowEntrypointConstructor; - const instance = new ctor(this.ctx, userEnv); + const instance = new ctor(this.ctx, this.env); // noinspection SuspiciousTypeOfGuard if (!(instance instanceof WorkflowEntrypoint)) { const message = `Expected ${entrypoint} export of ${mainPath} to be a subclass of \`WorkflowEntrypoint\``; diff --git a/packages/vitest-pool-workers/src/worker/env.ts b/packages/vitest-pool-workers/src/worker/env.ts index efddaa439f5b..f07b2b38e822 100644 --- a/packages/vitest-pool-workers/src/worker/env.ts +++ b/packages/vitest-pool-workers/src/worker/env.ts @@ -1,37 +1,43 @@ import assert from "node:assert"; +import { exports } from "cloudflare:workers"; -// See public facing `cloudflare:test` types for docs -export let env: Record; -export let SELF: Fetcher; +export { env } from "cloudflare:workers"; -export function stripInternalEnv( - internalEnv: Record & Env -): Record { - const result: Record & Partial = { ...internalEnv }; - delete result.__VITEST_POOL_WORKERS_SELF_NAME; - delete result.__VITEST_POOL_WORKERS_SELF_SERVICE; - delete result.__VITEST_POOL_WORKERS_LOOPBACK_SERVICE; - delete result.__VITEST_POOL_WORKERS_RUNNER_OBJECT; - delete result.__VITEST_POOL_WORKERS_UNSAFE_EVAL; - return result; -} - -export let internalEnv: Record & Env; -export function setEnv(newEnv: Record & Env) { - // Store full env for `WorkersSnapshotEnvironment` - internalEnv = newEnv; - SELF = newEnv.__VITEST_POOL_WORKERS_SELF_SERVICE; - - // Strip internal bindings from user facing `env` - env = stripInternalEnv(newEnv); -} +/** + * For reasons that aren't clear to me, just `SELF = exports.default` ends up with SELF being + * undefined in a test. This Proxy solution works. + */ +export const SELF = new Proxy( + {}, + { + get(_, p) { + const target = exports.default as unknown as Record< + string | symbol, + unknown + >; + const value = target[p]; + return typeof value === "function" ? value.bind(target) : value; + }, + } +); export function getSerializedOptions(): SerializedOptions { assert(typeof __vitest_worker__ === "object", "Expected global Vitest state"); - const options = __vitest_worker__.config?.poolOptions?.workers; + const options = __vitest_worker__.providedContext.cloudflarePoolOptions; // `options` should always be defined when running tests - assert(options !== undefined, "Expected serialised options"); - return options; + + assert( + options !== undefined, + "Expected serialised options, got keys: " + + Object.keys(__vitest_worker__.providedContext).join(", ") + ); + const parsedOptions = JSON.parse(options); + return { + ...parsedOptions, + durableObjectBindingDesignators: new Map( + parsedOptions.durableObjectBindingDesignators + ), + }; } export function getResolvedMainPath( @@ -40,7 +46,7 @@ export function getResolvedMainPath( const options = getSerializedOptions(); if (options.main === undefined) { throw new Error( - `Using ${forBindingType} bindings to the current worker requires \`poolOptions.workers.main\` to be set to your worker's entrypoint` + `Using ${forBindingType} bindings to the current worker requires \`poolOptions.workers.main\` to be set to your worker's entrypoint: ${JSON.stringify(options)}` ); } return options.main; diff --git a/packages/vitest-pool-workers/src/worker/fetch-mock.ts b/packages/vitest-pool-workers/src/worker/fetch-mock.ts index a08d466c6bca..65530a4019bf 100644 --- a/packages/vitest-pool-workers/src/worker/fetch-mock.ts +++ b/packages/vitest-pool-workers/src/worker/fetch-mock.ts @@ -1,200 +1,6 @@ -import assert from "node:assert"; -import { Buffer } from "node:buffer"; -import { isMockActive, MockAgent, setDispatcher } from "cloudflare:mock-agent"; -import type { Dispatcher } from "undici"; - -const DECODER = new TextDecoder(); - -/** - * Mutate an Error instance so it passes either of the checks in isAbortError - */ -export function castAsAbortError(err: Error): Error { - (err as Error & { code: string }).code = "ABORT_ERR"; - err.name = "AbortError"; - return err; -} - -// See public facing `cloudflare:test` types for docs -export const fetchMock = new MockAgent({ connections: 1 }); - -interface BufferedRequest { - request: Request; - body: Uint8Array | null; -} - -class SingleAccessMap extends Map { - override get(key: K): V | undefined { - const value = super.get(key); - super.delete(key); - return value; - } -} - -const requests = new SingleAccessMap(); -const responses = new SingleAccessMap(); - -// This is in a Workers context - const originalFetch = fetch; -setDispatcher((opts, handler) => { - const serialisedOptions = JSON.stringify(opts); - const request = requests.get(serialisedOptions); - assert(request !== undefined, "Expected dispatch to come from fetch()"); - originalFetch - .call(globalThis, request.request, { body: request.body }) - .then((response) => { - responses.set(serialisedOptions, response); - assert(handler.onComplete !== undefined, "Expected onComplete() handler"); - handler.onComplete?.([]); - }) - .catch((error) => { - assert(handler.onError !== undefined, "Expected onError() handler"); - handler.onError(error); - }); -}); -// Monkeypatch `fetch()` to intercept requests if the fetch mock is enabled. -//The way we've implemented this, `fetchMock` only mocks requests in the current -// worker. We kind of have to do it this way, as `fetchMock` supports functions -// as reply callbacks, and we can't serialise arbitrary functions across worker -// boundaries. For mocking requests in other workers, Miniflare's `fetchMock` -// option can be used in the `vitest.config.mts`. +// Monkeypatch `fetch()`. This looks like a no-op, but it's not. It allows MSW to intercept fetch calls using it's Fetch interceptor. globalThis.fetch = async (input, init) => { - const isActive = isMockActive(fetchMock); - if (!isActive) { - return originalFetch.call(globalThis, input, init); - } - - const request = new Request(input, init); - const url = new URL(request.url); - - // Use a signal and the aborted value if provided - const abortSignal = init?.signal; - let abortSignalAborted = abortSignal?.aborted ?? false; - abortSignal?.addEventListener("abort", () => { - abortSignalAborted = true; - }); - - // Don't allow mocked `Upgrade` requests - if (request.headers.get("Upgrade") !== null) { - return originalFetch.call(globalThis, request); - } - - // Convert headers into `undici` friendly format - const requestHeaders: { "set-cookie"?: string[] } & Record = - {}; - for (const entry of request.headers) { - const key = entry[0].toLowerCase(); - const value = entry[1]; - if (key === "set-cookie") { - (requestHeaders[key] ??= []).push(value); - } else { - requestHeaders[key] = value; - } - } - - // Buffer body in case it needs to be matched against. Note `undici` only - // supports matching against `string` bodies. To support binary bodies, we - // buffer the body to a `Uint8Array`, then try to decode it. We pass the - // decoded body via `DispatchOptions` for matching, then use the `Uint8Array` - // body if the request falls-through to an actual `fetch()` call. - const bodyArray = - request.body === null ? null : new Uint8Array(await request.arrayBuffer()); - // Note `DECODER` doesn't have the `fatal: true` option enabled, so will - // substitute invalid data with a replacement character - const bodyText = bodyArray === null ? "" : DECODER.decode(bodyArray); - const dispatchOptions: Dispatcher.DispatchOptions = { - origin: url.origin, - path: url.pathname + url.search, - method: request.method as Dispatcher.HttpMethod, - body: bodyText, - headers: requestHeaders, - }; - const serialisedOptions = JSON.stringify(dispatchOptions); - requests.set(serialisedOptions, { request, body: bodyArray }); - - // If the response was mocked, record data as we receive it - let responseStatusCode: number | undefined; - let responseStatusText: string | undefined; - let responseHeaders: string[][] | undefined; - const responseChunks: Buffer[] = []; - - // Create deferred promise for response - let responseResolve: (response: Response) => void; - let responseReject: (error: Error) => void; - const responsePromise = new Promise((resolve, reject) => { - responseResolve = resolve; - responseReject = reject; - }); - - // Dispatch the request through the mock agent - const dispatchHandlers: Dispatcher.DispatchHandler = { - onError(error) { - responseReject(error); - }, - onUpgrade(_statusCode, _headers, _socket) { - assert.fail("Unreachable: upgrade requests not supported"); - }, - // `onHeaders` and `onData` will only be called if the response was mocked - onHeaders(statusCode, headers, _resume, statusText) { - if (abortSignalAborted) { - return false; - } - - responseStatusCode = statusCode; - responseStatusText = statusText; - - if (headers === null) { - return true; - } - assert.strictEqual(headers.length % 2, 0, "Expected key/value array"); - responseHeaders = Array.from({ length: headers.length / 2 }).map( - (_, i) => [headers[i * 2].toString(), headers[i * 2 + 1].toString()] - ); - return true; - }, - onData(chunk) { - if (abortSignalAborted) { - return false; - } - - responseChunks.push(chunk); - return true; - }, - onComplete() { - if (abortSignalAborted) { - responseReject( - castAsAbortError(new Error("The operation was aborted")) - ); - return; - } - - // `maybeResponse` will be `undefined` if we mocked the request - const maybeResponse = responses.get(serialisedOptions); - if (maybeResponse === undefined) { - const responseBody = Buffer.concat(responseChunks); - const response = new Response(responseBody, { - status: responseStatusCode, - statusText: responseStatusText, - headers: responseHeaders, - }); - const throwImmutableHeadersError = () => { - throw new TypeError("Can't modify immutable headers"); - }; - Object.defineProperty(response, "url", { value: url.href }); - Object.defineProperties(response.headers, { - set: { value: throwImmutableHeadersError }, - append: { value: throwImmutableHeadersError }, - delete: { value: throwImmutableHeadersError }, - }); - responseResolve(response); - } else { - responseResolve(maybeResponse); - } - }, - onBodySent() {}, // (ignored) - }; - - fetchMock.dispatch(dispatchOptions, dispatchHandlers); - return responsePromise; + return originalFetch.call(globalThis, input, init); }; diff --git a/packages/vitest-pool-workers/src/worker/index.ts b/packages/vitest-pool-workers/src/worker/index.ts index 35b8c2e08b27..534cb66c4b24 100644 --- a/packages/vitest-pool-workers/src/worker/index.ts +++ b/packages/vitest-pool-workers/src/worker/index.ts @@ -1,26 +1,19 @@ import assert from "node:assert"; -import { Buffer } from "node:buffer"; -import events from "node:events"; -import process from "node:process"; import * as vm from "node:vm"; import defines from "__VITEST_POOL_WORKERS_DEFINES"; import { createWorkerEntrypointWrapper, - internalEnv, maybeHandleRunRequest, registerHandlerAndGlobalWaitUntil, runInRunnerObject, - setEnv, } from "cloudflare:test-internal"; +import { DurableObject } from "cloudflare:workers"; import * as devalue from "devalue"; // Using relative path here to ensure `esbuild` bundles it import { structuredSerializableReducers, structuredSerializableRevivers, } from "../../../miniflare/src/workers/core/devalue"; -import { createChunkingSocket } from "../shared/chunking-socket"; -import type { SocketLike } from "../shared/chunking-socket"; -import type { VitestExecutor as VitestExecutorType } from "vitest/execute"; function structuredSerializableStringify(value: unknown): string { return devalue.stringify(value, structuredSerializableReducers); @@ -29,16 +22,23 @@ function structuredSerializableParse(value: string): unknown { return devalue.parse(value, structuredSerializableRevivers); } -globalThis.Buffer = Buffer; // Required by `vite-node/source-map` +// Mock Service Worker needs this — stub with no-op methods since workerd +// doesn't provide BroadcastChannel +globalThis.BroadcastChannel = class { + constructor(public name: string) {} + postMessage(_message: unknown) {} + close() {} + addEventListener(_type: string, _listener: unknown) {} + removeEventListener(_type: string, _listener: unknown) {} + onmessage: ((event: unknown) => void) | null = null; + onmessageerror: ((event: unknown) => void) | null = null; +} as unknown as typeof BroadcastChannel; -globalThis.process = process; // Required by `vite-node` -process.argv = []; // Required by `@vitest/utils` let cwd: string | undefined; process.cwd = () => { assert(cwd !== undefined, "Expected cwd to be set"); return cwd; }; -Object.setPrototypeOf(process, events.EventEmitter.prototype); // Required by `vitest` globalThis.__console = console; @@ -71,9 +71,9 @@ const monkeypatchedSetTimeout = (...args: Parameters) => { const [callback, delay, ...restArgs] = args; const callbackName = args[0]?.name ?? ""; const callerFileName = getCallerFileName(monkeypatchedSetTimeout); - const fromVitest = /\/node_modules\/(\.store\/)?vitest/.test( - callerFileName ?? "" - ); + const fromVitest = + /\/node_modules\/(\.pnpm\/|\.store\/)?vitest/.test(callerFileName ?? "") || + /\/packages\/vitest\/dist/.test(callerFileName ?? ""); // If this `setTimeout()` isn't from Vitest, or has a non-zero delay, // just call the original function @@ -133,76 +133,6 @@ function isDifferentIOContextError(e: unknown) { ); } -// Wraps a `WebSocket` with a Node `MessagePort` like interface -class WebSocketMessagePort extends events.EventEmitter { - #chunkingSocket: SocketLike; - - constructor(private readonly socket: WebSocket) { - super(); - this.#chunkingSocket = createChunkingSocket({ - post(message) { - socket.send(message); - }, - on(listener) { - socket.addEventListener("message", (event) => { - listener(event.data); - }); - }, - }); - this.#chunkingSocket.on((message) => { - const parsed = structuredSerializableParse(message); - this.emit("message", parsed); - }); - socket.accept(); - } - - postMessage(data: unknown) { - const stringified = structuredSerializableStringify(data); - try { - // Accessing `readyState` may also throw different I/O context error - if (this.socket.readyState === WebSocket.READY_STATE_OPEN) { - this.#chunkingSocket.post(stringified); - } - } catch (error) { - // If the user tried to perform a dynamic `import()` or `console.log()` - // from inside a `export default { fetch() { ... } }` handler using `SELF` - // or from inside their own Durable Object, Vitest will try to send an - // RPC message from the I/O context that is different to the Runner Durable Object. - // There's nothing we can really do to prevent this: we want to run these things - // in different I/O contexts with the behaviour this causes. We'd still like to send - // the RPC message though, so if we detect this, we try resend the message - // from the runner object. - if (isDifferentIOContextError(error)) { - const promise = runInRunnerObject(internalEnv, () => { - this.#chunkingSocket.post(stringified); - }).catch((e) => { - __console.error("Error sending to pool inside runner:", e, data); - }); - registerHandlerAndGlobalWaitUntil(promise); - } else { - __console.error("Error sending to pool:", error, data); - } - } - } -} - -interface JsonError { - message?: string; - name?: string; - stack?: string; - cause?: JsonError; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function reduceError(e: any): JsonError { - return { - name: e?.name, - message: e?.message ?? String(e), - stack: e?.stack, - cause: e?.cause === undefined ? undefined : reduceError(e.cause), - }; -} - let patchedFunction = false; function ensurePatchedFunction(unsafeEval: UnsafeEval) { if (patchedFunction) { @@ -236,17 +166,12 @@ function applyDefines() { } } -// `__VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__` is a singleton and "colo local" ephemeral object. Refer to: -// https://github.com/cloudflare/workerd/blob/v1.20231206.0/src/workerd/server/workerd.capnp#L529-L543 -export class __VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__ - implements DurableObject -{ - executor: VitestExecutorType | undefined; - - constructor(_state: DurableObjectState, env: Record & Env) { - vm._setUnsafeEval(env.__VITEST_POOL_WORKERS_UNSAFE_EVAL); - ensurePatchedFunction(env.__VITEST_POOL_WORKERS_UNSAFE_EVAL); - setEnv(env); +// `__VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__` is a singleton +export class __VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__ extends DurableObject { + constructor(_state: DurableObjectState, doEnv: Cloudflare.Env) { + super(_state, doEnv); + vm._setUnsafeEval(doEnv.__VITEST_POOL_WORKERS_UNSAFE_EVAL); + ensurePatchedFunction(doEnv.__VITEST_POOL_WORKERS_UNSAFE_EVAL); applyDefines(); } @@ -255,57 +180,59 @@ export class __VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__ const { 0: poolSocket, 1: poolResponseSocket } = new WebSocketPair(); const workerDataHeader = request.headers.get("MF-Vitest-Worker-Data"); - assert(workerDataHeader !== null); - const wd = structuredSerializableParse(workerDataHeader); - assert(typeof wd === "object" && wd !== null); - assert("filePath" in wd && typeof wd.filePath === "string"); - assert("name" in wd && typeof wd.name === "string"); - assert("data" in wd && typeof wd.data === "object" && wd.data !== null); - assert("cwd" in wd && typeof wd.cwd === "string"); - cwd = wd.cwd; + assert(workerDataHeader); - const port = new WebSocketMessagePort(poolSocket); - try { - const module = await import(wd.filePath); + const wd = structuredSerializableParse(workerDataHeader); + assert( + wd && typeof wd === "object" && "cwd" in wd && typeof wd.cwd === "string" + ); - // HACK: Internally, Vitest's worker thread calls `startViteNode()`, which - // constructs a singleton `VitestExecutor`. `VitestExecutor` is a subclass - // of `ViteNodeRunner`, which is how the worker communicates with the - // Vite server. We'd like access to this singleton so we can transform and - // import code with Vite ourselves (e.g. for user worker's default exports - // and Durable Objects). Unfortunately, Vitest doesn't publicly export the - // `startViteNode()` function. Instead, we monkeypatch a `VitestExecutor` - // method we know is called to get the singleton. :see_no_evil: - // TODO(soon): see if we can get `startViteNode()` (https://github.com/vitest-dev/vitest/blob/8d183da4f7cc2986d11c802d16bacd221fb69b96/packages/vitest/src/runtime/execute.ts#L45) - // exported in `vitest/execute` (https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/public/execute.ts) - const { VitestExecutor } = await import("vitest/execute"); - const originalResolveUrl = VitestExecutor.prototype.resolveUrl; - // eslint-disable-next-line @typescript-eslint/no-this-alias - const that = this; - VitestExecutor.prototype.resolveUrl = function (...args) { - that.executor = this; - return originalResolveUrl.apply(this, args); - }; + cwd = wd.cwd; - (wd.data as { port: WebSocketMessagePort }).port = port; - module[wd.name](wd.data) - .then(() => { - poolSocket.close(1000, "Done"); - }) - .catch((e: unknown) => { - port.postMessage({ vitestPoolWorkersError: e }); - const error = reduceError(e); - __console.error("Error running worker:", error.stack); - poolSocket.close(1011, "Internal Error"); + const { init, runBaseTests, setupEnvironment } = await import( + "vitest/worker" + ); + + poolSocket.accept(); + + init({ + post: (response) => { + try { + poolSocket.send(structuredSerializableStringify(response)); + } catch (error) { + // If the user tried to perform a dynamic `import()` or `console.log()` + // from inside a `export default { fetch() { ... } }` handler using `SELF` + // or from inside their own Durable Object, Vitest will try to send an + // RPC message from a non-`RunnerObject` I/O context. There's nothing we + // can really do to prevent this: we want to run these things in different + // I/O contexts with the behaviour this causes. We'd still like to send + // the RPC message though, so if we detect this, we try resend the message + // from the runner object. + if (isDifferentIOContextError(error)) { + const promise = runInRunnerObject(() => { + poolSocket.send(structuredSerializableStringify(response)); + }).catch((e) => { + __console.error( + "Error sending to pool inside runner:", + e, + response + ); + }); + registerHandlerAndGlobalWaitUntil(promise); + } else { + __console.error("Error sending to pool:", error, response); + } + } + }, + on: (callback) => { + poolSocket.addEventListener("message", (m) => { + callback(structuredSerializableParse(m.data)); }); - } catch (e) { - const error = reduceError(e); - __console.error("Error initialising worker:", error.stack); - return Response.json(error, { - status: 500, - headers: { "MF-Experimental-Error-Stack": "true" }, - }); - } + }, + runTests: (state, traces) => runBaseTests("run", state, traces), + collectTests: (state, traces) => runBaseTests("collect", state, traces), + setup: setupEnvironment, + }); return new Response(null, { status: 101, webSocket: poolResponseSocket }); } diff --git a/packages/vitest-pool-workers/src/worker/lib/cloudflare/snapshot.ts b/packages/vitest-pool-workers/src/worker/lib/cloudflare/snapshot.ts new file mode 100644 index 000000000000..d0631c13cc9c --- /dev/null +++ b/packages/vitest-pool-workers/src/worker/lib/cloudflare/snapshot.ts @@ -0,0 +1,43 @@ +import assert from "node:assert"; +import { dirname } from "node:path"; +import { env } from "cloudflare:workers"; +import { VitestSnapshotEnvironment } from "vitest/runtime"; + +// Define a custom `SnapshotEnvironment` that uses a service binding for file +// system operations, rather than `node:fs` +class WorkersSnapshotEnvironment extends VitestSnapshotEnvironment { + #fetch(method: string, path: string, body?: BodyInit): Promise { + const encodedPath = encodeURIComponent(path); + const url = `http://placeholder/snapshot?path=${encodedPath}`; + return env.__VITEST_POOL_WORKERS_LOOPBACK_SERVICE.fetch(url, { + method, + body, + }); + } + + async prepareDirectory(dirPath: string): Promise { + const res = await this.#fetch("POST", dirPath); + assert.strictEqual(res.status, 204); + } + + async saveSnapshotFile(filePath: string, snapshot: string): Promise { + await this.prepareDirectory(dirname(filePath)); + const res = await this.#fetch("PUT", filePath, snapshot); + assert.strictEqual(res.status, 204); + } + + async readSnapshotFile(filePath: string): Promise { + const res = await this.#fetch("GET", filePath); + if (res.status === 404) { + return null; + } + assert.strictEqual(res.status, 200); + return await res.text(); + } + + async removeSnapshotFile(filePath: string): Promise { + const res = await this.#fetch("DELETE", filePath); + assert.strictEqual(res.status, 204); + } +} +export default new WorkersSnapshotEnvironment(); diff --git a/packages/vitest-pool-workers/src/worker/lib/cloudflare/test-internal.ts b/packages/vitest-pool-workers/src/worker/lib/cloudflare/test-internal.ts index afc69277687f..595f283a808a 100644 --- a/packages/vitest-pool-workers/src/worker/lib/cloudflare/test-internal.ts +++ b/packages/vitest-pool-workers/src/worker/lib/cloudflare/test-internal.ts @@ -1,3 +1,5 @@ +import "../../fetch-mock"; + // Hide internals in separate `cloudflare:test-internal` module, so they're not // exposed to users using e.g. `import * as test from "cloudflare:test"` export * from "../../d1"; @@ -5,6 +7,5 @@ export * from "../../durable-objects"; export * from "../../entrypoints"; export * from "../../env"; export * from "../../events"; -export * from "../../fetch-mock"; export * from "../../wait-until"; export * from "../../workflows"; diff --git a/packages/vitest-pool-workers/src/worker/lib/cloudflare/test-runner.ts b/packages/vitest-pool-workers/src/worker/lib/cloudflare/test-runner.ts deleted file mode 100644 index 766c450084b6..000000000000 --- a/packages/vitest-pool-workers/src/worker/lib/cloudflare/test-runner.ts +++ /dev/null @@ -1,358 +0,0 @@ -import assert from "node:assert"; -import { NodeSnapshotEnvironment } from "@vitest/snapshot/environment"; -import { resetMockAgent } from "cloudflare:mock-agent"; -import { - fetchMock, - getSerializedOptions, - internalEnv, - registerHandlerAndGlobalWaitUntil, - waitForGlobalWaitUntil, -} from "cloudflare:test-internal"; -import { vi } from "vitest"; -import { VitestTestRunner } from "vitest/runners"; -import workerdUnsafe from "workerd:unsafe"; -import type { Suite, Test } from "@vitest/runner"; -import type { SerializedConfig, WorkerGlobalState, WorkerRPC } from "vitest"; - -// When `DEBUG` is `true`, runner operations will be logged and slowed down -// TODO(soon): remove this -const DEBUG = false; -const _ = (n: number) => " ".repeat(n); - -// Define a custom `SnapshotEnvironment` that uses a service binding for file -// system operations, rather than `node:fs` -// Based on https://github.com/vitest-dev/vitest/blob/v1.0.0-beta.5/packages/vitest/src/integrations/snapshot/environments/node.ts -class WorkersSnapshotEnvironment extends NodeSnapshotEnvironment { - constructor(private rpc: WorkerRPC) { - super(); - } - - #fetch(method: string, path: string, body?: BodyInit): Promise { - const encodedPath = encodeURIComponent(path); - const url = `http://placeholder/snapshot?path=${encodedPath}`; - return internalEnv.__VITEST_POOL_WORKERS_LOOPBACK_SERVICE.fetch(url, { - method, - body, - }); - } - - getHeader(): string { - return `// Vitest Snapshot v${this.getVersion()}, https://vitest.dev/guide/snapshot.html`; - } - - resolvePath(filePath: string): Promise { - return this.rpc.resolveSnapshotPath(filePath); - } - - async prepareDirectory(dirPath: string): Promise { - const res = await this.#fetch("POST", dirPath); - assert.strictEqual(res.status, 204); - } - - async saveSnapshotFile(filePath: string, snapshot: string): Promise { - const res = await this.#fetch("PUT", filePath, snapshot); - assert.strictEqual(res.status, 204); - } - - async readSnapshotFile(filePath: string): Promise { - const res = await this.#fetch("GET", filePath); - if (res.status === 404) { - return null; - } - assert.strictEqual(res.status, 200); - return await res.text(); - } - - async removeSnapshotFile(filePath: string): Promise { - const res = await this.#fetch("DELETE", filePath); - assert.strictEqual(res.status, 204); - } -} - -let initialState: WorkerGlobalState | undefined; -let patchedPrepareStackTrace = false; -const getConsoleGetFileName = () => () => "node:internal/console/constructor"; - -interface TryOptions { - repeats: number; - retry: number; -} - -type TryKey = `${number}:${number}`; -function getTryKey({ repeats, retry }: TryOptions): TryKey { - return `${repeats}:${retry}`; -} - -interface TryState { - active?: TryKey; - popped: Set; -} -const tryStates = new WeakMap(); - -// Wrap RPC calls to register all RPC promises with handler `waitUntil()`s. -// This ensures all messages created in an `export default` request context are -// sent, rather than being silently discarded. -const waitUntilPatchedRpc = new WeakSet(); -export function createWaitUntilRpc(rpc: WorkerRPC): WorkerRPC { - return new Proxy(rpc, { - get(target, key, handler) { - if (key === "then") { - return; - } - const sendCall = Reflect.get(target, key, handler); - const waitUntilSendCall = async (...args: unknown[]) => { - const promise = sendCall(...args); - registerHandlerAndGlobalWaitUntil(promise); - return promise; - }; - waitUntilSendCall.asEvent = sendCall.asEvent; - return waitUntilSendCall; - }, - }); -} - -export default class WorkersTestRunner extends VitestTestRunner { - readonly state: WorkerGlobalState; - readonly isolatedStorage: boolean; - - constructor(config: SerializedConfig) { - super(config); - - // @ts-expect-error `this.workerState` has "private" access, how quaint :D - const state: WorkerGlobalState = this.workerState; - this.state = state; - - const { isolatedStorage } = getSerializedOptions(); - this.isolatedStorage = isolatedStorage ?? false; - - // Make sure we're using a `WorkersSnapshotEnvironment` - const opts = state.config.snapshotOptions; - if (!(opts.snapshotEnvironment instanceof WorkersSnapshotEnvironment)) { - opts.snapshotEnvironment = new WorkersSnapshotEnvironment(state.rpc); - } - - if (!waitUntilPatchedRpc.has(state.rpc)) { - waitUntilPatchedRpc.add(state.rpc); - state.rpc = createWaitUntilRpc(state.rpc); - } - - // If this is the first run in this isolate, store a reference to the state. - // Vitest only sets up its `console.log()` interceptor on the first run - // (https://github.com/vitest-dev/vitest/blob/v1.0.0-beta.5/packages/vitest/src/runtime/setup-node.ts#L58), - // and will use the `state` of the first run. Unfortunately, `state` is - // recreated on each run. In particular, `state.rpc` will be hooked up with - // a different `WebSocket` that gets closed at the end of each run. To - // prevent `Can't call WebSocket send() after close()` errors, update the - // initial state's `rpc` with the current `rpc`. Similarly, make sure - // `initialState.current` is updated with the current task later on so - // `console.log()`s report their current test correctly. - initialState ??= state; - initialState.rpc = state.rpc; - - // Vitests expects `node:console`s filename to start with `node:internal/console/`: - // https://github.com/vitest-dev/vitest/blob/v1.0.0-beta.5/packages/vitest/src/runtime/console.ts#L16 - if (!patchedPrepareStackTrace) { - patchedPrepareStackTrace = true; - // Need to patch this after Vitest's own source mapping handler installed - const originalPrepareStackTrace = Error.prepareStackTrace; - assert(originalPrepareStackTrace !== undefined); - Error.prepareStackTrace = (err, callSites) => { - for (const callSite of callSites) { - const fileName = callSite.getFileName(); - if (fileName?.endsWith("/dist/worker/lib/node/console.mjs")) { - Object.defineProperty(callSite, "getFileName", { - get: getConsoleGetFileName, - }); - } - } - return originalPrepareStackTrace(err, callSites); - }; - } - } - - async updateStackedStorage( - action: "push" | "pop", - source: Test | Suite - ): Promise { - if (!this.isolatedStorage) { - return; - } - - // Ensure all `ctx.waitUntil()` calls complete before aborting all objects. - // `ctx.waitUntil()`s may contain storage calls (e.g. caching responses) - // that could re-create Durable Objects and interrupt stack operations. - await waitForGlobalWaitUntil(); - - // Abort all Durable Objects apart from those marked with `preventEviction` - // (i.e. the runner object and the proxy server). - // On push, ensures objects are started with newly copied `.sqlite` files. - // On pop, ensures SQLite WAL checkpoint, allowing us to just copy `.sqlite` files. - await workerdUnsafe.abortAllDurableObjects(); - - // Send request to pool loopback service to update `.sqlite` files - const url = "http://placeholder/storage"; - const sourceString = `${source.file?.name ?? "an unknown file"}'s ${ - source.type - } ${JSON.stringify(source.name)}`; - - const res = await internalEnv.__VITEST_POOL_WORKERS_LOOPBACK_SERVICE.fetch( - url, - { - method: action === "pop" ? "DELETE" : "POST", - headers: { "MF-Vitest-Source": sourceString }, - } - ); - assert.strictEqual(res.status, 204, await res.text()); - } - - syncCurrentTaskWithInitialState() { - assert(initialState !== undefined); // Assigned in constructor - initialState.current = this.state.current; - } - - async onBeforeRunFiles() { - if (DEBUG) { - __console.log("onBeforeRunFiles"); - await scheduler.wait(100); - } - - resetMockAgent(fetchMock); - // @ts-expect-error Support Vitest v2 - if (super.onBeforeRunFiles) { - // @ts-expect-error Support Vitest v2 - return super.onBeforeRunFiles(); - } - } - - async onAfterRunFiles() { - if (DEBUG) { - __console.log("onAfterRunFiles"); - await scheduler.wait(100); - } - - // Unlike the official threads and forks pool, we do not recycle the miniflare instances to maintain the module cache. - // However, this creates a side effect where the module mock will not be re-evaluated on watch mode. - // This fixes https://github.com/cloudflare/workers-sdk/issues/6844 by resetting the module graph. - vi.resetModules(); - - // Ensure all `ctx.waitUntil()` calls complete before disposing the runtime - // (if using `vitest run`) and aborting all objects. `ctx.waitUntil()`s may - // contain storage calls (e.g. caching responses) that could try to access - // aborted Durable Objects. - await waitForGlobalWaitUntil(); - return super.onAfterRunFiles?.(); - } - - async onBeforeRunSuite(suite: Suite) { - if (DEBUG) { - __console.log(`${_(2)}onBeforeRunSuite: ${suite.name}`); - await scheduler.wait(100); - } - await this.updateStackedStorage("push", suite); - - return super.onBeforeRunSuite(suite); - } - async onAfterRunSuite(suite: Suite) { - if (DEBUG) { - __console.log(`${_(2)}onAfterRunSuite: ${suite.name}`); - await scheduler.wait(100); - } - await this.updateStackedStorage("pop", suite); - - return super.onAfterRunSuite(suite); - } - - async ensurePoppedActiveTryStorage( - test: Test, - newActive?: TryKey - ): Promise { - const tries = tryStates.get(test); - assert(tries !== undefined); - const active = tries.active; - if (newActive !== undefined) { - tries.active = newActive; - } - if (active !== undefined && !tries.popped.has(active)) { - tries.popped.add(active); - await this.updateStackedStorage("pop", test); - return true; - } - return false; - } - - async onBeforeRunTask(test: Test) { - if (DEBUG) { - __console.log(`${_(4)}onBeforeRunTask: ${test.name}`); - await scheduler.wait(100); - } - - tryStates.set(test, { popped: new Set() }); - if (this.isolatedStorage && test.concurrent) { - const quotedName = JSON.stringify(test.name); - const msg = [ - "Concurrent tests are unsupported with isolated storage. Please either:", - `- Remove \`.concurrent\` from the ${quotedName} test`, - `- Remove \`.concurrent\` from all \`describe()\` blocks containing the ${quotedName} test`, - "- Remove `isolatedStorage: true` from your project's Vitest config", - ]; - throw new Error(msg.join("\n")); - } - - const result = await super.onBeforeRunTask(test); - // Current task may be updated in `super.onBeforeRunTask()`: - // https://github.com/vitest-dev/vitest/blob/v1.0.0-beta.5/packages/vitest/src/runtime/runners/test.ts#L68 - this.syncCurrentTaskWithInitialState(); - return result; - } - async onAfterRunTask(test: Test) { - if (DEBUG) { - __console.log(`${_(4)}onAfterRunTask: ${test.name}`); - await scheduler.wait(100); - } - - // If we haven't popped storage for the test yet (i.e. the try threw, - // `onAfterTryTask()` wasn't called, and we didn't enable retries so - // `onBeforeTryTask()` wasn't called again), pop it - await this.ensurePoppedActiveTryStorage(test); - tryStates.delete(test); - - const result = await super.onAfterRunTask(test); - // Current task updated in `super.onAfterRunTask()`: - // https://github.com/vitest-dev/vitest/blob/v1.0.0-beta.5/packages/vitest/src/runtime/runners/test.ts#L47 - this.syncCurrentTaskWithInitialState(); - return result; - } - - // @ts-expect-error `VitestRunner` defines an additional `options` parameter - // that `VitestTestRunner` doesn't use - async onBeforeTryTask(test: Test, options: TryOptions) { - if (DEBUG) { - __console.log(`${_(6)}onBeforeTryTask: ${test.name}`, options); - await scheduler.wait(100); - } - - // If we haven't popped storage for the previous try yet (i.e. the try - // threw and `onAfterTryTask()` wasn't called), pop it first... - const newActive = getTryKey(options); - await this.ensurePoppedActiveTryStorage(test, newActive); - - await this.updateStackedStorage("push", test); - return super.onBeforeTryTask(test); - } - // @ts-expect-error `VitestRunner` defines an additional `options` parameter - // that `VitestTestRunner` doesn't use - async onAfterTryTask(test: Test, options: TryOptions) { - if (DEBUG) { - __console.log(`${_(6)}onAfterTryTask: ${test.name}`, options); - await scheduler.wait(100); - } - - // Pop storage for this try, asserting that we haven't done so already. - // `onAfterTryTask()` is never called multiple times for the same try, - // `onBeforeTryTask()` will only be called with a new try after this, - // and `onAfterRunTask()` will only be called after all tries. - assert(await this.ensurePoppedActiveTryStorage(test)); - - return super.onAfterTryTask(test); - } -} diff --git a/packages/vitest-pool-workers/src/worker/lib/cloudflare/test.ts b/packages/vitest-pool-workers/src/worker/lib/cloudflare/test.ts index 26246a242e26..a89acf2a8e39 100644 --- a/packages/vitest-pool-workers/src/worker/lib/cloudflare/test.ts +++ b/packages/vitest-pool-workers/src/worker/lib/cloudflare/test.ts @@ -7,7 +7,6 @@ export { env, SELF, - fetchMock, runInDurableObject, runDurableObjectAlarm, listDurableObjectIds, diff --git a/packages/vitest-pool-workers/src/worker/lib/debug.ts b/packages/vitest-pool-workers/src/worker/lib/debug.ts deleted file mode 100644 index c36fe4f16a61..000000000000 --- a/packages/vitest-pool-workers/src/worker/lib/debug.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function (_name: string) { - return (..._args: unknown[]) => {}; -} diff --git a/packages/vitest-pool-workers/src/worker/lib/mlly.ts b/packages/vitest-pool-workers/src/worker/lib/mlly.ts deleted file mode 100644 index 8247f6b51880..000000000000 --- a/packages/vitest-pool-workers/src/worker/lib/mlly.ts +++ /dev/null @@ -1,48 +0,0 @@ -function isObject(value: unknown): value is Record { - return value !== null && typeof value === "object"; -} - -export function resolvePathSync() { - throw new Error("resolvePathSync() not yet implemented in worker"); -} - -// https://github.com/unjs/mlly/blob/71563c22ec7dbf25672d46bc679619dbd65e79d2/src/cjs.ts#L34 -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function interopDefault(sourceModule: any): any { - if ( - !isObject(sourceModule) || - !("default" in sourceModule) || - !isObject(sourceModule.default) - ) { - return sourceModule; - } - const newModule = sourceModule.default; - for (const key in sourceModule) { - if (key === "default") { - try { - if (!(key in newModule)) { - Object.defineProperty(newModule, key, { - enumerable: false, - configurable: false, - get() { - return newModule; - }, - }); - } - } catch {} - } else { - try { - if (!(key in newModule)) { - Object.defineProperty(newModule, key, { - enumerable: true, - configurable: true, - get() { - return sourceModule[key]; - }, - }); - } - } catch {} - } - } - return newModule; -} diff --git a/packages/vitest-pool-workers/src/worker/lib/tinypool.ts b/packages/vitest-pool-workers/src/worker/lib/tinypool.ts deleted file mode 100644 index 554c24b89081..000000000000 --- a/packages/vitest-pool-workers/src/worker/lib/tinypool.ts +++ /dev/null @@ -1 +0,0 @@ -export const workerId = 0; diff --git a/packages/vitest-pool-workers/src/worker/node/console.ts b/packages/vitest-pool-workers/src/worker/node/console.ts index d3f69e0b9747..1ec811d02f3c 100644 --- a/packages/vitest-pool-workers/src/worker/node/console.ts +++ b/packages/vitest-pool-workers/src/worker/node/console.ts @@ -47,7 +47,7 @@ export class Console { } // Vitest expects this function to be called `value`: - // https://github.com/vitest-dev/vitest/blob/v1.0.0-beta.5/packages/vitest/src/runtime/console.ts#L16 + // https://github.com/vitest-dev/vitest/blob/v4.0.18/packages/vitest/src/runtime/console.ts#L19 value(stream: Writable, data: unknown[]): void { stream.write(formatWithOptions(this.#inspectOptions, ...data) + "\n"); } diff --git a/packages/vitest-pool-workers/src/worker/types-ambient.d.ts b/packages/vitest-pool-workers/src/worker/types-ambient.d.ts index e03ebd1d7374..7d2266d64f37 100644 --- a/packages/vitest-pool-workers/src/worker/types-ambient.d.ts +++ b/packages/vitest-pool-workers/src/worker/types-ambient.d.ts @@ -6,20 +6,18 @@ interface UnsafeEval { newAsyncFunction(script: string, name?: string, ...args: string[]): Function; } -// https://github.com/cloudflare/workerd/blob/v1.20240223.0/src/workerd/api/actor.h#L26 -interface EphemeralObjectNamespace { - get(id: Id): Fetcher; +namespace Cloudflare { + interface Env extends Record { + __VITEST_POOL_WORKERS_LOOPBACK_SERVICE: Fetcher; + __VITEST_POOL_WORKERS_UNSAFE_EVAL: UnsafeEval; + } + interface GlobalProps { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + mainModule: typeof import("./index"); + durableNamespaces: "__VITEST_POOL_WORKERS_RUNNER_DURABLE_OBJECT__"; + } } -interface Env { - __VITEST_POOL_WORKERS_SELF_NAME: string; - __VITEST_POOL_WORKERS_SELF_SERVICE: Fetcher; - __VITEST_POOL_WORKERS_LOOPBACK_SERVICE: Fetcher; - __VITEST_POOL_WORKERS_RUNNER_OBJECT: EphemeralObjectNamespace<"singleton">; - __VITEST_POOL_WORKERS_UNSAFE_EVAL: UnsafeEval; -} -type InternalUserEnv = Env & Record; - interface DurableObjectDesignator { className: string; scriptName?: string; @@ -33,7 +31,7 @@ interface SerializedOptions { string /* bound name */, DurableObjectDesignator >; - isolatedStorage?: boolean; + selfName?: string; } declare module "__VITEST_POOL_WORKERS_USER_OBJECT" {} diff --git a/packages/vitest-pool-workers/src/worker/types-globals.d.ts b/packages/vitest-pool-workers/src/worker/types-globals.d.ts index c0cf35f7f48f..2c6db96357d5 100644 --- a/packages/vitest-pool-workers/src/worker/types-globals.d.ts +++ b/packages/vitest-pool-workers/src/worker/types-globals.d.ts @@ -1,11 +1,15 @@ // noinspection ES6ConvertVarToLetConst -import type { WorkerGlobalState } from "vitest"; +import type { VitestUtils, WorkerGlobalState } from "vitest"; declare global { - // https://github.com/vitest-dev/vitest/blob/v1.0.0-beta.5/packages/vitest/src/runtime/worker.ts#L49-L69 - const __vitest_worker__: WorkerGlobalState & { - config?: { poolOptions?: { workers?: SerializedOptions } }; + // https://github.com/vitest-dev/vitest/blob/v4.0.18/packages/vitest/src/runtime/utils.ts#L24 + const __vitest_worker__: WorkerGlobalState; + // https://github.com/vitest-dev/vitest/blob/v4.0.18/packages/vitest/src/runtime/moduleRunner/moduleRunner.ts#L86 + const __vitest_mocker__: VitestUtils & { + moduleRunner: { + import: (id: string) => Promise>; + }; }; // Original, un-patched console that always logs directly to stdout/err, // without call site annotations diff --git a/packages/vitest-pool-workers/src/worker/workflows.ts b/packages/vitest-pool-workers/src/worker/workflows.ts index ec3cdc37448f..220f7e25de64 100644 --- a/packages/vitest-pool-workers/src/worker/workflows.ts +++ b/packages/vitest-pool-workers/src/worker/workflows.ts @@ -2,8 +2,8 @@ import { instanceStatusName, InstanceStatus as InstanceStatusNumber, } from "@cloudflare/workflows-shared/src/instance"; +import { env } from "cloudflare:workers"; import { runInRunnerObject } from "./durable-objects"; -import { env, internalEnv } from "./env"; import type { WorkflowBinding } from "@cloudflare/workflows-shared/src/binding"; import type { StepSelector, @@ -148,12 +148,11 @@ export async function introspectWorkflow( const instanceIntrospectors: WorkflowInstanceIntrospector[] = []; const bindingName = await workflow.unsafeGetBindingName(); - const internalOriginalWorkflow = internalEnv[bindingName] as Workflow; - const externalOriginalWorkflow = env[bindingName] as Workflow; + const originalWorkflow = env[bindingName] as Workflow; const introspectAndModifyInstance = async (instanceId: string) => { try { - await runInRunnerObject(internalEnv, async () => { + await runInRunnerObject(async () => { const introspector = await introspectWorkflowInstance( workflow, instanceId @@ -224,18 +223,15 @@ export async function introspectWorkflow( }; const dispose = () => { - internalEnv[bindingName] = internalOriginalWorkflow; - env[bindingName] = externalOriginalWorkflow; + env[bindingName] = originalWorkflow; }; // Create a single handler instance to be reused const proxyGetHandler = createWorkflowProxyGetHandler(); // Apply the proxies using the shared handler logic - internalEnv[bindingName] = new Proxy(internalOriginalWorkflow, { - get: proxyGetHandler, - }); - env[bindingName] = new Proxy(externalOriginalWorkflow, { + + env[bindingName] = new Proxy(originalWorkflow, { get: proxyGetHandler, }); diff --git a/packages/vitest-pool-workers/test/bindings.test.ts b/packages/vitest-pool-workers/test/bindings.test.ts index 8714352288f0..d9f25ecba49b 100644 --- a/packages/vitest-pool-workers/test/bindings.test.ts +++ b/packages/vitest-pool-workers/test/bindings.test.ts @@ -1,25 +1,16 @@ import dedent from "ts-dedent"; -import { test } from "./helpers"; +import { test, vitestConfig } from "./helpers"; test("hello_world support", async ({ expect, seed, vitestRun }) => { await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - singleWorker: true, - wrangler: { configPath: "./wrangler.jsonc" }, - }, - }, - } - }); - `, + "vitest.config.mts": vitestConfig({ + wrangler: { configPath: "./wrangler.jsonc" }, + }), "wrangler.jsonc": dedent` { "name": "test-worker", - "compatibility_date": "2025-01-01", + "compatibility_date": "2025-12-02", + "compatibility_flags": ["nodejs_compat"], "unsafe_hello_world": [ { "binding": "HELLO_WORLD", diff --git a/packages/vitest-pool-workers/test/chunking.test.ts b/packages/vitest-pool-workers/test/chunking.test.ts index 1ca2855a0aee..333cabd7272f 100644 --- a/packages/vitest-pool-workers/test/chunking.test.ts +++ b/packages/vitest-pool-workers/test/chunking.test.ts @@ -1,5 +1,5 @@ import dedent from "ts-dedent"; -import { test } from "./helpers"; +import { test, vitestConfig } from "./helpers"; test("chunks large WebSocket messages bi-directionally", async ({ expect, @@ -10,25 +10,13 @@ test("chunks large WebSocket messages bi-directionally", async ({ const bigText = "xyz".repeat(400_000); await seed({ "big.txt": bigText, - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - modulesRules: [ - { type: "Text", include: ["**/*.txt"] } - ] - }, - }, - }, - } - }); - `, + "vitest.config.mts": vitestConfig({ + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + modulesRules: [{ type: "Text", include: ["**/*.txt"] }], + }, + }), "index.test.ts": dedent` import text from "./big.txt"; import { it } from "vitest"; diff --git a/packages/vitest-pool-workers/test/console.test.ts b/packages/vitest-pool-workers/test/console.test.ts index 14094adb5dcb..ede1d6340ecd 100644 --- a/packages/vitest-pool-workers/test/console.test.ts +++ b/packages/vitest-pool-workers/test/console.test.ts @@ -1,11 +1,11 @@ import dedent from "ts-dedent"; -import { minimalVitestConfig, test, waitFor } from "./helpers"; +import { test, vitestConfig, waitFor } from "./helpers"; test.skipIf(process.platform === "win32")( "console.log()s include correct source-mapped locations", async ({ expect, seed, vitestDev }) => { await seed({ - "vitest.config.mts": minimalVitestConfig, + "vitest.config.mts": vitestConfig(), "index.test.ts": dedent` import { describe, it } from "vitest"; console.log("global"); @@ -52,15 +52,14 @@ test.skipIf(process.platform === "win32")( } ); -test("handles detatched console methods", async ({ +test("handles detached console methods", async ({ expect, seed, - vitestDev, + vitestRun, }) => { await seed({ - "vitest.config.mts": minimalVitestConfig, + "vitest.config.mts": vitestConfig(), "index.test.ts": dedent` - import { SELF } from "cloudflare:test"; import { expect, it } from "vitest"; it("does not crash when using a detached console method", async () => { const fn = console["debug"]; @@ -69,8 +68,8 @@ test("handles detatched console methods", async ({ }); `, }); - const result = vitestDev(); - expect(result.stderr).toMatch(""); + const result = await vitestRun(); + expect(await result.exitCode).toBe(0); }); test("console.logs() inside `export default`ed handlers with SELF", async ({ @@ -79,23 +78,14 @@ test("console.logs() inside `export default`ed handlers with SELF", async ({ vitestRun, }) => { await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - main: "./index.ts", - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - }, - }, - } - }); - `, + "vitest.config.mts": vitestConfig({ + main: "./index.ts", + singleWorker: true, + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + }, + }), "index.ts": dedent` export default { fetch() { diff --git a/packages/vitest-pool-workers/test/filtering.test.ts b/packages/vitest-pool-workers/test/filtering.test.ts index d999688eaedb..75d691bbb3af 100644 --- a/packages/vitest-pool-workers/test/filtering.test.ts +++ b/packages/vitest-pool-workers/test/filtering.test.ts @@ -1,5 +1,5 @@ import dedent from "ts-dedent"; -import { minimalVitestConfig, test } from "./helpers"; +import { test, vitestConfig } from "./helpers"; test("filter test suite by pattern includes non-ascii string", async ({ expect, @@ -7,7 +7,7 @@ test("filter test suite by pattern includes non-ascii string", async ({ vitestRun, }) => { await seed({ - "vitest.config.mts": minimalVitestConfig, + "vitest.config.mts": vitestConfig(), "index.test.ts": dedent` import { it, expect } from "vitest"; diff --git a/packages/vitest-pool-workers/test/global-setup.ts b/packages/vitest-pool-workers/test/global-setup.ts index ffc2ec731f1b..9d571a06cb47 100644 --- a/packages/vitest-pool-workers/test/global-setup.ts +++ b/packages/vitest-pool-workers/test/global-setup.ts @@ -6,14 +6,14 @@ import path from "node:path"; import { startMockNpmRegistry } from "@cloudflare/mock-npm-registry"; import { removeDir } from "@cloudflare/workers-utils"; import { version } from "../package.json"; -import type { GlobalSetupContext } from "vitest/node"; +import type { TestProject } from "vitest/node"; const repoRoot = path.resolve(__dirname, "../../.."); const packagesRoot = path.resolve(repoRoot, "packages"); // Using a global setup means we can modify tests without having to re-install // packages into our temporary directory -export default async function ({ provide }: GlobalSetupContext) { +export default async function ({ provide }: TestProject) { const stop = await startMockNpmRegistry("@cloudflare/vitest-pool-workers"); // Create temporary directory diff --git a/packages/vitest-pool-workers/test/helpers.ts b/packages/vitest-pool-workers/test/helpers.ts index b85a933c9030..77bb3575f0cd 100644 --- a/packages/vitest-pool-workers/test/helpers.ts +++ b/packages/vitest-pool-workers/test/helpers.ts @@ -7,26 +7,41 @@ import util from "node:util"; import { removeDir } from "@cloudflare/workers-utils"; import { stripAnsi } from "miniflare"; import treeKill from "tree-kill"; +import dedent from "ts-dedent"; import { test as baseTest, inject, vi } from "vitest"; const debuglog = util.debuglog("vitest-pool-workers:test"); -export const minimalVitestConfig = ` -import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; -export default defineWorkersConfig({ - test: { - testTimeout: 90_000, - poolOptions: { - workers: { - singleWorker: true, +export const vitestConfig = ( + cfOptions: Record = {}, + testOptions: Record = {} +) => dedent/* javascript */ ` + import { cloudflareTest } from "@cloudflare/vitest-pool-workers" + + import { BaseSequencer } from "vitest/node"; + + class DeterministicSequencer extends BaseSequencer { + sort(files) { + return [...files].sort((a, b) => a.moduleId.localeCompare(b.moduleId)); + } + } + + export default { + plugins: [ + cloudflareTest({ miniflare: { - compatibilityDate: "2024-01-01", + compatibilityDate: "2025-12-02", compatibilityFlags: ["nodejs_compat"], }, - }, - }, - } -}); + ...${JSON.stringify(cfOptions)} + }) + ], + test: { + sequence: { sequencer: DeterministicSequencer }, + testTimeout: 90_000, + ...${JSON.stringify(testOptions)} + } + }; `; export function waitFor(callback: Parameters>[0]) { diff --git a/packages/vitest-pool-workers/test/inspector.test.ts b/packages/vitest-pool-workers/test/inspector.test.ts index 6b862d94a629..18c31568945d 100644 --- a/packages/vitest-pool-workers/test/inspector.test.ts +++ b/packages/vitest-pool-workers/test/inspector.test.ts @@ -1,6 +1,6 @@ import net from "node:net"; import dedent from "ts-dedent"; -import { test } from "./helpers"; +import { test, vitestConfig } from "./helpers"; /** * Try to create a server that blocks a specific port. @@ -53,23 +53,13 @@ test("opens an inspector with the `--inspect` argument", async ({ vitestRun, }) => { await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - main: "./index.ts", - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - }, - }, - } - }); - `, + "vitest.config.mts": vitestConfig({ + main: "./index.ts", + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + }, + }), "index.ts": dedent` export default { async fetch(request, env, ctx) { @@ -94,33 +84,21 @@ test("opens an inspector with the `--inspect` argument", async ({ flags: ["--inspect", "--no-file-parallelism"], }); - expect(result.stdout).toMatch("inspector on port 9229"); + expect(result.stdout).toMatch("inspector on port"); }); test("customize inspector config", async ({ expect, seed, vitestRun }) => { await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - inspector: { - // Test if this overrides the inspector port - port: 3456, - }, - poolOptions: { - workers: { - main: "./index.ts", - // Test if we warn and override the singleWorker option when the inspector is open - singleWorker: false, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - }, - }, - } - }); - `, + "vitest.config.mts": vitestConfig( + { + main: "./index.ts", + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + }, + }, + { inspector: { port: 3456 } } + ), "index.ts": dedent` export default { async fetch(request, env, ctx) { @@ -145,11 +123,6 @@ test("customize inspector config", async ({ expect, seed, vitestRun }) => { flags: ["--inspect-brk", "--no-file-parallelism"], }); - expect(result.stdout).toMatch( - "Tests run in singleWorker mode when the inspector is open." - ); - expect(result.stdout).toMatch(`The "--inspect-brk" flag is not supported.`); - expect(result.stdout).toMatch("Starting single runtime"); expect(result.stdout).toMatch("inspector on port 3456"); }); @@ -161,23 +134,13 @@ test("uses next available port when default port 9229 is in use", async ({ const blockingServer = await tryBlockPort(9229); try { await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - main: "./index.ts", - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - }, - }, - } - }); - `, + "vitest.config.mts": vitestConfig({ + main: "./index.ts", + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + }, + }), "index.ts": dedent` export default { async fetch(request, env, ctx) { @@ -234,26 +197,21 @@ test("throws error when user-specified inspector port is not available", async ( await createEphemeralServer(); try { await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - inspector: { - port: ${blockedPort}, - }, - poolOptions: { - workers: { - main: "./index.ts", - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - }, - }, - } - }); - `, + "vitest.config.mts": vitestConfig( + { + main: "./index.ts", + singleWorker: true, + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + }, + }, + { + inspector: { + port: blockedPort, + }, + } + ), "index.ts": dedent` export default { async fetch(request, env, ctx) { diff --git a/packages/vitest-pool-workers/test/isolation.test.ts b/packages/vitest-pool-workers/test/isolation.test.ts index f0edd2a6083e..d6085a816281 100644 --- a/packages/vitest-pool-workers/test/isolation.test.ts +++ b/packages/vitest-pool-workers/test/isolation.test.ts @@ -1,16 +1,5 @@ import dedent from "ts-dedent"; -import { test } from "./helpers"; - -// Sequencer that always runs tests alphabetically by name -const deterministicSequencer = ` -import { BaseSequencer } from "vitest/node"; - -export class DeterministicSequencer extends BaseSequencer { - sort(files) { - return [...files].sort((a, b) => a[1].localeCompare(b[1])); - } -} -`; +import { test, vitestConfig } from "./helpers"; test( "isolated storage with multiple workers", @@ -19,93 +8,60 @@ test( // Check unique global scopes, storage isolated, and unique auxiliaries: // https://developers.cloudflare.com/workers/testing/vitest-integration/isolation-and-concurrency/#isolatedstorage-true-singleworker-false-default await seed({ - "sequencer.ts": deterministicSequencer, - "auxiliary.mjs": dedent` - let count = 0; - export default { - fetch() { - return new Response(++count); + "auxiliary.mjs": dedent/* javascript */ ` + let count = 0; + export default { + fetch() { + return new Response(++count); + } } - } - `, - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - import { DeterministicSequencer } from "./sequencer.ts"; - - export default defineWorkersConfig({ - test: { - sequence: { sequencer: DeterministicSequencer }, - poolOptions: { - workers: { - isolatedStorage: true, - singleWorker: false, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - kvNamespaces: ["NAMESPACE"], - serviceBindings: { AUXILIARY: "auxiliary" }, - workers: [ - { - name: "auxiliary", - modules: true, - scriptPath: "auxiliary.mjs" - } - ] - }, + `, + "vitest.config.mts": vitestConfig({ + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + kvNamespaces: ["NAMESPACE"], + serviceBindings: { AUXILIARY: "auxiliary" }, + workers: [ + { + name: "auxiliary", + modules: true, + scriptPath: "auxiliary.mjs", }, - }, - } - }); - `, - "a.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it("does something", async () => { - expect(globalThis.THING).toBe(undefined); - globalThis.THING = true; - - expect(await env.NAMESPACE.get("key")).toBe(null); - await env.NAMESPACE.put("key", "value"); - - const response = await env.AUXILIARY.fetch("https://example.com"); - expect(await response.text()).toBe("1"); - }); - `, - "b.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it("does something else", async () => { - expect(globalThis.THING).toBe(undefined); - globalThis.THING = true; - - expect(await env.NAMESPACE.get("key")).toBe(null); - await env.NAMESPACE.put("key", "value"); - - const response = await env.AUXILIARY.fetch("https://example.com"); - expect(await response.text()).toBe("1"); - }); - `, + ], + }, + }), + "a.test.ts": dedent/* javascript */ ` + import { env } from "cloudflare:test"; + import { it, expect } from "vitest"; + it("does something", async () => { + expect(globalThis.THING).toBe(undefined); + globalThis.THING = true; + + expect(await env.NAMESPACE.get("key")).toBe(null); + await env.NAMESPACE.put("key", "value"); + + const response = await env.AUXILIARY.fetch("https://example.com"); + expect(await response.text()).toBe("1"); + }); + `, + "b.test.ts": dedent/* javascript */ ` + import { env } from "cloudflare:test"; + import { it, expect } from "vitest"; + it("does something else", async () => { + expect(globalThis.THING).toBe(undefined); + globalThis.THING = true; + + expect(await env.NAMESPACE.get("key")).toBe(null); + await env.NAMESPACE.put("key", "value"); + + const response = await env.AUXILIARY.fetch("https://example.com"); + expect(await response.text()).toBe("1"); + }); + `, }); - let result = await vitestRun(); + const result = await vitestRun(); expect(await result.exitCode).toBe(0); - - // Check prohibits concurrent tests - await seed({ - "b.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it.concurrent("does something else", () => {}); - `, - }); - result = await vitestRun(); - expect(await result.exitCode).toBe(1); - const expected = dedent` - Error: Concurrent tests are unsupported with isolated storage. Please either: - - Remove \`.concurrent\` from the "does something else" test - - Remove \`.concurrent\` from all \`describe()\` blocks containing the "does something else" test - - Remove \`isolatedStorage: true\` from your project's Vitest config - `; - expect(result.stderr).toMatch(expected); } ); @@ -116,7 +72,6 @@ test( // Check shared global scope, storage isolated, and shared auxiliaries: // https://developers.cloudflare.com/workers/testing/vitest-integration/isolation-and-concurrency/#isolatedstorage-true-singleworker-true await seed({ - "sequencer.ts": deterministicSequencer, "auxiliary.mjs": dedent` let count = 0; export default { @@ -125,189 +80,46 @@ test( } } `, - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - import { DeterministicSequencer } from "./sequencer.ts"; - - export default defineWorkersConfig({ - test: { - sequence: { sequencer: DeterministicSequencer }, - poolOptions: { - workers: { - isolatedStorage: true, - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - kvNamespaces: ["NAMESPACE"], - serviceBindings: { AUXILIARY: "auxiliary" }, - workers: [ - { - name: "auxiliary", - modules: true, - scriptPath: "auxiliary.mjs" - } - ] - }, - }, - }, - } - }); - `, - "a.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it("does something", async () => { - expect(globalThis.THING).toBe(undefined); - globalThis.THING = true; - - expect(await env.NAMESPACE.get("key")).toBe(null); - await env.NAMESPACE.put("key", "value"); - - const response = await env.AUXILIARY.fetch("https://example.com"); - expect(await response.text()).toBe("1"); - }); - `, - "b.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it("does something else", async () => { - expect(globalThis.THING).toBe(true); - - expect(await env.NAMESPACE.get("key")).toBe(null); - - const response = await env.AUXILIARY.fetch("https://example.com"); - expect(await response.text()).toBe("2"); - }); - `, - }); - let result = await vitestRun(); - expect(await result.exitCode).toBe(0); - - // Check prohibits concurrent tests - await seed({ - "b.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it.concurrent("does something else", () => {}); - `, - }); - result = await vitestRun(); - expect(await result.exitCode).toBe(1); - const expected = dedent` - Error: Concurrent tests are unsupported with isolated storage. Please either: - - Remove \`.concurrent\` from the "does something else" test - - Remove \`.concurrent\` from all \`describe()\` blocks containing the "does something else" test - - Remove \`isolatedStorage: true\` from your project's Vitest config - `; - expect(result.stderr).toMatch(expected); - } -); - -test( - "shared storage with multiple workers", - { timeout: 30_000 }, - async ({ expect, seed, vitestRun }) => { - // Check unique global scopes, storage shared, and shared auxiliaries: - // https://developers.cloudflare.com/workers/testing/vitest-integration/isolation-and-concurrency/#isolatedstorage-false-singleworker-false - await seed({ - "sequencer.ts": deterministicSequencer, - "auxiliary.mjs": dedent` - export class SyncObject { - #resolves = []; - fetch(request) { - const deferred = Promise.withResolvers(); - this.#resolves.push(deferred.resolve); - if (this.#resolves.length === 2) { - for (const resolve of this.#resolves) resolve(new Response()); - } - return deferred.promise; - } - } - - let id; - export default { - fetch(request, env, ctx) { - id ??= env.SYNC.newUniqueId(); - const stub = env.SYNC.get(id); - return stub.fetch(request); - } - } - `, - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - import { DeterministicSequencer } from "./sequencer.ts"; - - export default defineWorkersConfig({ - test: { - sequence: { sequencer: DeterministicSequencer }, - poolOptions: { - workers: { - isolatedStorage: false, - singleWorker: false, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - kvNamespaces: ["NAMESPACE"], - serviceBindings: { AUXILIARY: "auxiliary" }, - workers: [ - { - name: "auxiliary", - modules: true, - scriptPath: "auxiliary.mjs", - durableObjects: { SYNC: "SyncObject" } - } - ] - }, + "vitest.config.mts": vitestConfig({ + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + kvNamespaces: ["NAMESPACE"], + serviceBindings: { AUXILIARY: "auxiliary" }, + workers: [ + { + name: "auxiliary", + modules: true, + scriptPath: "auxiliary.mjs", }, - }, - } - }); - `, - "a.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it("does something", async () => { - globalThis.A_THING = true; - await env.NAMESPACE.put("a", "1"); - - await env.AUXILIARY.fetch("https://example.com"); - - expect(globalThis.B_THING).toBe(undefined); - expect(await env.NAMESPACE.get("b")).toBe("2"); - }); - `, - "b.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it("does something else", async () => { - globalThis.B_THING = true; - await env.NAMESPACE.put("b", "2"); - - await env.AUXILIARY.fetch("https://example.com"); - - expect(globalThis.A_THING).toBe(undefined); - expect(await env.NAMESPACE.get("a")).toBe("1"); - }); - `, + ], + }, + }), + "a.test.ts": dedent/* javascript */ ` + import { env } from "cloudflare:test"; + import { it, expect } from "vitest"; + it("does something", async () => { + expect(await env.NAMESPACE.get("key")).toBe(null); + await env.NAMESPACE.put("key", "value"); + + const response = await env.AUXILIARY.fetch("https://example.com"); + expect(await response.text()).toBe("1"); + }); + `, + "b.test.ts": dedent/* javascript */ ` + import { env } from "cloudflare:test"; + import { it, expect } from "vitest"; + it("does something else", async () => { + expect(await env.NAMESPACE.get("key")).toBe(null); + + const response = await env.AUXILIARY.fetch("https://example.com"); + expect(await response.text()).toBe("1"); + }); + `, }); - let result = await vitestRun(); - expect(await result.exitCode).toBe(0); - - // Check allows concurrent tests - await seed({ - "a.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it.concurrent("does something", () => {}); - `, - "b.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it.concurrent("does something else", () => {}); - `, + const result = await vitestRun({ + flags: ["--max-workers=1"], }); - result = await vitestRun(); expect(await result.exitCode).toBe(0); } ); @@ -316,91 +128,61 @@ test( "shared storage with single worker", { timeout: 60_000 }, async ({ expect, seed, vitestRun }) => { - // Check shared global scopes, storage shared, and shared auxiliaries: - // https://developers.cloudflare.com/workers/testing/vitest-integration/isolation-and-concurrency/#isolatedstorage-false-singleworker-true + // With --no-isolate --max-workers=1, test files share globals, storage, and auxiliaries await seed({ - "sequencer.ts": deterministicSequencer, - "auxiliary.mjs": dedent` - let count = 0; - export default { - fetch() { - return new Response(++count); + "auxiliary.mjs": dedent/* javascript */ ` + let count = 0; + export default { + fetch() { + return new Response(++count); + } } - } - `, - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - import { DeterministicSequencer } from "./sequencer.ts"; - - export default defineWorkersConfig({ - test: { - sequence: { sequencer: DeterministicSequencer }, - poolOptions: { - workers: { - isolatedStorage: false, - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - kvNamespaces: ["NAMESPACE"], - serviceBindings: { AUXILIARY: "auxiliary" }, - workers: [ - { - name: "auxiliary", - modules: true, - scriptPath: "auxiliary.mjs" - } - ] - }, + `, + "vitest.config.mts": vitestConfig({ + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + kvNamespaces: ["NAMESPACE"], + serviceBindings: { AUXILIARY: "auxiliary" }, + workers: [ + { + name: "auxiliary", + modules: true, + scriptPath: "auxiliary.mjs", }, - }, - } - }); - `, - "a.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it("does something", async () => { - expect(globalThis.THING).toBe(undefined); - globalThis.THING = true; - - expect(await env.NAMESPACE.get("key")).toBe(null); - await env.NAMESPACE.put("key", "value"); - - const response = await env.AUXILIARY.fetch("https://example.com"); - expect(await response.text()).toBe("1"); - }); - `, - "b.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it("does something else", async () => { - expect(globalThis.THING).toBe(true); - - expect(await env.NAMESPACE.get("key")).toBe("value"); - - const response = await env.AUXILIARY.fetch("https://example.com"); - expect(await response.text()).toBe("2"); - }); - `, + ], + }, + }), + "a.test.ts": dedent/* javascript */ ` + import { env } from "cloudflare:test"; + import { it, expect } from "vitest"; + it("does something", async () => { + expect(globalThis.THING).toBe(undefined); + globalThis.THING = true; + + expect(await env.NAMESPACE.get("key")).toBe(null); + await env.NAMESPACE.put("key", "value"); + + const response = await env.AUXILIARY.fetch("https://example.com"); + expect(await response.text()).toBe("1"); + }); + `, + "b.test.ts": dedent/* javascript */ ` + import { env } from "cloudflare:test"; + import { it, expect } from "vitest"; + it("does something else", async () => { + expect(globalThis.THING).toBe(true); + + expect(await env.NAMESPACE.get("key")).toBe("value"); + + const response = await env.AUXILIARY.fetch("https://example.com"); + expect(await response.text()).toBe("2"); + }); + `, }); - let result = await vitestRun(); - expect(await result.exitCode).toBe(0); - - // Check allows concurrent tests - await seed({ - "a.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it.concurrent("does something", () => {}); - `, - "b.test.ts": dedent` - import { env } from "cloudflare:test"; - import { it, expect } from "vitest"; - it.concurrent("does something else", () => {}); - `, + const result = await vitestRun({ + flags: ["--no-isolate", "--max-workers=1"], }); - result = await vitestRun(); expect(await result.exitCode).toBe(0); } ); diff --git a/packages/vitest-pool-workers/test/snapshots.test.ts b/packages/vitest-pool-workers/test/snapshots.test.ts index 5286b8bc312b..5588ff0d9dc6 100644 --- a/packages/vitest-pool-workers/test/snapshots.test.ts +++ b/packages/vitest-pool-workers/test/snapshots.test.ts @@ -2,7 +2,7 @@ import { existsSync } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; import dedent from "ts-dedent"; -import { minimalVitestConfig, test } from "./helpers"; +import { test, vitestConfig } from "./helpers"; test( "disk snapshots", @@ -10,7 +10,7 @@ test( async ({ expect, seed, vitestRun, tmpPath }) => { // Check writes new snapshots await seed({ - "vitest.config.mts": minimalVitestConfig, + "vitest.config.mts": vitestConfig(), "index.test.ts": dedent` import { it, expect } from "vitest"; it("matches snapshot", () => { @@ -118,7 +118,7 @@ test.skipIf(process.platform === "win32")( async ({ expect, seed, vitestRun, tmpPath }) => { // Check writes new snapshots await seed({ - "vitest.config.mts": minimalVitestConfig, + "vitest.config.mts": vitestConfig(), "index.test.ts": dedent` import { it, expect } from "vitest"; it("matches snapshot", () => { @@ -148,7 +148,7 @@ test.skipIf(process.platform === "win32")( // Check fails if snapshots differ await seed({ - "vitest.config.mts": minimalVitestConfig, + "vitest.config.mts": vitestConfig(), "index.test.ts": dedent` import { it, expect } from "vitest"; it("matches snapshot", () => { diff --git a/packages/vitest-pool-workers/test/validation.test.ts b/packages/vitest-pool-workers/test/validation.test.ts index 745f9b7f7c54..51122c1a15e3 100644 --- a/packages/vitest-pool-workers/test/validation.test.ts +++ b/packages/vitest-pool-workers/test/validation.test.ts @@ -1,6 +1,6 @@ import path from "node:path"; import dedent from "ts-dedent"; -import { test } from "./helpers"; +import { test, vitestConfig } from "./helpers"; test( "formats config validation errors", @@ -10,81 +10,45 @@ test( // Check top-level options validated await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - singleWorker: 42, - isolatedStorage: "yes please", - miniflare: [], - wrangler: "./wrangler.toml" - }, - }, - } - }); - `, + "vitest.config.mts": vitestConfig({ + miniflare: [], + wrangler: "./wrangler.toml", + }), "index.test.ts": "", }); let result = await vitestRun(); expect(await result.exitCode).toBe(1); let expected = dedent` - TypeError: Unexpected pool options in project ${path.join(tmpPathName, "vitest.config.mts")}: - { - test: { - poolOptions: { - workers: { - singleWorker: 42, - ^ Expected boolean, received number - isolatedStorage: 'yes please', - ^ Expected boolean, received string - miniflare: [], - ^ Expected object, received array - wrangler: './wrangler.toml', - ^ Expected object, received string - }, - }, - }, - } - `.replaceAll("\t", " "); + TypeError: Unexpected options in project ${path.join(tmpPathName, "vitest.config.mts")}: + { + miniflare: [], + ^ Expected object, received array + wrangler: './wrangler.toml', + ^ Expected object, received string + } + `; expect(result.stderr).toMatch(expected); // Check `miniflare` options validated with correct error paths await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - miniflare: { - compatibilityDate: { year: 2024, month: 1, day: 1 } - }, - }, - }, - } - }); - `, + "vitest.config.mts": vitestConfig({ + miniflare: { + compatibilityDate: { year: 2024, month: 1, day: 1 }, + }, + }), "index.test.ts": "", }); result = await vitestRun(); expect(await result.exitCode).toBe(1); expected = dedent` - TypeError: Unexpected pool options in project ${path.join(tmpPathName, "vitest.config.mts")}: - { - test: { - poolOptions: { - workers: { - miniflare: { - compatibilityDate: { year: 2024, month: 1, day: 1 }, - ^ Expected string, received object - }, - }, - }, - }, - } - `.replaceAll("\t", " "); + TypeError: Unexpected options in project ${path.join(tmpPathName, "vitest.config.mts")}: + { + miniflare: { + compatibilityDate: { year: 2024, month: 1, day: 1 }, + ^ Expected string, received object + }, + } + `; expect(result.stderr).toMatch(expected); } ); @@ -97,69 +61,49 @@ test( // Check with no entrypoint await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - }, - }, - } - }); - `, - "index.test.ts": dedent` - import { SELF } from "cloudflare:test"; - import { it, expect } from "vitest"; - it("sends request", async () => { - const response = await SELF.fetch("https://example.com/"); - expect(response.ok).toBe(true); - }); - `, + "vitest.config.mts": vitestConfig({ + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + }, + }), + "index.test.ts": dedent/* javascript */ ` + import { SELF } from "cloudflare:test"; + import { it, expect } from "vitest"; + it("sends request", async () => { + const response = await SELF.fetch("https://example.com/"); + expect(response.ok).toBe(true); + }); + `, }); let result = await vitestRun(); expect(await result.exitCode).toBe(1); let expected = dedent` - Error: Using service bindings to the current worker requires \`poolOptions.workers.main\` to be set to your worker's entrypoint - `; + Error: Using service bindings to the current worker requires \`poolOptions.workers.main\` to be set to your worker's entrypoint + `; expect(result.stderr).toMatch(expected); // Check with service worker await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - main: "./index.ts", - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - }, - }, - } - }); - `, - "index.ts": dedent` - addEventListener("fetch", (event) => { - event.respondWith(new Response("body")); - }); - `, + "vitest.config.mts": vitestConfig({ + main: "./index.ts", + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + }, + }), + "index.ts": dedent/* javascript */ ` + addEventListener("fetch", (event) => { + event.respondWith(new Response("body")); + }); + `, }); result = await vitestRun(); expect(await result.exitCode).toBe(1); expected = dedent` - ${path.join(tmpPathName, "index.ts")} does not export a default entrypoint. \`@cloudflare/vitest-pool-workers\` does not support service workers or named entrypoints for \`SELF\`. - If you're using service workers, please migrate to the modules format: https://developers.cloudflare.com/workers/reference/migrate-to-module-workers. - `; + ${path.join(tmpPathName, "index.ts")} does not export a default entrypoint. \`@cloudflare/vitest-pool-workers\` does not support service workers or named entrypoints for \`SELF\`. + If you're using service workers, please migrate to the modules format: https://developers.cloudflare.com/workers/reference/migrate-to-module-workers. + `; expect(result.stderr).toMatch(expected); } ); diff --git a/packages/vitest-pool-workers/test/watch.test.ts b/packages/vitest-pool-workers/test/watch.test.ts index 3f3cc870b8f8..c2d0bf5fc58f 100644 --- a/packages/vitest-pool-workers/test/watch.test.ts +++ b/packages/vitest-pool-workers/test/watch.test.ts @@ -1,204 +1,8 @@ import dedent from "ts-dedent"; -import { minimalVitestConfig, test, waitFor } from "./helpers"; +import { test, vitestConfig, waitFor } from "./helpers"; -const durableObjectWorker = dedent` - export class Counter { - count = 0; - - constructor(readonly state: DurableObjectState) { - void state.blockConcurrencyWhile(async () => { - this.count = (await state.storage.get("count")) ?? 0; - }); - } - - fetch(request: Request) { - this.count++; - void this.state.storage.put("count", this.count); - return new Response(this.count.toString()); - } - } - - export default { - fetch(request: Request, env: any) { - const { pathname } = new URL(request.url); - const id = env.COUNTER.idFromName(pathname); - const stub = env.COUNTER.get(id); - return stub.fetch(request); - } - } -`; - -function makeDurableObjectConfig(isolatedStorage: boolean) { - return dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - main: "./index.ts", - singleWorker: true, - isolatedStorage: ${isolatedStorage}, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - durableObjects: { - COUNTER: "Counter", - }, - }, - }, - }, - } - }); - `; -} - -// when isolatedStorage: true storage be reset at every test -const isolatedStorageTests = dedent` - import { SELF } from "cloudflare:test"; - import { it, expect } from "vitest"; - - it("first test: verifies incrementing works", async () => { - // First fetch returns 1 - let response = await SELF.fetch("https://example.com/test-path"); - expect(await response.text()).toBe("1"); - // Second fetch returns 2 (counter increments) - response = await SELF.fetch("https://example.com/test-path"); - expect(await response.text()).toBe("2"); - // Third fetch returns 3 - response = await SELF.fetch("https://example.com/test-path"); - expect(await response.text()).toBe("3"); - }); - - it("second test: storage is reset, starts at 1 again", async () => { - // With isolatedStorage: true, storage is reset between tests - const response = await SELF.fetch("https://example.com/test-path"); - expect(await response.text()).toBe("1"); - }); - - it("third test: storage is reset, starts at 1 again", async () => { - // With isolatedStorage: true, storage is reset between tests - const response = await SELF.fetch("https://example.com/test-path"); - expect(await response.text()).toBe("1"); - }); -`; - -// when isolatedStorage: false storage should leak in between tests -const sharedStorageTests = dedent` - import { SELF } from "cloudflare:test"; - import { it, expect } from "vitest"; - - it("first increment", async () => { - const response = await SELF.fetch("https://example.com/test-path"); - // First test in a run should always see "1" (storage reset between runs) - expect(await response.text()).toBe("1"); - }); - - it("second increment", async () => { - const response = await SELF.fetch("https://example.com/test-path"); - // With isolatedStorage: false, storage leaks between tests - expect(await response.text()).toBe("2"); - }); - - it("third increment", async () => { - const response = await SELF.fetch("https://example.com/test-path"); - // With isolatedStorage: false, storage leaks between tests - expect(await response.text()).toBe("3"); - }); -`; - -test( - "DO storage is reset between vitest runs (isolatedStorage: true)", - { timeout: 60_000 }, - async ({ expect, seed, vitestRun }) => { - await seed({ - "vitest.config.mts": makeDurableObjectConfig(true), - "index.ts": durableObjectWorker, - "index.test.ts": isolatedStorageTests, - }); - - let result = await vitestRun(); - expect(result.stdout).toMatch(/Tests.*3 passed/s); - expect(await result.exitCode).toBe(0); - - result = await vitestRun(); - expect(result.stdout).toMatch(/Tests.*3 passed/s); - expect(await result.exitCode).toBe(0); - } -); - -test( - "DO storage is reset between vitest runs (isolatedStorage: false)", - { timeout: 60_000 }, - async ({ expect, seed, vitestRun }) => { - await seed({ - "vitest.config.mts": makeDurableObjectConfig(false), - "index.ts": durableObjectWorker, - "index.test.ts": sharedStorageTests, - }); - - let result = await vitestRun(); - expect(result.stdout).toMatch(/Tests.*3 passed/s); - expect(await result.exitCode).toBe(0); - - result = await vitestRun(); - expect(result.stdout).toMatch(/Tests.*3 passed/s); - expect(await result.exitCode).toBe(0); - } -); - -test( - "DO storage is reset in watch mode (isolatedStorage: true)", - { timeout: 50000 }, - async ({ expect, seed, vitestDev }) => { - await seed({ - "vitest.config.mts": makeDurableObjectConfig(true), - "index.ts": durableObjectWorker, - "index.test.ts": isolatedStorageTests, - }); - - const result = vitestDev(); - - await waitFor(() => { - expect(result.stdout).toMatch(/Tests.*3 passed/s); - }); - - await seed({ - "index.test.ts": isolatedStorageTests + "\n// trigger re-run", - }); - - await waitFor(() => { - const matches = result.stdout.match(/Tests\s+3 passed/g); - expect(matches?.length).toBeGreaterThanOrEqual(2); - }); - } -); - -test( - "DO storage is reset in watch mode (isolatedStorage: false)", - { timeout: 50000 }, - async ({ expect, seed, vitestDev }) => { - await seed({ - "vitest.config.mts": makeDurableObjectConfig(false), - "index.ts": durableObjectWorker, - "index.test.ts": sharedStorageTests, - }); - - const result = vitestDev(); - - await waitFor(() => { - expect(result.stdout).toMatch(/Tests.*3 passed/s); - }); - - await seed({ - "index.test.ts": sharedStorageTests + "\n// trigger re-run", - }); - - await waitFor(() => { - const matches = result.stdout.match(/Tests\s+3 passed/g); - expect(matches?.length).toBeGreaterThanOrEqual(2); - }); - } -); +// NOTE: The DO storage isolation tests (isolatedStorage/singleWorker) were removed +// because these features were dropped in the vitest 4 pool rewrite. test("automatically re-runs unit tests", async ({ expect, @@ -206,15 +10,15 @@ test("automatically re-runs unit tests", async ({ vitestDev, }) => { await seed({ - "vitest.config.mts": minimalVitestConfig, - "index.ts": dedent` + "vitest.config.mts": vitestConfig(), + "index.ts": dedent/* javascript */ ` export default { async fetch(request, env, ctx) { return new Response("wrong"); } } `, - "index.test.ts": dedent` + "index.test.ts": dedent/* javascript */ ` import { env, createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { it, expect } from "vitest"; import worker from "./index"; @@ -229,12 +33,12 @@ test("automatically re-runs unit tests", async ({ }); const result = vitestDev(); await waitFor(() => { - expect(result.stdout).toMatch("expected 'wrong' to be 'correct'"); - expect(result.stdout).toMatch("Tests 1 failed"); + expect(result.stderr).toMatch("expected 'wrong' to be 'correct'"); + expect(result.stderr).toMatch("Failed Tests 1"); }); await seed({ - "index.ts": dedent` + "index.ts": dedent/* javascript */ ` export default { async fetch(request, env, ctx) { return new Response("correct"); @@ -253,31 +57,21 @@ test("automatically re-runs integration tests", async ({ vitestDev, }) => { await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - main: "./index.ts", - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - }, - }, - } - }); - `, - "index.ts": dedent` + "vitest.config.mts": vitestConfig({ + main: "./index.ts", + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + }, + }), + "index.ts": dedent/* javascript */ ` export default { async fetch(request, env, ctx) { return new Response("wrong"); } } `, - "index.test.ts": dedent` + "index.test.ts": dedent/* javascript */ ` import { SELF } from "cloudflare:test"; import { it, expect } from "vitest"; it("sends request", async () => { @@ -288,12 +82,12 @@ test("automatically re-runs integration tests", async ({ }); const result = vitestDev(); await waitFor(() => { - expect(result.stdout).toMatch("expected 'wrong' to be 'correct'"); - expect(result.stdout).toMatch("Tests 1 failed"); + expect(result.stderr).toMatch("expected 'wrong' to be 'correct'"); + expect(result.stderr).toMatch("Failed Tests 1"); }); await seed({ - "index.ts": dedent` + "index.ts": dedent/* javascript */ ` export default { async fetch(request, env, ctx) { return new Response("correct"); @@ -312,29 +106,19 @@ test("automatically reset module graph", async ({ vitestDev, }) => { await seed({ - "vitest.config.mts": dedent` - import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; - export default defineWorkersConfig({ - test: { - poolOptions: { - workers: { - main: "./index.ts", - singleWorker: true, - miniflare: { - compatibilityDate: "2024-01-01", - compatibilityFlags: ["nodejs_compat"], - }, - }, - }, - } - }); - `, - "answer.ts": dedent` + "vitest.config.mts": vitestConfig({ + main: "./index.ts", + miniflare: { + compatibilityDate: "2025-12-02", + compatibilityFlags: ["nodejs_compat"], + }, + }), + "answer.ts": dedent/* javascript */ ` export function getAnswer() { return "wrong"; } `, - "index.ts": dedent` + "index.ts": dedent/* javascript */ ` import { getAnswer } from "./answer"; export default { @@ -344,7 +128,7 @@ test("automatically reset module graph", async ({ } } `, - "index.test.ts": dedent` + "index.test.ts": dedent/* javascript */ ` import { env, createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { it, expect, vi } from "vitest"; import worker from "./index"; @@ -371,7 +155,7 @@ test("automatically reset module graph", async ({ // Trigger a re-run by updating the test file with an extra test. await seed({ - "index.test.ts": dedent` + "index.test.ts": dedent/* javascript */ ` import { env, createExecutionContext, waitOnExecutionContext } from "cloudflare:test"; import { it, expect, vi } from "vitest"; import worker from "./index"; diff --git a/packages/vitest-pool-workers/tsconfig.emit.json b/packages/vitest-pool-workers/tsconfig.emit.json deleted file mode 100644 index a029494da0fb..000000000000 --- a/packages/vitest-pool-workers/tsconfig.emit.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "dist", - "noEmit": false, - "declaration": true, - "emitDeclarationOnly": true, - "incremental": false - }, - // Only want to emit `.d.ts` for `/config` sub-export - "include": ["./src/shared/types-global.d.ts", "./src/config/**/*.ts"] -} diff --git a/packages/vitest-pool-workers/tsconfig.json b/packages/vitest-pool-workers/tsconfig.json index 3c1af3f46121..5d92346e0d54 100644 --- a/packages/vitest-pool-workers/tsconfig.json +++ b/packages/vitest-pool-workers/tsconfig.json @@ -4,19 +4,16 @@ "alwaysStrict": true, "forceConsistentCasingInFileNames": true, "module": "esnext", - "lib": ["esnext"], - "paths": { - "@cloudflare/vitest-pool-workers/config": ["./src/config"] - } + "lib": ["esnext"] }, "exclude": ["./scripts/**/rtti.ts"], "include": [ "./scripts/**/*", - "./src/config/**/*.ts", "./src/pool/**/*.ts", "./src/shared/**/*.ts", "./test/**/*.ts", "./vitest.config.*ts", - "./vitest.workspace.ts" + "./vitest.workspace.ts", + "./tsdown.config.ts" ] } diff --git a/packages/vitest-pool-workers/tsdown.config.ts b/packages/vitest-pool-workers/tsdown.config.ts new file mode 100644 index 000000000000..07c59f8ea196 --- /dev/null +++ b/packages/vitest-pool-workers/tsdown.config.ts @@ -0,0 +1,87 @@ +import { readdirSync } from "node:fs"; +import path from "node:path"; +import { defineConfig } from "tsdown"; +import { getBuiltinModules } from "./scripts/rtti/query.mjs"; +import type { UserConfig } from "tsdown"; + +const pkgRoot = path.resolve(import.meta.dirname); + +function* walk(rootPath: string): Generator { + for (const entry of readdirSync(rootPath, { withFileTypes: true })) { + const filePath = path.join(rootPath, entry.name); + if (entry.isDirectory()) { + yield* walk(filePath); + } else { + yield filePath; + } + } +} + +// Build pool, worker and libs +const libPaths = [ + ...walk(path.join(pkgRoot, "src/worker/lib")), + ...walk(path.join(pkgRoot, "src/worker/node")), +]; + +const commonOptions: UserConfig = { + platform: "node", + target: "esnext", + format: "esm", + unbundle: false, + noExternal: ["devalue"], + external: [ + // Cloudflare/workerd built-ins + /^cloudflare:.*$/, + /^workerd:.*$/, + // Virtual/runtime modules + "__VITEST_POOL_WORKERS_DEFINES", + "__VITEST_POOL_WORKERS_USER_OBJECT", + // All npm packages (previously handled by packages: "external") + "cjs-module-lexer", + "esbuild", + "miniflare", + "semver", + "semver/*", + "wrangler", + "zod", + "undici", + "undici/*", + // Peer dependencies + "vitest", + "vitest/*", + "@vitest/runner", + "@vitest/snapshot", + "@vitest/snapshot/*", + ], + sourcemap: true, + outDir: path.join(pkgRoot, "dist"), + ignoreWatch: ["dist"], +}; +export default defineConfig(async () => { + const builtinModules = await getBuiltinModules(); + return [ + { + ...commonOptions, + entry: path.join(pkgRoot, "src", "pool", "index.ts"), + outDir: path.join(pkgRoot, "dist", "pool"), + dts: true, + define: { + VITEST_POOL_WORKERS_DEFINE_BUILTIN_MODULES: + JSON.stringify(builtinModules), + }, + }, + { + ...commonOptions, + entry: [ + path.join(pkgRoot, "src", "worker", "index.ts"), + ...libPaths.filter((libPath) => /\.m?ts$/.test(libPath)), + ], + outDir: path.join(pkgRoot, "dist", "worker"), + dts: false, + define: { + VITEST_POOL_WORKERS_DEFINE_BUILTIN_MODULES: + JSON.stringify(builtinModules), + }, + }, + ]; +}); diff --git a/packages/vitest-pool-workers/types/cloudflare-test.d.ts b/packages/vitest-pool-workers/types/cloudflare-test.d.ts index 7cd833aa920b..002f540029dc 100644 --- a/packages/vitest-pool-workers/types/cloudflare-test.d.ts +++ b/packages/vitest-pool-workers/types/cloudflare-test.d.ts @@ -1,41 +1,18 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ declare module "cloudflare:test" { - // eslint-disable-next-line @typescript-eslint/no-empty-object-type - interface ProvidedEnv {} - /** - * 2nd argument passed to modules-format exported handlers. Contains bindings - * configured in top-level `miniflare` pool options. To configure the type - * of this value, use an ambient module type: - * - * ```ts - * declare module "cloudflare:test" { - * interface ProvidedEnv { - * NAMESPACE: KVNamespace; - * } - * - * // ...or if you have an existing `Env` type... - * interface ProvidedEnv extends Env {} - * } - * ``` + * @deprecated Instead, use `import { env } from "cloudflare:workers"` */ - export const env: ProvidedEnv; + export const env: Cloudflare.Env; /** * Service binding to the default export defined in the `main` worker. Note * this `main` worker runs in the same isolate/context as tests, so any global * mocks will apply to it too. + * @deprecated Instead, use `import { exports } from "cloudflare:workers"` and `exports.default.fetch()` */ export const SELF: Fetcher; - /** - * Declarative interface for mocking outbound `fetch()` requests. Deactivated - * by default and reset before running each test file. Only mocks `fetch()` - * requests for the current test runner worker. Auxiliary workers should mock - * `fetch()`es with the Miniflare `fetchMock`/`outboundService` options. - */ - export const fetchMock: MockAgent; - /** * Runs `callback` inside the Durable Object pointed-to by `stub`'s context. * Conceptually, this temporarily replaces your Durable Object's `fetch()` @@ -63,8 +40,6 @@ declare module "cloudflare:test" { ): Promise; /** * Gets the IDs of all objects that have been created in the `namespace`. - * Respects `isolatedStorage` if enabled, i.e. objects created in a different - * test won't be returned. */ export function listDurableObjectIds( namespace: DurableObjectNamespace @@ -81,7 +56,7 @@ declare module "cloudflare:test" { * `EventContext`s return by `createPagesEventContext()`. */ export function waitOnExecutionContext( - ctx: ExecutionContext | EventContext + ctx: ExecutionContext | EventContext ): Promise; /** * Creates an instance of `ScheduledController` for use as the 1st argument to @@ -566,7 +541,7 @@ declare module "cloudflare:test" { * Functions. */ export function createPagesEventContext< - F extends PagesFunction, + F extends PagesFunction, >(init: EventContextInit[0]>): Parameters[0]; // Taken from `undici` (https://github.com/nodejs/undici/tree/main/types) with diff --git a/packages/workers-shared/package.json b/packages/workers-shared/package.json index e4cbddedc680..567ec2ab6a1c 100644 --- a/packages/workers-shared/package.json +++ b/packages/workers-shared/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@cloudflare/eslint-config-shared": "workspace:*", - "@cloudflare/vitest-pool-workers": "catalog:default", + "@cloudflare/vitest-pool-workers": "catalog:vitest-3", "@cloudflare/workers-tsconfig": "workspace:*", "@cloudflare/workers-types": "catalog:default", "@sentry/cli": "^2.37.0", @@ -54,7 +54,7 @@ "mime": "^4.0.7", "toucan-js": "4.0.0", "typescript": "catalog:default", - "vitest": "~2.1.0", + "vitest": "catalog:vitest-3", "zod": "^3.25.76" }, "engines": { diff --git a/packages/workflows-shared/package.json b/packages/workflows-shared/package.json index 2b972fc90b3c..9dc9d4d57406 100644 --- a/packages/workflows-shared/package.json +++ b/packages/workflows-shared/package.json @@ -41,14 +41,14 @@ }, "devDependencies": { "@cloudflare/eslint-config-shared": "workspace:*", - "@cloudflare/vitest-pool-workers": "catalog:default", + "@cloudflare/vitest-pool-workers": "catalog:vitest-3", "@cloudflare/workers-tsconfig": "workspace:*", "@cloudflare/workers-types": "catalog:default", "@types/mime": "^3.0.4", "esbuild": "catalog:default", "eslint": "catalog:default", "typescript": "catalog:default", - "vitest": "catalog:default" + "vitest": "catalog:vitest-3" }, "engines": { "node": ">=18.0.0" diff --git a/packages/workflows-shared/src/engine.ts b/packages/workflows-shared/src/engine.ts index 6b84402a402a..d79bcd4c7ed5 100644 --- a/packages/workflows-shared/src/engine.ts +++ b/packages/workflows-shared/src/engine.ts @@ -23,6 +23,7 @@ import type { WorkflowEntrypoint, WorkflowEvent } from "cloudflare:workers"; interface Env { USER_WORKFLOW: WorkflowEntrypoint; + ENGINE: DurableObjectNamespace; } export type DatabaseWorkflow = { diff --git a/packages/workflows-shared/tests/binding.test.ts b/packages/workflows-shared/tests/binding.test.ts index e1e76fc6127e..308f571ff7da 100644 --- a/packages/workflows-shared/tests/binding.test.ts +++ b/packages/workflows-shared/tests/binding.test.ts @@ -1,12 +1,8 @@ -import { - createExecutionContext, - env, - runInDurableObject, -} from "cloudflare:test"; +import { createExecutionContext, runInDurableObject } from "cloudflare:test"; +import { env } from "cloudflare:workers"; import { describe, it, vi } from "vitest"; import { WorkflowBinding } from "../src/binding"; import type { Engine } from "../src/engine"; -import type { ProvidedEnv } from "cloudflare:test"; import type { WorkflowEvent, WorkflowStep } from "cloudflare:workers"; async function setWorkflowEntrypoint( @@ -21,7 +17,7 @@ async function setWorkflowEntrypoint( // eslint-disable-next-line @typescript-eslint/no-shadow protected ctx: ExecutionContext, // eslint-disable-next-line @typescript-eslint/no-shadow - protected env: ProvidedEnv + protected env: Cloudflare.Env ) {} public async run( event: Readonly>, @@ -65,7 +61,7 @@ describe("WorkflowBinding", () => { const disposeSpy = vi.fn(); - await runInDurableObject(engineStub, (engine) => { + await runInDurableObject(engineStub, (engine) => { const originalReceiveEvent = engine.receiveEvent.bind(engine); engine.receiveEvent = (event) => { const result = originalReceiveEvent(event); diff --git a/packages/workflows-shared/tests/engine.test.ts b/packages/workflows-shared/tests/engine.test.ts index f00b5c4c6905..f93ad2805a11 100644 --- a/packages/workflows-shared/tests/engine.test.ts +++ b/packages/workflows-shared/tests/engine.test.ts @@ -1,8 +1,5 @@ -import { - createExecutionContext, - env, - runInDurableObject, -} from "cloudflare:test"; +import { createExecutionContext, runInDurableObject } from "cloudflare:test"; +import { env } from "cloudflare:workers"; import { NonRetryableError } from "cloudflare:workflows"; import { describe, it, vi } from "vitest"; import { InstanceEvent, InstanceStatus } from "../src"; @@ -13,7 +10,6 @@ import type { Engine, EngineLogs, } from "../src/engine"; -import type { ProvidedEnv } from "cloudflare:test"; import type { WorkflowEvent, WorkflowStep } from "cloudflare:workers"; async function setWorkflowEntrypoint( @@ -28,7 +24,7 @@ async function setWorkflowEntrypoint( // eslint-disable-next-line @typescript-eslint/no-shadow protected ctx: ExecutionContext, // eslint-disable-next-line @typescript-eslint/no-shadow - protected env: ProvidedEnv + protected env: Cloudflare.Env ) {} public async run( event: Readonly>, diff --git a/packages/workflows-shared/tests/env.d.ts b/packages/workflows-shared/tests/env.d.ts index 28f77257f833..38fe220a9c40 100644 --- a/packages/workflows-shared/tests/env.d.ts +++ b/packages/workflows-shared/tests/env.d.ts @@ -1,10 +1,8 @@ -import { type WorkflowEntrypoint } from "cloudflare:workers"; -import { type Engine } from "../src/index"; +/* eslint-disable */ -declare module "cloudflare:test" { - // Controls the type of `import("cloudflare:test").env` - interface ProvidedEnv extends Env { - ENGINE: DurableObjectNamespace; - USER_WORKFLOW: WorkflowEntrypoint; +declare namespace Cloudflare { + interface Env { + ENGINE: DurableObjectNamespace; + USER_WORKFLOW: import("cloudflare:workers").WorkflowEntrypoint; } } diff --git a/packages/workflows-shared/vitest.config.ts b/packages/workflows-shared/vitest.config.ts index d102e7743280..c124f987a1ba 100644 --- a/packages/workflows-shared/vitest.config.ts +++ b/packages/workflows-shared/vitest.config.ts @@ -4,8 +4,6 @@ export default defineWorkersProject({ test: { poolOptions: { workers: { - singleWorker: true, - isolatedStorage: true, main: "src/index.ts", miniflare: { compatibilityDate: "2025-02-04", diff --git a/packages/wrangler/e2e/vitest.config.mts b/packages/wrangler/e2e/vitest.config.mts index 74fcd53e018e..78558e4ddc7e 100644 --- a/packages/wrangler/e2e/vitest.config.mts +++ b/packages/wrangler/e2e/vitest.config.mts @@ -4,11 +4,9 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { testTimeout: 90_000, - poolOptions: { - threads: { - singleThread: true, - }, - }, + pool: "threads", + maxWorkers: 1, + isolate: false, include: [process.env.WRANGLER_E2E_TEST_FILE || "e2e/**/*.test.ts"], outputFile: process.env.TEST_REPORT_PATH ?? ".e2e-test-report/index.html", globalSetup: path.resolve(__dirname, "./validate-environment.ts"), diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index dbef7fdc81f9..fd4b42aca648 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -110,7 +110,7 @@ "@types/supports-color": "^8.1.1", "@types/ws": "^8.5.7", "@types/yargs": "^17.0.22", - "@vitest/ui": "catalog:default", + "@vitest/ui": "catalog:vitest-3", "@webcontainer/env": "^1.1.0", "am-i-vibing": "^0.1.0", "capnweb": "catalog:default", @@ -142,7 +142,7 @@ "mime": "^3.0.0", "minimatch": "^5.1.0", "mock-socket": "^9.3.1", - "msw": "2.12.0", + "msw": "catalog:default", "node-forge": "^1.3.1", "open": "^8.4.0", "p-queue": "^9.0.0", @@ -165,7 +165,7 @@ "typescript": "catalog:default", "undici": "catalog:default", "update-check": "^1.5.4", - "vitest": "catalog:default", + "vitest": "catalog:vitest-3", "vitest-websocket-mock": "^0.4.0", "ws": "catalog:default", "xxhash-wasm": "^1.0.1", diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index 7d5fd7c23c6c..2969ddf49736 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -38,6 +38,7 @@ describe("wrangler", () => { COMMANDS wrangler docs [search..] 📚 Open Wrangler's command documentation in your browser wrangler complete [shell] ⌨️ Generate and handle shell completions + wrangler codemod [name] 🔨 Apply a code modification to your project [experimental] ACCOUNT wrangler auth 🔐 Manage authentication @@ -109,6 +110,7 @@ describe("wrangler", () => { COMMANDS wrangler docs [search..] 📚 Open Wrangler's command documentation in your browser wrangler complete [shell] ⌨️ Generate and handle shell completions + wrangler codemod [name] 🔨 Apply a code modification to your project [experimental] ACCOUNT wrangler auth 🔐 Manage authentication diff --git a/packages/wrangler/src/cli.ts b/packages/wrangler/src/cli.ts index cc7086752910..3126104b8883 100644 --- a/packages/wrangler/src/cli.ts +++ b/packages/wrangler/src/cli.ts @@ -103,6 +103,7 @@ export interface Unstable_ASSETSBindingsOptions { log: Logger; proxyPort?: number; directory?: string; + signal?: AbortSignal; } export const unstable_generateASSETSBinding: ( opts: Unstable_ASSETSBindingsOptions diff --git a/packages/wrangler/src/codemod.ts b/packages/wrangler/src/codemod.ts new file mode 100644 index 000000000000..263c0341967f --- /dev/null +++ b/packages/wrangler/src/codemod.ts @@ -0,0 +1,223 @@ +import assert from "node:assert"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { UserError } from "@cloudflare/workers-utils"; +import * as recast from "recast"; +import { transformFile } from "./autoconfig/c3-vendor/codemod"; +import { createCommand } from "./core/create-command"; +import { logger } from "./logger"; + +const b = recast.types.builders; +const t = recast.types.namedTypes; + +function isNamedProp( + prop: unknown, + name: string +): prop is + | recast.types.namedTypes.ObjectProperty + | recast.types.namedTypes.Property { + return ( + (t.Property.check(prop) || t.ObjectProperty.check(prop)) && + t.Identifier.check(prop.key) && + prop.key.name === name + ); +} + +function vitestPoolV3ToV4(args: string | undefined) { + const filePathTS = path.join(process.cwd(), `vitest.config.ts`); + const filePathJS = path.join(process.cwd(), `vitest.config.js`); + + let filePath: string; + + if (args) { + if (!existsSync(args)) { + throw new UserError( + `Selected Vitest config file does not exist: '${args}'` + ); + } + filePath = path.resolve(process.cwd(), args); + } else if (existsSync(filePathTS)) { + filePath = filePathTS; + } else if (existsSync(filePathJS)) { + filePath = filePathJS; + } else { + throw new UserError("Could not find Vitest config file to modify"); + } + + transformFile(filePath, { + visitProgram(n) { + // Find the existing import of @cloudflare/vitest-pool-workers/config + // ``` + // import { defineWorkersProject } from "@cloudflare/vitest-pool-workers/config"; + // ``` + const importAst = n.node.body.find( + (s) => + s.type === "ImportDeclaration" && + s.source.value === "@cloudflare/vitest-pool-workers/config" && + s.specifiers?.some( + (specifier) => + specifier.type === "ImportSpecifier" && + specifier.imported?.name === "defineWorkersProject" + ) + ); + if (!importAst) { + throw new UserError( + "Could not find import of `@cloudflare/vitest-pool-workers/config`" + ); + } + + const lastImportIndex = n.node.body.findLastIndex( + (statement) => statement.type === "ImportDeclaration" + ); + const lastImport = n.get("body", lastImportIndex); + const vitestConfigAst = b.importDeclaration( + [b.importSpecifier(b.identifier("defineConfig"))], + b.stringLiteral("vitest/config") + ); + + lastImport.insertAfter(vitestConfigAst); + + assert(importAst.type === "ImportDeclaration"); + + importAst.source.value = "@cloudflare/vitest-pool-workers"; + importAst.specifiers = [ + b.importSpecifier(b.identifier("cloudflareTest")), + ...(importAst.specifiers?.filter( + (specifier) => + !( + specifier.type === "ImportSpecifier" && + specifier.imported?.name === "defineWorkersProject" + ) + ) ?? []), + ]; + + return this.traverse(n); + }, + visitCallExpression: function (n) { + // Add the imported plugin to the config + // ``` + // defineConfig({ + // plugins: [cloudflare({ viteEnvironment: { name: 'ssr' } })], + // }); + const callee = n.node.callee as recast.types.namedTypes.Identifier; + if (callee.name !== "defineWorkersProject") { + return this.traverse(n); + } + callee.name = "defineConfig"; + + const config = n.node.arguments[0]; + assert( + t.ObjectExpression.check(config), + "defineWorkersProject() is called with a function and not an object, and so is too complex to apply a codemod to. Please refer to the migration docs to perform the migration manually." + ); + + const testProp = config.properties.find((prop) => + isNamedProp(prop, "test") + ); + + assert( + testProp && t.ObjectExpression.check(testProp.value), + "Could not find `test` property in config" + ); + + const poolOptionsProp = testProp.value.properties.find((prop) => + isNamedProp(prop, "poolOptions") + ); + + assert( + poolOptionsProp && t.ObjectExpression.check(poolOptionsProp.value), + "Could not find `test.poolOptions` property in config" + ); + + const poolOptionsWorkersProp = poolOptionsProp.value.properties.find( + (prop) => isNamedProp(prop, "workers") + ); + + assert( + poolOptionsWorkersProp && + (t.ObjectExpression.check(poolOptionsWorkersProp.value) || + t.FunctionExpression.check(poolOptionsWorkersProp.value) || + t.ArrowFunctionExpression.check(poolOptionsWorkersProp.value)), + "Could not find `test.poolOptions.workers` property in config" + ); + + const pluginsProp = config.properties.find((prop) => + isNamedProp(prop, "plugins") + ); + + if (pluginsProp) { + assert(t.ArrayExpression.check(pluginsProp.value)); + pluginsProp.value.elements.unshift( + b.callExpression(b.identifier("cloudflareTest"), [ + poolOptionsWorkersProp.value, + ]) + ); + } else { + config.properties.unshift( + b.objectProperty( + b.identifier("plugins"), + b.arrayExpression([ + b.callExpression(b.identifier("cloudflareTest"), [ + poolOptionsWorkersProp.value, + ]), + ]) + ) + ); + } + testProp.value.properties = testProp.value.properties.filter( + (prop) => !isNamedProp(prop, "poolOptions") + ); + return false; + }, + }); +} + +const validCodemods = new Map void>([ + ["vitest-pool-v3-to-v4", vitestPoolV3ToV4], +]); + +export const codemodCommand = createCommand({ + behaviour: { + provideConfig: false, + overrideExperimentalFlags: (args) => ({ + MULTIWORKER: Array.isArray(args.config), + RESOURCES_PROVISION: args.experimentalProvision ?? false, + AUTOCREATE_RESOURCES: args.experimentalAutoCreate, + }), + }, + metadata: { + description: "🔨 Apply a code modification to your project", + owner: "Workers: Authoring and Testing", + status: "experimental", + }, + positionalArgs: ["name"], + args: { + name: { + describe: "The name of the codemod you want to apply", + type: "string", + }, + args: { + describe: "Extra arguments to pass to the selected codemod", + type: "string", + }, + }, + async validateArgs(args) { + if (args.name !== undefined && !validCodemods.has(args.name)) { + throw new UserError( + `${args.name} is not a valid codemod name. Valid names are ${[...validCodemods.keys()].join(", ")}` + ); + } + }, + async handler(args) { + if (args.name === undefined || args.name === "") { + logger.log("Apply one of the following codemods:"); + for (const name of validCodemods.keys()) { + logger.log(`- ${name}`); + } + } else { + const codemod = validCodemods.get(args.name); + assert(codemod); + await codemod(args.args); + } + }, +}); diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index 006faf77aa23..18fd8abe80d9 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -45,6 +45,7 @@ import { cloudchamberSshListCommand, cloudchamberSshNamespace, } from "./cloudchamber"; +import { codemodCommand } from "./codemod"; import { completionsCommand } from "./complete"; import { getDefaultEnvFiles, loadDotEnv } from "./config/dot-env"; import { @@ -715,6 +716,14 @@ export function createCLIParser(argv: string[]) { ]); registry.registerNamespace("dev"); + registry.define([ + { + command: "wrangler codemod", + definition: codemodCommand, + }, + ]); + registry.registerNamespace("codemod"); + registry.define([ { command: "wrangler deploy", diff --git a/packages/wrangler/src/miniflare-cli/assets.ts b/packages/wrangler/src/miniflare-cli/assets.ts index eea8b96b4dba..34cf689cb30f 100644 --- a/packages/wrangler/src/miniflare-cli/assets.ts +++ b/packages/wrangler/src/miniflare-cli/assets.ts @@ -23,12 +23,17 @@ export interface Options { log: Logger; proxyPort?: number; directory?: string; + signal?: AbortSignal; } export default async function generateASSETSBinding(options: Options) { const assetsFetch = options.directory !== undefined - ? await generateAssetsFetch(options.directory, options.log) + ? await generateAssetsFetch( + options.directory, + options.log, + options.signal + ) : invalidAssetsFetch; return async function (miniflareRequest: Request) { @@ -128,7 +133,8 @@ class ProxyDispatcher extends Dispatcher { async function generateAssetsFetch( directory: string, - log: Logger + log: Logger, + signal?: AbortSignal ): Promise { directory = resolve(directory); // Defer importing miniflare until we really need it @@ -174,35 +180,46 @@ async function generateAssetsFetch( logger: log, }); - watch([headersFile, redirectsFile], { persistent: true }).on( - "change", - (path) => { - switch (path) { - case headersFile: { - log.log("_headers modified. Re-evaluating..."); - const contents = readFileSync(headersFile).toString(); - headers = parseHeaders(contents); - break; - } - case redirectsFile: { - log.log("_redirects modified. Re-evaluating..."); - const contents = readFileSync(redirectsFile).toString(); - redirects = parseRedirects(contents, { - htmlHandling: undefined, // Pages dev server doesn't expose html_handling configuration in this context. - }); - break; - } + const watcher = watch([headersFile, redirectsFile], { + persistent: true, + }).on("change", (path) => { + switch (path) { + case headersFile: { + log.log("_headers modified. Re-evaluating..."); + const contents = readFileSync(headersFile).toString(); + headers = parseHeaders(contents); + break; + } + case redirectsFile: { + log.log("_redirects modified. Re-evaluating..."); + const contents = readFileSync(redirectsFile).toString(); + redirects = parseRedirects(contents, { + htmlHandling: undefined, // Pages dev server doesn't expose html_handling configuration in this context. + }); + break; } + } - metadata = createMetadataObject({ - redirects, - headers, - redirectsFile, - headersFile, - logger: log, - }); + metadata = createMetadataObject({ + redirects, + headers, + redirectsFile, + headersFile, + logger: log, + }); + }); + + if (signal) { + if (signal.aborted) { + void watcher.close().catch(() => {}); + } else { + signal.addEventListener( + "abort", + () => void watcher.close().catch(() => {}), + { once: true } + ); } - ); + } const generateResponse = async (request: Request) => { const assetKeyEntryMap = new Map(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2132827b440..66dc9a116dca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,9 +6,6 @@ settings: catalogs: default: - '@cloudflare/vitest-pool-workers': - specifier: ^0.10.11 - version: 0.10.15 '@cloudflare/workers-types': specifier: ^4.20260305.0 version: 4.20260305.0 @@ -19,14 +16,14 @@ catalogs: specifier: ^8.35.1 version: 8.46.3 '@vitest/runner': - specifier: ~3.2.0 - version: 3.2.3 + specifier: 4.1.0-beta.4 + version: 4.1.0-beta.4 '@vitest/snapshot': - specifier: ~3.2.0 - version: 3.2.3 + specifier: 4.1.0-beta.4 + version: 4.1.0-beta.4 '@vitest/ui': - specifier: ~3.2.0 - version: 3.2.3 + specifier: 4.1.0-beta.4 + version: 4.1.0-beta.4 capnp-es: specifier: ^0.0.14 version: 0.0.14 @@ -45,6 +42,9 @@ catalogs: jsonc-parser: specifier: ^3.2.0 version: 3.2.0 + msw: + specifier: 2.12.4 + version: 2.12.4 playwright-chromium: specifier: ^1.56.1 version: 1.56.1 @@ -61,11 +61,11 @@ catalogs: specifier: 7.18.2 version: 7.18.2 vite: - specifier: ^5.4.14 - version: 5.4.14 + specifier: ^7.3.1 + version: 7.3.1 vitest: - specifier: ~3.2.0 - version: 3.2.3 + specifier: 4.1.0-beta.4 + version: 4.1.0-beta.4 ws: specifier: 8.18.0 version: 8.18.0 @@ -73,6 +73,19 @@ catalogs: vite: specifier: 7.1.12 version: 7.1.12 + vitest-3: + '@cloudflare/vitest-pool-workers': + specifier: ^0.10.0 + version: 0.10.15 + '@vitest/ui': + specifier: 3.2.4 + version: 3.2.4 + vite: + specifier: ^5.4.14 + version: 5.4.14 + vitest: + specifier: 3.2.4 + version: 3.2.4 overrides: '@types/react-dom@18>@types/react': ^18 @@ -80,8 +93,9 @@ overrides: '@types/react-transition-group>@types/react': ^18 '@cloudflare/elements>@types/react': ^18 '@types/node': ^20.19.9 - vitest>vite: ^5.0.0 '@types/node>undici-types': 7.18.2 + vitest@4>vite: 7.1.12 + vitest@3>vite: ^5.0.0 patchedDependencies: '@cloudflare/component-listbox@1.10.6': @@ -157,10 +171,10 @@ importers: version: 5.8.3 vite: specifier: catalog:default - version: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + version: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) fixtures/additional-modules: devDependencies: @@ -184,7 +198,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -211,7 +225,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -253,7 +267,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -277,7 +291,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -304,7 +318,7 @@ importers: version: 7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -322,7 +336,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -343,7 +357,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -382,7 +396,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -412,7 +426,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -436,7 +450,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -463,7 +477,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -487,7 +501,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) fixtures/isomorphic-random-example: {} @@ -515,7 +529,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -551,7 +565,7 @@ importers: version: 7.0.0 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -579,7 +593,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -619,7 +633,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -640,7 +654,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -676,7 +690,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -700,7 +714,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -724,7 +738,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -755,7 +769,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -779,7 +793,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -800,7 +814,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -824,7 +838,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -848,7 +862,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -872,7 +886,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -915,7 +929,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -939,7 +953,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -957,7 +971,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -981,7 +995,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1005,7 +1019,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1026,7 +1040,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1047,7 +1061,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1068,7 +1082,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1089,7 +1103,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1110,7 +1124,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1131,7 +1145,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1155,7 +1169,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1173,7 +1187,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1191,7 +1205,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1269,7 +1283,7 @@ importers: version: 4.20260305.0 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1297,6 +1311,9 @@ importers: fixtures/vitest-pool-workers-examples: devDependencies: + '@better-auth/stripe': + specifier: ^1.4.6 + version: 1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(better-auth@1.4.19(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(typescript@5.8.3))(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(vitest@4.1.0-beta.4))(better-call@1.1.8(zod@4.3.6))(stripe@20.4.0(@types/node@20.19.9)) '@cloudflare/containers': specifier: ^0.0.25 version: 0.0.25 @@ -1318,6 +1335,12 @@ importers: '@types/node': specifier: ^20.19.9 version: 20.19.9 + '@types/nunjucks': + specifier: ^3.2.6 + version: 3.2.6 + better-auth: + specifier: ^1.4.6 + version: 1.4.19(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(typescript@5.8.3))(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(vitest@4.1.0-beta.4) discord-api-types: specifier: 0.37.98 version: 0.37.98 @@ -1325,7 +1348,7 @@ importers: specifier: file:./module-resolution/vendor/ext-dep version: file:fixtures/vitest-pool-workers-examples/module-resolution/vendor/ext-dep jose: - specifier: ^5.2.2 + specifier: ^5.9.3 version: 5.9.3 mime-types: specifier: ^2.1.35 @@ -1333,9 +1356,15 @@ importers: miniflare: specifier: workspace:* version: link:../../packages/miniflare - run-script-os: - specifier: ^1.1.6 - version: 1.1.6 + msw: + specifier: catalog:default + version: 2.12.4(@types/node@20.19.9)(typescript@5.8.3) + nunjucks: + specifier: ^3.2.4 + version: 3.2.4(chokidar@3.6.0) + stripe: + specifier: ^20.0.0 + version: 20.4.0(@types/node@20.19.9) toucan-js: specifier: 4.0.0 version: 4.0.0(patch_hash=qxsfpdzvzbhq2ecirbu5xq4vlq) @@ -1344,10 +1373,10 @@ importers: version: 5.8.3 vite: specifier: catalog:default - version: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + version: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1365,7 +1394,7 @@ importers: version: 5.8.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.2))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.2))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1416,7 +1445,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1434,7 +1463,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1467,7 +1496,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1491,7 +1520,7 @@ importers: version: 2.2.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1524,7 +1553,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1548,7 +1577,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1572,7 +1601,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1596,7 +1625,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1620,7 +1649,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1656,7 +1685,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1672,6 +1701,9 @@ importers: '@cloudflare/workers-types': specifier: catalog:default version: 4.20260305.0 + '@types/node': + specifier: ^20.19.9 + version: 20.19.9 playwright-chromium: specifier: catalog:default version: 1.56.1 @@ -1683,7 +1715,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1707,7 +1739,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1728,7 +1760,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../../packages/wrangler @@ -1789,7 +1821,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) packages/create-cloudflare: devDependencies: @@ -1929,14 +1961,14 @@ importers: specifier: catalog:default version: 7.18.2 vite: - specifier: catalog:default + specifier: catalog:vitest-3 version: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) vite-tsconfig-paths: specifier: ^4.0.8 version: 4.2.0(typescript@5.8.3)(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) vitest: - specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + specifier: catalog:vitest-3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) which-pm-runs: specifier: ^1.1.0 version: 1.1.0 @@ -1983,8 +2015,8 @@ importers: specifier: workspace:* version: link:../eslint-config-shared '@cloudflare/vitest-pool-workers': - specifier: catalog:default - version: 0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3) + specifier: workspace:* + version: link:../vitest-pool-workers '@cloudflare/workers-types': specifier: catalog:default version: 4.20260305.0 @@ -2005,7 +2037,7 @@ importers: version: 4.0.0(patch_hash=qxsfpdzvzbhq2ecirbu5xq4vlq) vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) wrangler: specifier: workspace:* version: link:../wrangler @@ -2085,8 +2117,8 @@ importers: specifier: workspace:* version: link:../eslint-config-shared '@cloudflare/vitest-pool-workers': - specifier: catalog:default - version: 0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@2.1.9) + specifier: catalog:vitest-3 + version: 0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@4.1.0-beta.4)(@vitest/snapshot@4.1.0-beta.4)(vitest@3.2.4) '@cloudflare/workers-types': specifier: catalog:default version: 4.20260305.0 @@ -2106,8 +2138,8 @@ importers: specifier: 8.3.0 version: 8.3.0(@microsoft/api-extractor@7.52.8(@types/node@20.19.9))(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.1) vitest: - specifier: ~2.1.0 - version: 2.1.9(@types/node@20.19.9)(@vitest/ui@2.1.9)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3)) + specifier: catalog:vitest-3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) packages/local-explorer-ui: dependencies: @@ -2116,7 +2148,7 @@ importers: version: 1.1.0(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@cloudflare/kumo': specifier: ^1.5.0 - version: 1.5.0(@phosphor-icons/react@2.1.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 1.5.0(@phosphor-icons/react@2.1.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(zod@4.3.6) '@cloudflare/workers-editor-shared': specifier: ^0.1.1 version: 0.1.1(@cloudflare/style-const@5.7.3(react@19.2.1))(@cloudflare/style-container@7.12.2(@cloudflare/style-const@5.7.3(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -2140,7 +2172,7 @@ importers: version: 2.1.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@tailwindcss/vite': specifier: ^4.0.15 - version: 4.0.15(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) + version: 4.0.15(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/react-router': specifier: ^1.158.0 version: 1.158.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -2168,7 +2200,7 @@ importers: version: 1.158.0(@tanstack/react-router@1.158.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@tanstack/router-core@1.158.0)(csstype@3.2.3)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@tanstack/router-plugin': specifier: ^1.158.0 - version: 1.158.0(@tanstack/react-router@1.158.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) + version: 1.158.0(@tanstack/react-router@1.158.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) '@types/react': specifier: ^19.2.0 version: 19.2.10 @@ -2177,7 +2209,7 @@ importers: version: 19.2.3(@types/react@19.2.10) '@vitejs/plugin-react': specifier: ^4.4.1 - version: 4.7.0(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) + version: 4.7.0(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) concurrently: specifier: ^9.0.0 version: 9.2.1 @@ -2186,13 +2218,13 @@ importers: version: 5.8.3 vite: specifier: catalog:default - version: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + version: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) vite-plugin-svgr: specifier: ^4.3.0 - version: 4.5.0(rollup@4.44.1)(typescript@5.8.3)(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) + version: 4.5.0(rollup@4.44.1)(typescript@5.8.3)(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) packages/miniflare: dependencies: @@ -2361,7 +2393,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.7)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) which: specifier: ^2.0.2 version: 2.0.2 @@ -2415,8 +2447,8 @@ importers: specifier: workspace:* version: link:../eslint-config-shared '@cloudflare/vitest-pool-workers': - specifier: catalog:default - version: 0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3) + specifier: catalog:vitest-3 + version: 0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@4.1.0-beta.4)(@vitest/snapshot@4.1.0-beta.4)(vitest@3.2.4) '@cloudflare/workers-shared': specifier: workspace:* version: link:../workers-shared @@ -2445,8 +2477,8 @@ importers: specifier: catalog:default version: 5.8.3 vitest: - specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + specifier: catalog:vitest-3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) packages/playground-preview-worker: dependencies: @@ -2671,8 +2703,8 @@ importers: specifier: catalog:vite-plugin version: 7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) vitest: - specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + specifier: catalog:vitest-3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) packages/vite-plugin-cloudflare/playground: devDependencies: @@ -2694,6 +2726,9 @@ importers: typescript: specifier: catalog:default version: 5.8.3 + vitest: + specifier: catalog:vitest-3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) packages/vite-plugin-cloudflare/playground/additional-modules: devDependencies: @@ -3769,6 +3804,9 @@ importers: wrangler: specifier: workspace:* version: link:../wrangler + zod: + specifier: ^3.25.76 + version: 3.25.76 devDependencies: '@cloudflare/eslint-config-shared': specifier: workspace:* @@ -3796,10 +3834,10 @@ importers: version: 7.5.1 '@vitest/runner': specifier: catalog:default - version: 3.2.3 + version: 4.1.0-beta.4 '@vitest/snapshot': specifier: catalog:default - version: 3.2.3 + version: 4.1.0-beta.4 birpc: specifier: 0.2.14 version: 0.2.14 @@ -3824,6 +3862,9 @@ importers: ts-dedent: specifier: ^2.2.0 version: 2.2.0 + tsdown: + specifier: 0.16.3 + version: 0.16.3(ms@2.1.3)(typescript@5.8.3) typescript: specifier: catalog:default version: 5.8.3 @@ -3832,10 +3873,7 @@ importers: version: 7.18.2 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) - zod: - specifier: ^3.25.76 - version: 3.25.76 + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) packages/workers-editor-shared: dependencies: @@ -3860,7 +3898,7 @@ importers: version: 18.3.3 '@vitejs/plugin-react': specifier: ^4.3.3 - version: 4.3.3(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) + version: 4.3.3(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) eslint: specifier: catalog:default version: 9.39.1(jiti@2.6.1) @@ -3875,10 +3913,10 @@ importers: version: 5.8.3 vite: specifier: catalog:default - version: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + version: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) vite-plugin-dts: specifier: ^4.0.1 - version: 4.0.1(@types/node@20.19.9)(rollup@4.44.1)(typescript@5.8.3)(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) + version: 4.0.1(@types/node@20.19.9)(rollup@4.44.1)(typescript@5.8.3)(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) packages/workers-playground: dependencies: @@ -3981,7 +4019,7 @@ importers: version: 9.0.4 '@vitejs/plugin-react': specifier: ^4.3.3 - version: 4.3.3(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) + version: 4.3.3(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@3.12.10)(yaml@2.8.1)) eslint: specifier: catalog:default version: 9.39.1(jiti@2.6.1) @@ -3996,7 +4034,7 @@ importers: version: 7.18.2 vite: specifier: catalog:default - version: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + version: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@3.12.10)(yaml@2.8.1) wrangler: specifier: workspace:^ version: link:../wrangler @@ -4007,8 +4045,8 @@ importers: specifier: workspace:* version: link:../eslint-config-shared '@cloudflare/vitest-pool-workers': - specifier: catalog:default - version: 0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@2.1.9) + specifier: catalog:vitest-3 + version: 0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@4.1.0-beta.4)(@vitest/snapshot@4.1.0-beta.4)(vitest@3.2.4) '@cloudflare/workers-tsconfig': specifier: workspace:* version: link:../workers-tsconfig @@ -4040,8 +4078,8 @@ importers: specifier: catalog:default version: 5.8.3 vitest: - specifier: ~2.1.0 - version: 2.1.9(@types/node@20.19.9)(@vitest/ui@2.1.9)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3)) + specifier: catalog:vitest-3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) zod: specifier: ^3.25.76 version: 3.25.76 @@ -4067,7 +4105,7 @@ importers: version: 20.19.9 '@vitest/ui': specifier: catalog:default - version: 3.2.3(vitest@3.2.3) + version: 4.1.0-beta.4(vitest@4.1.0-beta.4) cloudflare: specifier: ^5.2.0 version: 5.2.0(encoding@0.1.13) @@ -4100,7 +4138,7 @@ importers: version: 5.8.3 vitest: specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + version: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) xdg-app-paths: specifier: ^8.3.0 version: 8.3.0 @@ -4124,8 +4162,8 @@ importers: specifier: workspace:* version: link:../eslint-config-shared '@cloudflare/vitest-pool-workers': - specifier: catalog:default - version: 0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3) + specifier: catalog:vitest-3 + version: 0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@4.1.0-beta.4)(@vitest/snapshot@4.1.0-beta.4)(vitest@3.2.4) '@cloudflare/workers-tsconfig': specifier: workspace:* version: link:../workers-tsconfig @@ -4145,8 +4183,8 @@ importers: specifier: catalog:default version: 5.8.3 vitest: - specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + specifier: catalog:vitest-3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) packages/wrangler: dependencies: @@ -4282,8 +4320,8 @@ importers: specifier: ^17.0.22 version: 17.0.24 '@vitest/ui': - specifier: catalog:default - version: 3.2.3(vitest@3.2.3) + specifier: catalog:vitest-3 + version: 3.2.4(vitest@3.2.4) '@webcontainer/env': specifier: ^1.1.0 version: 1.1.0 @@ -4378,8 +4416,8 @@ importers: specifier: ^9.3.1 version: 9.3.1 msw: - specifier: 2.12.0 - version: 2.12.0(@types/node@20.19.9)(typescript@5.8.3) + specifier: catalog:default + version: 2.12.4(@types/node@20.19.9)(typescript@5.8.3) node-forge: specifier: ^1.3.1 version: 1.3.1 @@ -4447,11 +4485,11 @@ importers: specifier: ^1.5.4 version: 1.5.4 vitest: - specifier: catalog:default - version: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + specifier: catalog:vitest-3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) vitest-websocket-mock: specifier: ^0.4.0 - version: 0.4.0(vitest@3.2.3) + version: 0.4.0(vitest@3.2.4) ws: specifier: catalog:default version: 8.18.0 @@ -4933,6 +4971,35 @@ packages: '@types/react': optional: true + '@better-auth/core@1.4.19': + resolution: {integrity: sha512-uADLHG1jc5BnEJi7f6ijUN5DmPPRSj++7m/G19z3UqA3MVCo4Y4t1MMa4IIxLCqGDFv22drdfxescgW+HnIowA==} + peerDependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + better-call: 1.1.8 + jose: ^6.1.0 + kysely: ^0.28.5 + nanostores: ^1.0.1 + + '@better-auth/stripe@1.4.19': + resolution: {integrity: sha512-LkU5s6st3jj61Pyl2bGhkNSkJZZAtxD+RuzXep9dGwlgDTxs36nZ/LMWgjVpZvsI2iSZmsPolRaHaLe0Yk2Ayw==} + peerDependencies: + '@better-auth/core': 1.4.19 + better-auth: 1.4.19 + better-call: 1.1.8 + stripe: ^18 || ^19 || ^20 + + '@better-auth/telemetry@1.4.19': + resolution: {integrity: sha512-ApGNS7olCTtDpKF8Ow3Z+jvFAirOj7c4RyFUpu8axklh3mH57ndpfUAUjhgA8UVoaaH/mnm/Tl884BlqiewLyw==} + peerDependencies: + '@better-auth/core': 1.4.19 + + '@better-auth/utils@0.3.0': + resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} + + '@better-fetch/fetch@1.1.21': + resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + '@bomb.sh/tab@0.0.12': resolution: {integrity: sha512-dYRwg4MqfHR5/BcTy285XOGRhjQFmNpaJBZ0tl2oU+RY595MQ5ApTF6j3OvauPAooHL6cfoOZMySQrOQztT8RQ==} hasBin: true @@ -7017,6 +7084,14 @@ packages: engines: {node: '>=18.14.0'} hasBin: true + '@noble/ciphers@2.1.1': + resolution: {integrity: sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw==} + engines: {node: '>= 20.19.0'} + + '@noble/hashes@2.0.1': + resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} + engines: {node: '>= 20.19.0'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -8234,6 +8309,9 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@styled-system/background@5.1.2': resolution: {integrity: sha512-jtwH2C/U6ssuGSvwTN3ri/IyjdHb8W9X/g8Y0JLcrH02G+BW3OS8kZdHphF1/YyRklnrKrBT2ngwGUK6aqqV3A==} @@ -8662,6 +8740,9 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/nunjucks@3.2.6': + resolution: {integrity: sha512-pHiGtf83na1nCzliuAdq8GowYiXvH5l931xZ0YEHaLMNFgynpEqx+IPStlu7UaDkehfvl01e4x/9Tpwhy7Ue3w==} + '@types/pg@8.11.6': resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==} @@ -8743,8 +8824,8 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/statuses@2.0.5': - resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} '@types/stoppable@1.1.3': resolution: {integrity: sha512-7wGKIBJGE4ZxFjk9NkjAxZMLlIXroETqP1FJCdoSvKmEznwmBxQFmTB1dsCkAvVcNemuSZM5qkkd9HE/NL2JTw==} @@ -8945,28 +9026,28 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitest/expect@2.1.9': - resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - '@vitest/expect@3.2.3': - resolution: {integrity: sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==} + '@vitest/expect@4.1.0-beta.4': + resolution: {integrity: sha512-50CzsTy9kVrlI7V0Ot63jPb5q069r1Xn/z489q/pWmFImEUC30oiO9gaRInkWUmgHpSZTO8E9rSdu6jFZwRHjg==} - '@vitest/mocker@2.1.9': - resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/mocker@3.2.3': - resolution: {integrity: sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==} + '@vitest/mocker@4.1.0-beta.4': + resolution: {integrity: sha512-JIHUUrevhes/tP8U3TqPHo/n8lmruITvC9YdnbYyA+L0Y9zyYZ5zW/07+i/aXpmabtxIiJG7eKIv2ootcBu4Vw==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true @@ -8976,48 +9057,48 @@ packages: '@vitest/pretty-format@2.1.8': resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} - '@vitest/pretty-format@2.1.9': - resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - '@vitest/pretty-format@3.2.3': - resolution: {integrity: sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==} + '@vitest/pretty-format@4.1.0-beta.4': + resolution: {integrity: sha512-rAJOtUSRzgobQtuW98WV3bSkomdILArhgSc4JQ5G6Et0eaD6DTeMpr+k7B//F/xYG7oVeuabOTx3EWu06ILCgA==} - '@vitest/runner@2.1.9': - resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - '@vitest/runner@3.2.3': - resolution: {integrity: sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==} + '@vitest/runner@4.1.0-beta.4': + resolution: {integrity: sha512-/uhv354dTwbqiDCAk9IUCVXNBGByn40Xh8DkJ5EdzqEYN3aw5x25OmmpxpSjxlkBkoxQEUP3zg6WOUGeTYKoOw==} - '@vitest/snapshot@2.1.9': - resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - '@vitest/snapshot@3.2.3': - resolution: {integrity: sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==} + '@vitest/snapshot@4.1.0-beta.4': + resolution: {integrity: sha512-ukET4KPzUZgCD1flrQFuhuXS9J2/c6bzMoRCCmjB12+JwwgYvxCEE6wURZXRUjwcA6jD2HCpxeKiVq/4Ojz0EQ==} - '@vitest/spy@2.1.9': - resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - '@vitest/spy@3.2.3': - resolution: {integrity: sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==} + '@vitest/spy@4.1.0-beta.4': + resolution: {integrity: sha512-aRsQ3vLSKbEifcMufUXAp1OCBcLEWDDmGAItaVa2WDwh08pxcpoDCCfchCDDYjftajq5Mi22ZNN9Afy9IW4ZJw==} - '@vitest/ui@2.1.9': - resolution: {integrity: sha512-izzd2zmnk8Nl5ECYkW27328RbQ1nKvkm6Bb5DAaz1Gk59EbLkiCMa6OLT0NoaAYTjOFS6N+SMYW1nh4/9ljPiw==} + '@vitest/ui@3.2.4': + resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==} peerDependencies: - vitest: 2.1.9 + vitest: 3.2.4 - '@vitest/ui@3.2.3': - resolution: {integrity: sha512-9aR2tY/WT7GRHGEH/9sSIipJqeA21Eh3C6xmiOVmfyBCFmezUSUFLalpaSmRHlRzWCKQU10yz3AHhKuYcdnZGQ==} + '@vitest/ui@4.1.0-beta.4': + resolution: {integrity: sha512-ls/+b2iM34IRacIzUClZwSdpvkg+L0Zz40m/Z6lKFUUXk1hG/bYgGTj5s4bykxrbgIsFRuTXr00HwmxDWx6VlA==} peerDependencies: - vitest: 3.2.3 + vitest: 4.1.0-beta.4 '@vitest/utils@2.1.8': resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} - '@vitest/utils@2.1.9': - resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - '@vitest/utils@3.2.3': - resolution: {integrity: sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==} + '@vitest/utils@4.1.0-beta.4': + resolution: {integrity: sha512-c6oj0FpdLwmOisNpeVwhXqwe9Harehj+n9Pfz4/Iv45dSR32VHDzK2uosqT2ocCZhgzXwX4xpL8thl2Wr/wyrw==} '@volar/language-core@2.3.4': resolution: {integrity: sha512-wXBhY11qG6pCDAqDnbBRFIDSIwbqkWI7no+lj5+L7IlA7HRIjRP7YQLGzT0LF4lS6eHkMSsclXqy9DwYJasZTQ==} @@ -9085,6 +9166,9 @@ packages: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true + a-sync-waterfall@1.0.1: + resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -9263,6 +9347,9 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1@0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} @@ -9395,6 +9482,76 @@ packages: before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + better-auth@1.4.19: + resolution: {integrity: sha512-3RlZJcA0+NH25wYD85vpIGwW9oSTuEmLIaGbT8zg41w/Pa2hVWHKedjoUHHJtnzkBXzDb+CShkLnSw7IThDdqQ==} + peerDependencies: + '@lynx-js/react': '*' + '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 + '@sveltejs/kit': ^2.0.0 + '@tanstack/react-start': ^1.0.0 + '@tanstack/solid-start': ^1.0.0 + better-sqlite3: ^12.0.0 + drizzle-kit: '>=0.31.4' + drizzle-orm: '>=0.41.0' + mongodb: ^6.0.0 || ^7.0.0 + mysql2: ^3.0.0 + next: ^14.0.0 || ^15.0.0 || ^16.0.0 + pg: ^8.0.0 + prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + solid-js: ^1.0.0 + svelte: ^4.0.0 || ^5.0.0 + vitest: ^2.0.0 || ^3.0.0 || ^4.0.0 + vue: ^3.0.0 + peerDependenciesMeta: + '@lynx-js/react': + optional: true + '@prisma/client': + optional: true + '@sveltejs/kit': + optional: true + '@tanstack/react-start': + optional: true + '@tanstack/solid-start': + optional: true + better-sqlite3: + optional: true + drizzle-kit: + optional: true + drizzle-orm: + optional: true + mongodb: + optional: true + mysql2: + optional: true + next: + optional: true + pg: + optional: true + prisma: + optional: true + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vitest: + optional: true + vue: + optional: true + + better-call@1.1.8: + resolution: {integrity: sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw==} + peerDependencies: + zod: ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -9406,9 +9563,6 @@ packages: birpc@0.2.14: resolution: {integrity: sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA==} - birpc@2.6.1: - resolution: {integrity: sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==} - birpc@2.8.0: resolution: {integrity: sha512-Bz2a4qD/5GRhiHSwj30c/8kC8QGj12nNDwz3D4ErQ4Xhy35dsSDvF+RA/tWpjyU0pdGtSDiEk6B5fBGE1qNVhw==} @@ -9548,14 +9702,14 @@ packages: caseless@0.12.0: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - chai@5.1.2: - resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} - engines: {node: '>=12'} - chai@5.2.0: resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} engines: {node: '>=12'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -9701,6 +9855,10 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -10336,6 +10494,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -10590,14 +10751,14 @@ packages: expect-type@0.15.0: resolution: {integrity: sha512-yWnriYB4e8G54M5/fAFj7rCIBiKs1HAACaY13kCz6Ku0dezjS9aMcfcdVK2X8Tv2tEV1BPz/wKfQ7WA4S/d8aA==} - expect-type@1.1.0: - resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} - engines: {node: '>=12.0.0'} - expect-type@1.2.1: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -11008,8 +11169,8 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - graphql@16.8.1: - resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} + graphql@16.13.0: + resolution: {integrity: sha512-uSisMYERbaB9bkA9M4/4dnqyktaEkf1kMHNKq/7DHyxVeWqHQ2mBmVqm5u6/FVHwF3iCNalKcg82Zfl+tffWoA==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} gunzip-maybe@1.4.2: @@ -11597,6 +11758,9 @@ packages: jose@5.9.3: resolution: {integrity: sha512-egLIoYSpcd+QUF+UHgobt5YzI2Pkw/H39ou9suW687MY6PmCwPmkNV/4TNjn1p2tX5xO3j0d0sq5hiYE24bSlg==} + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -11723,6 +11887,10 @@ packages: resolution: {integrity: sha512-HzhziW6sc5m0pwi5M196+7cEBtbt0lCYi67wNsiwMUmz833wloE0gbzJPWKs1gliFKQb34huItDQX97LyOdPdA==} engines: {node: '>=18'} + kysely@0.28.11: + resolution: {integrity: sha512-zpGIFg0HuoC893rIjYX1BETkVWdDnzTzF5e0kWXJFg5lE0k1/LfNWBejrcnOFu8Q2Rfq/hTDTU7XLUM8QOrpzg==} + engines: {node: '>=20.0.0'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -11964,6 +12132,9 @@ packages: loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lowdb@1.0.0: resolution: {integrity: sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==} engines: {node: '>=4'} @@ -12198,8 +12369,8 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msw@2.12.0: - resolution: {integrity: sha512-jzf2eVnd8+iWXN74dccLrHUw3i3hFVvNVQRWS4vBl2KxaUt7Tdur0Eyda/DODGFkZDu2P5MXaeLe/9Qx8PZkrg==} + msw@2.12.4: + resolution: {integrity: sha512-rHNiVfTyKhzc0EjoXUBVGteNKBevdjOlVC6GlIRXpy+/3LHEIGRovnB5WPjcvmNODVQ1TNFnoa7wsGbd0V3epg==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -12243,6 +12414,10 @@ packages: engines: {node: ^18 || >=20} hasBin: true + nanostores@1.1.1: + resolution: {integrity: sha512-EYJqS25r2iBeTtGQCHidXl1VfZ1jXM7Q04zXJOrMlxVVmD0ptxJaNux92n1mJ7c5lN3zTq12MhH/8x59nP+qmg==} + engines: {node: ^20.0.0 || >=22.0.0} + napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -12334,6 +12509,16 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nunjucks@3.2.4: + resolution: {integrity: sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==} + engines: {node: '>= 6.9.0'} + hasBin: true + peerDependencies: + chokidar: ^3.3.0 + peerDependenciesMeta: + chokidar: + optional: true + nypm@0.6.1: resolution: {integrity: sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==} engines: {node: ^14.16.0 || >=16.10.0} @@ -12394,6 +12579,9 @@ packages: ms: optional: true + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -13481,6 +13669,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rou3@0.7.12: + resolution: {integrity: sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -13615,6 +13806,9 @@ packages: set-cookie-parser@2.6.0: resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -13838,8 +14032,8 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.8.0: - resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -13940,6 +14134,15 @@ packages: strip-literal@3.0.0: resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + stripe@20.4.0: + resolution: {integrity: sha512-F/aN1IQ9vHmlyLNi3DkiIbyzQb6gyBG0uYFd/VrEVQSc9BLtlgknPUx0EvzZdBMRLFuRaPFIFd7Mxwtg7Pbwzw==} + engines: {node: '>=16'} + peerDependencies: + '@types/node': ^20.19.9 + peerDependenciesMeta: + '@types/node': + optional: true + stripe@9.16.0: resolution: {integrity: sha512-Dn8K+jSoQcXjxCobRI4HXUdHjOXsiF/KszK49fJnkbeCFjZ3EZxLG2JiM/CX+Hcq27NBDtv/Sxhvy+HhTmvyaQ==} engines: {node: ^8.1 || >=10.*} @@ -14003,6 +14206,10 @@ packages: tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} @@ -14064,9 +14271,6 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@0.3.1: - resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} - tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -14085,12 +14289,8 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinypool@1.0.1: - resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinypool@1.1.0: - resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} tinyrainbow@1.2.0: @@ -14101,8 +14301,8 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} tinyspy@4.0.3: @@ -14363,14 +14563,14 @@ packages: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} engines: {node: '>=10'} - type-fest@4.26.1: - resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} - engines: {node: '>=16'} - type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} + type-fest@5.4.4: + resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} + engines: {node: '>=20'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -14613,13 +14813,8 @@ packages: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} - vite-node@2.1.9: - resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite-node@3.2.3: - resolution: {integrity: sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true @@ -14757,25 +14952,68 @@ packages: yaml: optional: true + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.9 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + 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: vitest: '>=2' - vitest@2.1.9: - resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} - engines: {node: ^18.0.0 || >=20.0.0} + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 '@types/node': ^20.19.9 - '@vitest/browser': 2.1.9 - '@vitest/ui': 2.1.9 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true + '@types/debug': + optional: true '@types/node': optional: true '@vitest/browser': @@ -14787,26 +15025,33 @@ packages: jsdom: optional: true - vitest@3.2.3: - resolution: {integrity: sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vitest@4.1.0-beta.4: + resolution: {integrity: sha512-MimZ9YLGPFhLGVR+WWQdSvc2vmOwH75f5SDRttg+cnlBJ0XNs6mTvrV4Oi6xIF3FOsH3LPs2f/LM1WyCGy40qA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 + '@opentelemetry/api': ^1.9.0 '@types/node': ^20.19.9 - '@vitest/browser': 3.2.3 - '@vitest/ui': 3.2.3 + '@vitest/browser-playwright': 4.1.0-beta.4 + '@vitest/browser-preview': 4.1.0-beta.4 + '@vitest/browser-webdriverio': 4.1.0-beta.4 + '@vitest/ui': 4.1.0-beta.4 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/debug': + '@opentelemetry/api': optional: true '@types/node': optional: true - '@vitest/browser': + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': optional: true '@vitest/ui': optional: true @@ -15044,6 +15289,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + snapshots: '@actions/core@1.11.1': @@ -15879,6 +16127,36 @@ snapshots: optionalDependencies: '@types/react': 19.2.10 + '@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1)': + dependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + '@standard-schema/spec': 1.0.0 + better-call: 1.1.8(zod@4.3.6) + jose: 5.9.3 + kysely: 0.28.11 + nanostores: 1.1.1 + zod: 4.3.6 + + '@better-auth/stripe@1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1))(better-auth@1.4.19(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(typescript@5.8.3))(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(vitest@4.1.0-beta.4))(better-call@1.1.8(zod@4.3.6))(stripe@20.4.0(@types/node@20.19.9))': + dependencies: + '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + better-auth: 1.4.19(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(typescript@5.8.3))(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(vitest@4.1.0-beta.4) + better-call: 1.1.8(zod@4.3.6) + defu: 6.1.4 + stripe: 20.4.0(@types/node@20.19.9) + zod: 4.3.6 + + '@better-auth/telemetry@1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1))': + dependencies: + '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + + '@better-auth/utils@0.3.0': {} + + '@better-fetch/fetch@1.1.21': {} + '@bomb.sh/tab@0.0.12(cac@6.7.14)(citty@0.1.6)': optionalDependencies: cac: 6.7.14 @@ -16286,7 +16564,7 @@ snapshots: dependencies: react: 19.2.1 - '@cloudflare/kumo@1.5.0(@phosphor-icons/react@2.1.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + '@cloudflare/kumo@1.5.0(@phosphor-icons/react@2.1.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(zod@4.3.6)': dependencies: '@base-ui/react': 1.1.0(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@phosphor-icons/react': 2.1.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -16294,6 +16572,8 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) tailwind-merge: 3.4.0 + optionalDependencies: + zod: 4.3.6 transitivePeerDependencies: - '@types/react' @@ -16413,33 +16693,16 @@ snapshots: lodash.memoize: 4.1.2 marked: 0.3.19 - '@cloudflare/vitest-pool-workers@0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@2.1.9)': + '@cloudflare/vitest-pool-workers@0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@4.1.0-beta.4)(@vitest/snapshot@4.1.0-beta.4)(vitest@3.2.4)': dependencies: - '@vitest/runner': 3.2.3 - '@vitest/snapshot': 3.2.3 + '@vitest/runner': 4.1.0-beta.4 + '@vitest/snapshot': 4.1.0-beta.4 birpc: 0.2.14 cjs-module-lexer: 1.2.3 devalue: 5.3.2 miniflare: 4.20251210.0 semver: 7.7.3 - vitest: 2.1.9(@types/node@20.19.9)(@vitest/ui@2.1.9)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3)) - wrangler: 4.54.0(@cloudflare/workers-types@4.20260305.0) - zod: 3.25.76 - transitivePeerDependencies: - - '@cloudflare/workers-types' - - bufferutil - - utf-8-validate - - '@cloudflare/vitest-pool-workers@0.10.15(@cloudflare/workers-types@4.20260305.0)(@vitest/runner@3.2.3)(@vitest/snapshot@3.2.3)(vitest@3.2.3)': - dependencies: - '@vitest/runner': 3.2.3 - '@vitest/snapshot': 3.2.3 - birpc: 0.2.14 - cjs-module-lexer: 1.2.3 - devalue: 5.3.2 - miniflare: 4.20251210.0 - semver: 7.7.3 - vitest: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1) wrangler: 4.54.0(@cloudflare/workers-types@4.20260305.0) zod: 3.25.76 transitivePeerDependencies: @@ -17754,6 +18017,10 @@ snapshots: yaml: 2.8.1 yargs: 17.7.2 + '@noble/ciphers@2.1.1': {} + + '@noble/hashes@2.0.1': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -18978,6 +19245,8 @@ snapshots: '@standard-schema/spec@1.0.0': {} + '@standard-schema/spec@1.1.0': {} + '@styled-system/background@5.1.2': dependencies: '@styled-system/core': 5.1.2 @@ -19152,21 +19421,21 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.0.15 '@tailwindcss/oxide-win32-x64-msvc': 4.0.15 - '@tailwindcss/vite@4.0.15(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': + '@tailwindcss/vite@4.0.15(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@tailwindcss/node': 4.0.15 '@tailwindcss/oxide': 4.0.15 lightningcss: 1.29.2 tailwindcss: 4.0.15 - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + vite: 7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) - '@tailwindcss/vite@4.0.15(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': + '@tailwindcss/vite@4.0.15(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@tailwindcss/node': 4.0.15 '@tailwindcss/oxide': 4.0.15 lightningcss: 1.29.2 tailwindcss: 4.0.15 - vite: 7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) '@tanstack/history@1.154.14': {} @@ -19231,7 +19500,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.158.0(@tanstack/react-router@1.158.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': + '@tanstack/router-plugin@1.158.0(@tanstack/react-router@1.158.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.5) @@ -19248,7 +19517,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.158.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -19445,6 +19714,8 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@types/nunjucks@3.2.6': {} + '@types/pg@8.11.6': dependencies: '@types/node': 20.19.9 @@ -19532,7 +19803,7 @@ snapshots: '@types/stack-utils@2.0.3': {} - '@types/statuses@2.0.5': {} + '@types/statuses@2.0.6': {} '@types/stoppable@1.1.3': dependencies: @@ -19830,18 +20101,29 @@ snapshots: dependencies: vite: 7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) - '@vitejs/plugin-react@4.3.3(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': + '@vitejs/plugin-react@4.3.3(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@3.12.10)(yaml@2.8.1))': 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: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@3.12.10)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.7.0(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': + '@vitejs/plugin-react@4.3.3(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': + 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: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-react@4.7.0(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -19849,11 +20131,11 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + vite: 7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.7.0(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': + '@vitejs/plugin-react@4.7.0(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -19861,135 +20143,144 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitest/expect@2.1.9': + '@vitest/expect@3.2.4': dependencies: - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 - tinyrainbow: 1.2.0 + tinyrainbow: 2.0.0 - '@vitest/expect@3.2.3': + '@vitest/expect@4.1.0-beta.4': dependencies: + '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.2 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 - chai: 5.2.0 - tinyrainbow: 2.0.0 + '@vitest/spy': 4.1.0-beta.4 + '@vitest/utils': 4.1.0-beta.4 + chai: 6.2.2 + tinyrainbow: 3.0.3 - '@vitest/mocker@2.1.9(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': + '@vitest/mocker@3.2.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': dependencies: - '@vitest/spy': 2.1.9 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.0(@types/node@20.19.9)(typescript@5.8.3) + msw: 2.12.4(@types/node@20.19.9)(typescript@5.8.3) vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) - '@vitest/mocker@2.1.9(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': + '@vitest/mocker@3.2.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': dependencies: - '@vitest/spy': 2.1.9 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.0(@types/node@20.19.9)(typescript@5.9.3) + msw: 2.12.4(@types/node@20.19.9)(typescript@5.9.3) vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) - '@vitest/mocker@3.2.3(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.2))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': + '@vitest/mocker@4.1.0-beta.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.2))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@vitest/spy': 3.2.3 + '@vitest/spy': 4.1.0-beta.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.0(@types/node@20.19.9)(typescript@5.8.2) - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + msw: 2.12.4(@types/node@20.19.9)(typescript@5.8.2) + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) - '@vitest/mocker@3.2.3(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': + '@vitest/mocker@4.1.0-beta.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@vitest/spy': 3.2.3 + '@vitest/spy': 4.1.0-beta.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.0(@types/node@20.19.9)(typescript@5.8.3) - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + msw: 2.12.4(@types/node@20.19.9)(typescript@5.8.3) + vite: 7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) - '@vitest/mocker@3.2.3(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2))': + '@vitest/mocker@4.1.0-beta.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - '@vitest/spy': 3.2.3 + '@vitest/spy': 4.1.0-beta.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.0(@types/node@20.19.9)(typescript@5.9.3) - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + msw: 2.12.4(@types/node@20.19.9)(typescript@5.8.3) + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) - '@vitest/pretty-format@2.1.8': + '@vitest/mocker@4.1.0-beta.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1))': dependencies: - tinyrainbow: 1.2.0 + '@vitest/spy': 4.1.0-beta.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.12.4(@types/node@20.19.9)(typescript@5.9.3) + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) - '@vitest/pretty-format@2.1.9': + '@vitest/pretty-format@2.1.8': dependencies: tinyrainbow: 1.2.0 - '@vitest/pretty-format@3.2.3': + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - '@vitest/runner@2.1.9': + '@vitest/pretty-format@4.1.0-beta.4': dependencies: - '@vitest/utils': 2.1.9 - pathe: 1.1.2 + tinyrainbow: 3.0.3 - '@vitest/runner@3.2.3': + '@vitest/runner@3.2.4': dependencies: - '@vitest/utils': 3.2.3 + '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.0.0 - '@vitest/snapshot@2.1.9': + '@vitest/runner@4.1.0-beta.4': dependencies: - '@vitest/pretty-format': 2.1.9 - magic-string: 0.30.21 - pathe: 1.1.2 + '@vitest/utils': 4.1.0-beta.4 + pathe: 2.0.3 - '@vitest/snapshot@3.2.3': + '@vitest/snapshot@3.2.4': dependencies: - '@vitest/pretty-format': 3.2.3 + '@vitest/pretty-format': 3.2.4 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@2.1.9': + '@vitest/snapshot@4.1.0-beta.4': dependencies: - tinyspy: 3.0.2 + '@vitest/pretty-format': 4.1.0-beta.4 + '@vitest/utils': 4.1.0-beta.4 + magic-string: 0.30.21 + pathe: 2.0.3 - '@vitest/spy@3.2.3': + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.3 - '@vitest/ui@2.1.9(vitest@2.1.9)': + '@vitest/spy@4.1.0-beta.4': {} + + '@vitest/ui@3.2.4(vitest@3.2.4)': dependencies: - '@vitest/utils': 2.1.9 + '@vitest/utils': 3.2.4 fflate: 0.8.2 flatted: 3.3.3 - pathe: 1.1.2 + pathe: 2.0.3 sirv: 3.0.2 tinyglobby: 0.2.15 - tinyrainbow: 1.2.0 - vitest: 2.1.9(@types/node@20.19.9)(@vitest/ui@2.1.9)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3)) - optional: true + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) - '@vitest/ui@3.2.3(vitest@3.2.3)': + '@vitest/ui@4.1.0-beta.4(vitest@4.1.0-beta.4)': dependencies: - '@vitest/utils': 3.2.3 + '@vitest/utils': 4.1.0-beta.4 fflate: 0.8.2 flatted: 3.3.3 pathe: 2.0.3 sirv: 3.0.2 tinyglobby: 0.2.15 - tinyrainbow: 2.0.0 - vitest: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + tinyrainbow: 3.0.3 + vitest: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) '@vitest/utils@2.1.8': dependencies: @@ -19997,17 +20288,16 @@ snapshots: loupe: 3.1.3 tinyrainbow: 1.2.0 - '@vitest/utils@2.1.9': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 2.1.9 - loupe: 3.1.3 - tinyrainbow: 1.2.0 + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 - '@vitest/utils@3.2.3': + '@vitest/utils@4.1.0-beta.4': dependencies: - '@vitest/pretty-format': 3.2.3 - loupe: 3.1.3 - tinyrainbow: 2.0.0 + '@vitest/pretty-format': 4.1.0-beta.4 + tinyrainbow: 3.0.3 '@volar/language-core@2.3.4': dependencies: @@ -20115,6 +20405,8 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 + a-sync-waterfall@1.0.1: {} + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -20324,6 +20616,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + asap@2.0.6: {} + asn1@0.2.6: dependencies: safer-buffer: 2.1.2 @@ -20457,6 +20751,38 @@ snapshots: before-after-hook@2.2.3: {} + better-auth@1.4.19(@prisma/client@7.0.1(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(typescript@5.8.3))(mysql2@3.15.3)(pg@8.16.3)(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(vitest@4.1.0-beta.4): + dependencies: + '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@5.9.3)(kysely@0.28.11)(nanostores@1.1.1) + '@better-auth/telemetry': 1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.3)(kysely@0.28.11)(nanostores@1.1.1)) + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + '@noble/ciphers': 2.1.1 + '@noble/hashes': 2.0.1 + better-call: 1.1.8(zod@4.3.6) + defu: 6.1.4 + jose: 6.1.3 + kysely: 0.28.11 + nanostores: 1.1.1 + zod: 4.3.6 + optionalDependencies: + '@prisma/client': 7.0.1(prisma@7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3))(typescript@5.8.3) + mysql2: 3.15.3 + pg: 8.16.3 + prisma: 7.0.1(@types/react@19.2.10)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.8.3) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + vitest: 4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) + + better-call@1.1.8(zod@4.3.6): + dependencies: + '@better-auth/utils': 0.3.0 + '@better-fetch/fetch': 1.1.21 + rou3: 0.7.12 + set-cookie-parser: 2.7.2 + optionalDependencies: + zod: 4.3.6 + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -20465,8 +20791,6 @@ snapshots: birpc@0.2.14: {} - birpc@2.6.1: {} - birpc@2.8.0: {} bl@4.1.0: @@ -20641,14 +20965,6 @@ snapshots: caseless@0.12.0: {} - chai@5.1.2: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.1.3 - pathval: 2.0.0 - chai@5.2.0: dependencies: assertion-error: 2.0.1 @@ -20657,6 +20973,8 @@ snapshots: loupe: 3.1.3 pathval: 2.0.0 + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -20798,6 +21116,8 @@ snapshots: commander@4.1.1: {} + commander@5.1.0: {} + commander@7.2.0: {} comment-json@4.5.0: @@ -21228,7 +21548,7 @@ snapshots: dot-prop@9.0.0: dependencies: - type-fest: 4.26.1 + type-fest: 4.41.0 dotenv-cli@7.3.0: dependencies: @@ -21483,6 +21803,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -22005,10 +22327,10 @@ snapshots: expect-type@0.15.0: {} - expect-type@1.1.0: {} - expect-type@1.2.1: {} + expect-type@1.3.0: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -22258,7 +22580,7 @@ snapshots: escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 - statuses: 2.0.1 + statuses: 2.0.2 transitivePeerDependencies: - supports-color @@ -22550,7 +22872,7 @@ snapshots: graphemer@1.4.0: {} - graphql@16.8.1: {} + graphql@16.13.0: {} gunzip-maybe@1.4.2: dependencies: @@ -23106,6 +23428,8 @@ snapshots: jose@5.9.3: {} + jose@6.1.3: {} + joycon@3.1.1: {} js-base64@3.7.7: {} @@ -23227,6 +23551,8 @@ snapshots: ky@1.7.5: {} + kysely@0.28.11: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -23408,6 +23734,8 @@ snapshots: loupe@3.1.3: {} + loupe@3.2.1: {} + lowdb@1.0.0: dependencies: graceful-fs: 4.2.11 @@ -23613,14 +23941,14 @@ snapshots: ms@2.1.3: {} - msw@2.12.0(@types/node@20.19.9)(typescript@5.8.2): + msw@2.12.4(@types/node@20.19.9)(typescript@5.8.2): dependencies: '@inquirer/confirm': 5.1.21(@types/node@20.19.9) '@mswjs/interceptors': 0.40.0 '@open-draft/deferred-promise': 2.2.0 - '@types/statuses': 2.0.5 + '@types/statuses': 2.0.6 cookie: 1.0.2 - graphql: 16.8.1 + graphql: 16.13.0 headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 @@ -23630,7 +23958,7 @@ snapshots: statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.0 - type-fest: 4.41.0 + type-fest: 5.4.4 until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: @@ -23639,14 +23967,14 @@ snapshots: - '@types/node' optional: true - msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3): + msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3): dependencies: '@inquirer/confirm': 5.1.21(@types/node@20.19.9) '@mswjs/interceptors': 0.40.0 '@open-draft/deferred-promise': 2.2.0 - '@types/statuses': 2.0.5 + '@types/statuses': 2.0.6 cookie: 1.0.2 - graphql: 16.8.1 + graphql: 16.13.0 headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 @@ -23656,7 +23984,7 @@ snapshots: statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.0 - type-fest: 4.41.0 + type-fest: 5.4.4 until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: @@ -23664,14 +23992,14 @@ snapshots: transitivePeerDependencies: - '@types/node' - msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3): + msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3): dependencies: '@inquirer/confirm': 5.1.21(@types/node@20.19.9) '@mswjs/interceptors': 0.40.0 '@open-draft/deferred-promise': 2.2.0 - '@types/statuses': 2.0.5 + '@types/statuses': 2.0.6 cookie: 1.0.2 - graphql: 16.8.1 + graphql: 16.13.0 headers-polyfill: 4.0.3 is-node-process: 1.2.0 outvariant: 1.4.3 @@ -23681,7 +24009,7 @@ snapshots: statuses: 2.0.2 strict-event-emitter: 0.5.1 tough-cookie: 6.0.0 - type-fest: 4.41.0 + type-fest: 5.4.4 until-async: 3.0.2 yargs: 17.7.2 optionalDependencies: @@ -23725,6 +24053,8 @@ snapshots: nanoid@5.1.0: {} + nanostores@1.1.1: {} + napi-build-utils@2.0.0: optional: true @@ -23800,6 +24130,14 @@ snapshots: dependencies: boolbase: 1.0.0 + nunjucks@3.2.4(chokidar@3.6.0): + dependencies: + a-sync-waterfall: 1.0.1 + asap: 2.0.6 + commander: 5.1.0 + optionalDependencies: + chokidar: 3.6.0 + nypm@0.6.1: dependencies: citty: 0.1.6 @@ -23869,6 +24207,8 @@ snapshots: optionalDependencies: ms: 2.1.3 + obug@2.1.1: {} + ohash@2.0.11: {} on-exit-leak-free@2.1.2: {} @@ -24753,7 +25093,7 @@ snapshots: '@types/normalize-package-data': 2.4.4 normalize-package-data: 6.0.2 parse-json: 8.3.0 - type-fest: 4.26.1 + type-fest: 4.41.0 unicorn-magic: 0.1.0 read-yaml-file@1.1.0: @@ -24924,7 +25264,7 @@ snapshots: '@babel/parser': 7.28.4 '@babel/types': 7.28.4 ast-kit: 2.1.3 - birpc: 2.6.1 + birpc: 2.8.0 debug: 4.4.3 dts-resolver: 2.1.2 get-tsconfig: 4.13.0 @@ -25053,6 +25393,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.44.1 fsevents: 2.3.3 + rou3@0.7.12: {} + router@2.2.0: dependencies: debug: 4.4.1(supports-color@9.2.2) @@ -25184,7 +25526,7 @@ snapshots: ms: 2.1.3 on-finished: 2.4.1 range-parser: 1.2.1 - statuses: 2.0.1 + statuses: 2.0.2 transitivePeerDependencies: - supports-color @@ -25216,6 +25558,8 @@ snapshots: set-cookie-parser@2.6.0: {} + set-cookie-parser@2.7.2: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -25504,7 +25848,7 @@ snapshots: statuses@2.0.2: {} - std-env@3.8.0: {} + std-env@3.10.0: {} std-env@3.9.0: {} @@ -25632,6 +25976,10 @@ snapshots: dependencies: js-tokens: 9.0.1 + stripe@20.4.0(@types/node@20.19.9): + optionalDependencies: + '@types/node': 20.19.9 + stripe@9.16.0: dependencies: '@types/node': 20.19.9 @@ -25709,6 +26057,8 @@ snapshots: tabbable@6.4.0: {} + tagged-tag@1.0.0: {} + tailwind-merge@3.4.0: {} tailwindcss@4.0.15: {} @@ -25791,8 +26141,6 @@ snapshots: tinybench@2.9.0: {} - tinyexec@0.3.1: {} - tinyexec@0.3.2: {} tinyexec@1.0.1: {} @@ -25809,15 +26157,13 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinypool@1.0.1: {} - - tinypool@1.1.0: {} + tinypool@1.1.1: {} tinyrainbow@1.2.0: {} tinyrainbow@2.0.0: {} - tinyspy@3.0.2: {} + tinyrainbow@3.0.3: {} tinyspy@4.0.3: {} @@ -26098,10 +26444,12 @@ snapshots: type-fest@1.4.0: {} - type-fest@4.26.1: {} - type-fest@4.41.0: {} + type-fest@5.4.4: + dependencies: + tagged-tag: 1.0.0 + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -26441,25 +26789,7 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-node@2.1.9(@types/node@20.19.9)(lightningcss@1.30.2): - dependencies: - cac: 6.7.14 - debug: 4.4.1(supports-color@9.2.2) - es-module-lexer: 1.7.0 - pathe: 1.1.2 - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite-node@3.2.3(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1): + vite-node@3.2.4(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1): dependencies: cac: 6.7.14 debug: 4.4.1(supports-color@9.2.2) @@ -26480,7 +26810,7 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.0.1(@types/node@20.19.9)(rollup@4.44.1)(typescript@5.8.3)(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)): + vite-plugin-dts@4.0.1(@types/node@20.19.9)(rollup@4.44.1)(typescript@5.8.3)(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)): dependencies: '@microsoft/api-extractor': 7.47.4(@types/node@20.19.9) '@rollup/pluginutils': 5.1.0(rollup@4.44.1) @@ -26494,18 +26824,18 @@ snapshots: typescript: 5.8.3 vue-tsc: 2.0.29(typescript@5.8.3) optionalDependencies: - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-svgr@4.5.0(rollup@4.44.1)(typescript@5.8.3)(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)): + vite-plugin-svgr@4.5.0(rollup@4.44.1)(typescript@5.8.3)(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.44.1) '@svgr/core': 8.1.0(typescript@5.8.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3)) - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) transitivePeerDependencies: - rollup - supports-color @@ -26564,94 +26894,54 @@ snapshots: tsx: 4.21.0 yaml: 2.8.1 - vitest-websocket-mock@0.4.0(vitest@3.2.3): + vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@3.12.10)(yaml@2.8.1): dependencies: - '@vitest/utils': 2.1.8 - mock-socket: 9.3.1 - vitest: 3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) - - vitest@2.1.9(@types/node@20.19.9)(@vitest/ui@2.1.9)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3)): - dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.1.2 - debug: 4.4.1(supports-color@9.2.2) - expect-type: 1.1.0 - magic-string: 0.30.17 - pathe: 1.1.2 - std-env: 3.8.0 - tinybench: 2.9.0 - tinyexec: 0.3.1 - tinypool: 1.0.1 - tinyrainbow: 1.2.0 - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) - vite-node: 2.1.9(@types/node@20.19.9)(lightningcss@1.30.2) - why-is-node-running: 2.3.0 + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.44.1 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 20.19.9 - '@vitest/ui': 2.1.9(vitest@2.1.9) - transitivePeerDependencies: - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + tsx: 3.12.10 + yaml: 2.8.1 - vitest@2.1.9(@types/node@20.19.9)(@vitest/ui@2.1.9)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3)): + vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1): dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.1.2 - debug: 4.4.1(supports-color@9.2.2) - expect-type: 1.1.0 - magic-string: 0.30.17 - pathe: 1.1.2 - std-env: 3.8.0 - tinybench: 2.9.0 - tinyexec: 0.3.1 - tinypool: 1.0.1 - tinyrainbow: 1.2.0 - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) - vite-node: 2.1.9(@types/node@20.19.9)(lightningcss@1.30.2) - why-is-node-running: 2.3.0 + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.44.1 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 20.19.9 - '@vitest/ui': 2.1.9(vitest@2.1.9) - transitivePeerDependencies: - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + tsx: 4.21.0 + yaml: 2.8.1 + + vitest-websocket-mock@0.4.0(vitest@3.2.4): + dependencies: + '@vitest/utils': 2.1.8 + mock-socket: 9.3.1 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) - vitest@3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.2))(tsx@4.21.0)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 - '@vitest/expect': 3.2.3 - '@vitest/mocker': 3.2.3(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.2))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) - '@vitest/pretty-format': 3.2.3 - '@vitest/runner': 3.2.3 - '@vitest/snapshot': 3.2.3 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 debug: 4.4.1(supports-color@9.2.2) expect-type: 1.2.1 @@ -26662,15 +26952,15 @@ snapshots: tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.15 - tinypool: 1.1.0 + tinypool: 1.1.1 tinyrainbow: 2.0.0 vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) - vite-node: 3.2.3(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 '@types/node': 20.19.9 - '@vitest/ui': 3.2.3(vitest@3.2.3) + '@vitest/ui': 3.2.4(vitest@3.2.4) transitivePeerDependencies: - jiti - less @@ -26685,16 +26975,16 @@ snapshots: - tsx - yaml - vitest@3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.4)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 - '@vitest/expect': 3.2.3 - '@vitest/mocker': 3.2.3(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) - '@vitest/pretty-format': 3.2.3 - '@vitest/runner': 3.2.3 - '@vitest/snapshot': 3.2.3 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 debug: 4.4.1(supports-color@9.2.2) expect-type: 1.2.1 @@ -26705,15 +26995,15 @@ snapshots: tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.15 - tinypool: 1.1.0 + tinypool: 1.1.1 tinyrainbow: 2.0.0 vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) - vite-node: 3.2.3(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + vite-node: 3.2.4(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 '@types/node': 20.19.9 - '@vitest/ui': 3.2.3(vitest@3.2.3) + '@vitest/ui': 3.2.4(vitest@3.2.4) transitivePeerDependencies: - jiti - less @@ -26728,91 +27018,118 @@ snapshots: - tsx - yaml - vitest@3.2.3(@types/debug@4.1.12)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.1): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.3 - '@vitest/mocker': 3.2.3(msw@2.12.0(@types/node@20.19.9)(typescript@5.9.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) - '@vitest/pretty-format': 3.2.3 - '@vitest/runner': 3.2.3 - '@vitest/snapshot': 3.2.3 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 - chai: 5.2.0 - debug: 4.4.1(supports-color@9.2.2) - expect-type: 1.2.1 + vitest@4.1.0-beta.4(@opentelemetry/api@1.7.0)(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)): + dependencies: + '@vitest/expect': 4.1.0-beta.4 + '@vitest/mocker': 4.1.0-beta.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) + '@vitest/pretty-format': 4.1.0-beta.4 + '@vitest/runner': 4.1.0-beta.4 + '@vitest/snapshot': 4.1.0-beta.4 + '@vitest/spy': 4.1.0-beta.4 + '@vitest/utils': 4.1.0-beta.4 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.9.0 + std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.2 tinyglobby: 0.2.15 - tinypool: 1.1.0 - tinyrainbow: 2.0.0 - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) - vite-node: 3.2.3(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/debug': 4.1.12 + '@opentelemetry/api': 1.7.0 '@types/node': 20.19.9 - '@vitest/ui': 3.2.3(vitest@3.2.3) + '@vitest/ui': 4.1.0-beta.4(vitest@4.1.0-beta.4) transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vitest@3.2.3(@types/debug@4.1.7)(@types/node@20.19.9)(@vitest/ui@3.2.3)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(tsx@4.21.0)(yaml@2.8.1): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.3 - '@vitest/mocker': 3.2.3(msw@2.12.0(@types/node@20.19.9)(typescript@5.8.3))(vite@5.4.14(@types/node@20.19.9)(lightningcss@1.30.2)) - '@vitest/pretty-format': 3.2.3 - '@vitest/runner': 3.2.3 - '@vitest/snapshot': 3.2.3 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 - chai: 5.2.0 - debug: 4.4.1(supports-color@9.2.2) - expect-type: 1.2.1 + vitest@4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.2))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)): + dependencies: + '@vitest/expect': 4.1.0-beta.4 + '@vitest/mocker': 4.1.0-beta.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.2))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) + '@vitest/pretty-format': 4.1.0-beta.4 + '@vitest/runner': 4.1.0-beta.4 + '@vitest/snapshot': 4.1.0-beta.4 + '@vitest/spy': 4.1.0-beta.4 + '@vitest/utils': 4.1.0-beta.4 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.9.0 + std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.2 tinyglobby: 0.2.15 - tinypool: 1.1.0 - tinyrainbow: 2.0.0 - vite: 5.4.14(@types/node@20.19.9)(lightningcss@1.30.2) - vite-node: 3.2.3(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(supports-color@9.2.2)(tsx@4.21.0)(yaml@2.8.1) + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/debug': 4.1.7 '@types/node': 20.19.9 - '@vitest/ui': 3.2.3(vitest@3.2.3) + '@vitest/ui': 4.1.0-beta.4(vitest@4.1.0-beta.4) + transitivePeerDependencies: + - msw + + vitest@4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)): + dependencies: + '@vitest/expect': 4.1.0-beta.4 + '@vitest/mocker': 4.1.0-beta.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.8.3))(vite@7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) + '@vitest/pretty-format': 4.1.0-beta.4 + '@vitest/runner': 4.1.0-beta.4 + '@vitest/snapshot': 4.1.0-beta.4 + '@vitest/spy': 4.1.0-beta.4 + '@vitest/utils': 4.1.0-beta.4 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.1.12(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.9 + '@vitest/ui': 4.1.0-beta.4(vitest@4.1.0-beta.4) + transitivePeerDependencies: + - msw + + vitest@4.1.0-beta.4(@types/node@20.19.9)(@vitest/ui@4.1.0-beta.4)(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)): + dependencies: + '@vitest/expect': 4.1.0-beta.4 + '@vitest/mocker': 4.1.0-beta.4(msw@2.12.4(@types/node@20.19.9)(typescript@5.9.3))(vite@7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1)) + '@vitest/pretty-format': 4.1.0-beta.4 + '@vitest/runner': 4.1.0-beta.4 + '@vitest/snapshot': 4.1.0-beta.4 + '@vitest/spy': 4.1.0-beta.4 + '@vitest/utils': 4.1.0-beta.4 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@20.19.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.9 + '@vitest/ui': 4.1.0-beta.4(vitest@4.1.0-beta.4) transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml vscode-uri@3.0.8: {} @@ -27077,3 +27394,5 @@ snapshots: zod@3.22.3: {} zod@3.25.76: {} + + zod@4.3.6: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f5c1497599d6..181efcd955c9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -19,16 +19,16 @@ catalog: "@types/node": "^20.19.9" "@typescript-eslint/eslint-plugin": "^8.35.1" "@typescript-eslint/parser": "^8.35.1" - "@vitest/runner": ~3.2.0 - "@vitest/snapshot": ~3.2.0 - "@vitest/ui": ~3.2.0 + "@vitest/runner": 4.1.0-beta.4 + "@vitest/snapshot": 4.1.0-beta.4 + "@vitest/ui": 4.1.0-beta.4 typescript: "~5.8.3" undici: "7.18.2" # Override undici-types from @types/node so that the Cloudflare SDK typings match our installed # version of Undici undici-types: "7.18.2" - vitest: "~3.2.0" - vite: "^5.4.14" + vitest: "4.1.0-beta.4" + vite: "^7.3.1" "ws": "8.18.0" esbuild: "0.27.3" playwright-chromium: "^1.56.1" @@ -37,11 +37,8 @@ catalog: eslint: "^9.39.1" jsonc-parser: "^3.2.0" smol-toml: "^1.5.2" + msw: "2.12.4" "tree-kill": "^1.2.2" - # CAUTION: Most usage of @cloudflare/vitest-pool-workers in this mono repo should use workspace:* instead of this catalog version - # However, some packages (pages-shared, workers-shared, etc...) need to be tested using vitest-pool-workers but are themselves - # ultimately included in vitest-pool-workers (through Wrangler), causing a circular dependency. - "@cloudflare/vitest-pool-workers": "^0.10.11" "capnp-es": "^0.0.14" "capnweb": "^0.5.0" "ci-info": "^4.4.0" @@ -52,3 +49,11 @@ catalogs: # This is because a breaking change to the module runner was introduced in https://github.com/vitejs/vite/pull/20924 and released in v7.2.0 "vite": "7.1.12" "@types/node": "^22.10.1" + vitest-3: + "vitest": 3.2.4 + "@vitest/ui": 3.2.4 + "vite": "^5.4.14" + # CAUTION: Most usage of @cloudflare/vitest-pool-workers in this monorepo should use workspace:* instead of this catalog version + # However, some packages (pages-shared, workers-shared, etc...) need to be tested using vitest-pool-workers but are themselves + # ultimately included in vitest-pool-workers (through Wrangler), causing a circular dependency. + "@cloudflare/vitest-pool-workers": "^0.10.0" diff --git a/tools/dependabot/__tests__/generate-dependabot-pr-changesets.test.ts b/tools/dependabot/__tests__/generate-dependabot-pr-changesets.test.ts index c0741845a989..5618e7ae59ec 100644 --- a/tools/dependabot/__tests__/generate-dependabot-pr-changesets.test.ts +++ b/tools/dependabot/__tests__/generate-dependabot-pr-changesets.test.ts @@ -194,6 +194,7 @@ describe("writeChangeSet()", () => { describe("commitAndPush()", () => { it("should call spawnSync with appropriate git commands", () => { + (spawnSync as Mock).mockClear(); (spawnSync as Mock).mockReturnValue({ output: [] }); const commitMessage = dedent` chore: update dependencies of "@namespace/package" package diff --git a/vitest.shared.ts b/vitest.shared.ts index cb8c57b7a684..7ab474e6e7c9 100644 --- a/vitest.shared.ts +++ b/vitest.shared.ts @@ -13,9 +13,6 @@ export default defineConfig({ testTimeout: 50_000, hookTimeout: 50_000, teardownTimeout: 50_000, - poolOptions: { - useAtomics: true, - }, restoreMocks: true, // A lot of the fixture tests are extremely flaky because of the dev registry // Retry tests by default so that only real errors are reported