From 2519d2bbe9f7868c332dff8c1aa385213be92402 Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Tue, 7 Oct 2025 12:25:35 +0100 Subject: [PATCH 1/7] fix(vite-plugin-cloudflare): track server restart in module scope --- packages/vite-plugin-cloudflare/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vite-plugin-cloudflare/src/index.ts b/packages/vite-plugin-cloudflare/src/index.ts index 8f7b248495a8..d76c118ff002 100644 --- a/packages/vite-plugin-cloudflare/src/index.ts +++ b/packages/vite-plugin-cloudflare/src/index.ts @@ -64,6 +64,8 @@ const debuglog = util.debuglog("@cloudflare:vite-plugin"); // this flag is used to show the workers configs warning only once let workersConfigsWarningShown = false; +/** Used to track whether hooks are being called because of a server restart or a server close event. */ +let restartingServer = false; let miniflare: Miniflare | undefined; /** @@ -76,8 +78,6 @@ let miniflare: Miniflare | undefined; export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] { const ctx = new PluginContext(); let containerImageTagsSeen = new Set(); - /** Used to track whether hooks are being called because of a server restart or a server close event. */ - let restartingServer = false; return [ { From 909fdf1ceebd10cca58f43ae418acc08a7b021d1 Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Tue, 14 Oct 2025 18:56:19 +0100 Subject: [PATCH 2/7] update changeset --- .changeset/salty-plums-worry.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/salty-plums-worry.md diff --git a/.changeset/salty-plums-worry.md b/.changeset/salty-plums-worry.md new file mode 100644 index 000000000000..54044ecfcc69 --- /dev/null +++ b/.changeset/salty-plums-worry.md @@ -0,0 +1,7 @@ +--- +"@cloudflare/vite-plugin": patch +--- + +fix: track server restart in module scope + +When using `@cloudflare/vite-plugin` with React Router, miniflare might be disposed during restart. This change makes sure to track when the dev server restart in module scope to avoid unexpected behavior. From eed47a8e0acb429eadcbc47b00cf70730a84bc59 Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Tue, 14 Oct 2025 20:29:43 +0100 Subject: [PATCH 3/7] introduce verifyDev test for react router template --- .../e2e/helpers/framework-helpers.ts | 107 +++++++++++++++--- .../create-cloudflare/e2e/helpers/run-c3.ts | 11 ++ .../e2e/tests/frameworks/frameworks.test.ts | 8 ++ .../e2e/tests/frameworks/test-config.ts | 10 ++ 4 files changed, 122 insertions(+), 14 deletions(-) diff --git a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts index 740f8bb7a4c7..48dba8241c52 100644 --- a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts +++ b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts @@ -30,7 +30,6 @@ import { runC3 } from "./run-c3"; import { kill, spawnWithLogging } from "./spawn"; import type { TemplateConfig } from "../../src/templates"; import type { RunnerConfig } from "./run-c3"; -import type { JsonMap } from "@iarna/toml"; import type { Writable } from "stream"; export type FrameworkTestConfig = RunnerConfig & { @@ -87,33 +86,45 @@ export async function runC3ForFrameworkTest( return match[1]; } -/** - * Either update or create a wrangler configuration file to include a `TEST` var. - * - * This is rather than having a wrangler configuration file in the e2e test's fixture folder, - * which overwrites any that comes from the framework's template. - */ -export async function addTestVarsToWranglerToml(projectPath: string) { +export async function updateWranglerConfig( + projectPath: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handleUpdate: >(config: T) => T, +) { const wranglerTomlPath = join(projectPath, "wrangler.toml"); const wranglerJsoncPath = join(projectPath, "wrangler.jsonc"); if (existsSync(wranglerTomlPath)) { const wranglerToml = readToml(wranglerTomlPath); - wranglerToml.vars ??= {}; - (wranglerToml.vars as JsonMap).TEST = "C3_TEST"; - writeToml(wranglerTomlPath, wranglerToml); + writeToml(wranglerTomlPath, handleUpdate(wranglerToml)); } else if (existsSync(wranglerJsoncPath)) { const wranglerJsonc = readJSON(wranglerJsoncPath) as { vars: Record; }; - wranglerJsonc.vars ??= {}; - wranglerJsonc.vars.TEST = "C3_TEST"; - writeJSON(wranglerJsoncPath, wranglerJsonc); + writeJSON(wranglerJsoncPath, handleUpdate(wranglerJsonc)); } } +/** + * Either update or create a wrangler configuration file to include a `TEST` var. + * + * This is rather than having a wrangler configuration file in the e2e test's fixture folder, + * which overwrites any that comes from the framework's template. + */ +export async function addTestVarsToWranglerToml(projectPath: string) { + await updateWranglerConfig(projectPath, (config) => { + return { + ...config, + vars: { + ...config.vars, + TEST: "C3_TEST", + }, + }; + }); +} + export async function verifyDeployment( { testCommitMessage }: FrameworkTestConfig, frameworkId: string, @@ -141,6 +152,74 @@ export async function verifyDeployment( }); } +export async function verifyDevScript( + { verifyDev }: FrameworkTestConfig, + { devScript }: TemplateConfig, + projectPath: string, + logStream: Writable, +) { + if (!verifyDev) { + return; + } + + assert( + devScript, + "Expected a dev script as we are verifying the dev session in " + + projectPath, + ); + + // Run the dev-server on random ports to avoid colliding with other tests + const port = await getPort(); + const proc = spawnWithLogging( + [ + packageManager.name, + "run", + devScript, + ...(packageManager.name === "npm" ? ["--"] : []), + "--port", + `${port}`, + ], + { + cwd: projectPath, + env: { + VITEST: undefined, + }, + }, + logStream, + ); + + try { + await retry( + { times: 300, sleepMs: 5000 }, + async () => await fetch(`http://127.0.0.1:${port}${verifyDev.route}`), + ); + + // Make a request to the specified test route + const res = await fetch(`http://127.0.0.1:${port}${verifyDev.route}`); + expect(await res.text()).toContain(verifyDev.expectedText); + + if (verifyDev.configChanges) { + const updatedVars = verifyDev.configChanges.vars; + await updateWranglerConfig(projectPath, (config) => ({ + ...config, + vars: { + ...config.vars, + ...updatedVars, + }, + })); + + const res2 = await fetch(`http://127.0.0.1:${port}${verifyDev.route}`); + expect(await res2.text()).toContain(verifyDev.configChanges.expectedText); + } + } finally { + // Kill the process gracefully so ports can be cleaned up + await kill(proc); + // Wait for a second to allow process to exit cleanly. Otherwise, the port might + // end up camped and cause future runs to fail + await sleep(1000); + } +} + export async function verifyPreviewScript( { verifyPreview }: FrameworkTestConfig, { previewScript }: TemplateConfig, diff --git a/packages/create-cloudflare/e2e/helpers/run-c3.ts b/packages/create-cloudflare/e2e/helpers/run-c3.ts index 988f212051d7..3dee69fd370a 100644 --- a/packages/create-cloudflare/e2e/helpers/run-c3.ts +++ b/packages/create-cloudflare/e2e/helpers/run-c3.ts @@ -46,6 +46,17 @@ export type RunnerConfig = { * Specifies whether to run the test script for the project and verify the exit code. */ verifyTest?: boolean; + /** + * Specifies whether to run the dev script for the project and verify the response from the specified route. + */ + verifyDev?: { + route: string; + expectedText: string; + configChanges?: { + vars: Record; + expectedText: string; + }; + }; }; export const runC3 = async ( diff --git a/packages/create-cloudflare/e2e/tests/frameworks/frameworks.test.ts b/packages/create-cloudflare/e2e/tests/frameworks/frameworks.test.ts index 4ebcd80462fe..88f295b68bb5 100644 --- a/packages/create-cloudflare/e2e/tests/frameworks/frameworks.test.ts +++ b/packages/create-cloudflare/e2e/tests/frameworks/frameworks.test.ts @@ -17,6 +17,7 @@ import { shouldRunTest, testGitCommitMessage, verifyDeployment, + verifyDevScript, verifyPreviewScript, verifyTypes, } from "../../helpers/framework-helpers"; @@ -134,6 +135,13 @@ describe } } + await verifyDevScript( + testConfig, + frameworkConfig, + project.path, + logStream, + ); + await verifyPreviewScript( testConfig, frameworkConfig, diff --git a/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts b/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts index 046c8f9be67f..3847a0e37c7c 100644 --- a/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts +++ b/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts @@ -30,6 +30,16 @@ function getFrameworkTestConfig(pm: string): NamedFrameworkTestConfig[] { expectedText: "Hello from Cloudflare", previewArgs: ["--host=127.0.0.1"], }, + verifyDev: { + route: "/", + expectedText: "Hello from Cloudflare", + configChanges: { + vars: { + VALUE_FROM_CLOUDFLARE: "Hello React Router", + }, + expectedText: "Hello React Router", + }, + }, nodeCompat: false, flags: ["--no-install", "--no-git-init"], }, From 36b417ca7cd9ab84ba39572a8c27d7a1c875659e Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Wed, 15 Oct 2025 13:37:37 +0100 Subject: [PATCH 4/7] fixup! introduce verifyDev test for react router template --- .../create-cloudflare/e2e/helpers/framework-helpers.ts | 10 +++++++--- packages/create-cloudflare/e2e/helpers/run-c3.ts | 1 + .../e2e/tests/frameworks/test-config.ts | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts index 48dba8241c52..0b19e996dc8a 100644 --- a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts +++ b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts @@ -176,6 +176,7 @@ export async function verifyDevScript( "run", devScript, ...(packageManager.name === "npm" ? ["--"] : []), + ...(verifyDev.devArgs ?? []), "--port", `${port}`, ], @@ -199,7 +200,8 @@ export async function verifyDevScript( expect(await res.text()).toContain(verifyDev.expectedText); if (verifyDev.configChanges) { - const updatedVars = verifyDev.configChanges.vars; + const { configChanges } = verifyDev; + const updatedVars = configChanges.vars; await updateWranglerConfig(projectPath, (config) => ({ ...config, vars: { @@ -208,8 +210,10 @@ export async function verifyDevScript( }, })); - const res2 = await fetch(`http://127.0.0.1:${port}${verifyDev.route}`); - expect(await res2.text()).toContain(verifyDev.configChanges.expectedText); + await retry({ times: 10, sleepMs: 500 }, async () => { + const res2 = await fetch(`http://127.0.0.1:${port}${verifyDev.route}`); + expect(await res2.text()).toContain(configChanges.expectedText); + }); } } finally { // Kill the process gracefully so ports can be cleaned up diff --git a/packages/create-cloudflare/e2e/helpers/run-c3.ts b/packages/create-cloudflare/e2e/helpers/run-c3.ts index 3dee69fd370a..1992df0f91f3 100644 --- a/packages/create-cloudflare/e2e/helpers/run-c3.ts +++ b/packages/create-cloudflare/e2e/helpers/run-c3.ts @@ -50,6 +50,7 @@ export type RunnerConfig = { * Specifies whether to run the dev script for the project and verify the response from the specified route. */ verifyDev?: { + devArgs?: string[]; route: string; expectedText: string; configChanges?: { diff --git a/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts b/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts index 3847a0e37c7c..467b28a54a1e 100644 --- a/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts +++ b/packages/create-cloudflare/e2e/tests/frameworks/test-config.ts @@ -33,6 +33,7 @@ function getFrameworkTestConfig(pm: string): NamedFrameworkTestConfig[] { verifyDev: { route: "/", expectedText: "Hello from Cloudflare", + devArgs: ["--host=127.0.0.1"], configChanges: { vars: { VALUE_FROM_CLOUDFLARE: "Hello React Router", From e410738fe9feabee29c1ef91314f8ee758b07894 Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Wed, 15 Oct 2025 16:48:03 +0100 Subject: [PATCH 5/7] improve mock registry message --- packages/mock-npm-registry/src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/mock-npm-registry/src/index.ts b/packages/mock-npm-registry/src/index.ts index 49906ee35eac..5b00922cf42b 100644 --- a/packages/mock-npm-registry/src/index.ts +++ b/packages/mock-npm-registry/src/index.ts @@ -41,9 +41,13 @@ export async function startMockNpmRegistry(...targetPackages: string[]) { ); console.log( - `Starting up local npm registry on http://localhost:${registryPort} at ${registryPath}` + `Starting up local npm registry on http://localhost:${registryPort} at ${registryPath} with ${pkgs.size} packages published:` ); + for (const [pkg, pkgPath] of pkgs.entries()) { + console.log(` - ${pkg} (${pkgPath})`); + } + let stopServer = await startVerdaccioServer(configPath); const tempConfig = path.join(registryPath, ".npmrc"); From d42cf5fbf01c89de2b6dfcbb97b26bd1d7db67f2 Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Wed, 15 Oct 2025 17:34:18 +0100 Subject: [PATCH 6/7] try restoring config after verifyDev --- .../e2e/helpers/framework-helpers.ts | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts index 0b19e996dc8a..ccf5bef372f1 100644 --- a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts +++ b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts @@ -86,7 +86,7 @@ export async function runC3ForFrameworkTest( return match[1]; } -export async function updateWranglerConfig( +export function updateWranglerConfig( projectPath: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any handleUpdate: >(config: T) => T, @@ -97,14 +97,32 @@ export async function updateWranglerConfig( if (existsSync(wranglerTomlPath)) { const wranglerToml = readToml(wranglerTomlPath); - writeToml(wranglerTomlPath, handleUpdate(wranglerToml)); + writeToml( + wranglerTomlPath, + handleUpdate(JSON.parse(JSON.stringify(wranglerToml))), + ); + + return () => { + writeToml(wranglerTomlPath, handleUpdate(wranglerToml)); + }; } else if (existsSync(wranglerJsoncPath)) { const wranglerJsonc = readJSON(wranglerJsoncPath) as { vars: Record; }; - writeJSON(wranglerJsoncPath, handleUpdate(wranglerJsonc)); + writeJSON( + wranglerJsoncPath, + handleUpdate(JSON.parse(JSON.stringify(wranglerJsonc))), + ); + + return () => { + writeJSON(wranglerJsoncPath, wranglerJsonc); + }; } + + throw new Error( + `Could not find a wrangler.toml or wrangler.jsonc file in ${projectPath}`, + ); } /** @@ -114,7 +132,7 @@ export async function updateWranglerConfig( * which overwrites any that comes from the framework's template. */ export async function addTestVarsToWranglerToml(projectPath: string) { - await updateWranglerConfig(projectPath, (config) => { + updateWranglerConfig(projectPath, (config) => { return { ...config, vars: { @@ -189,6 +207,8 @@ export async function verifyDevScript( logStream, ); + let restoreConfig: (() => void) | undefined; + try { await retry( { times: 300, sleepMs: 5000 }, @@ -202,7 +222,8 @@ export async function verifyDevScript( if (verifyDev.configChanges) { const { configChanges } = verifyDev; const updatedVars = configChanges.vars; - await updateWranglerConfig(projectPath, (config) => ({ + + restoreConfig = updateWranglerConfig(projectPath, (config) => ({ ...config, vars: { ...config.vars, @@ -218,6 +239,8 @@ export async function verifyDevScript( } finally { // Kill the process gracefully so ports can be cleaned up await kill(proc); + // Restore the wrangler config if we modified it + restoreConfig?.(); // Wait for a second to allow process to exit cleanly. Otherwise, the port might // end up camped and cause future runs to fail await sleep(1000); From 9af7c628fe5f21bb51dcf6b6c6bae53e7cdb5de6 Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Wed, 15 Oct 2025 19:11:15 +0100 Subject: [PATCH 7/7] fixup! try restoring config after verifyDev --- packages/create-cloudflare/e2e/helpers/framework-helpers.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts index ccf5bef372f1..db0dad554b7d 100644 --- a/packages/create-cloudflare/e2e/helpers/framework-helpers.ts +++ b/packages/create-cloudflare/e2e/helpers/framework-helpers.ts @@ -119,10 +119,6 @@ export function updateWranglerConfig( writeJSON(wranglerJsoncPath, wranglerJsonc); }; } - - throw new Error( - `Could not find a wrangler.toml or wrangler.jsonc file in ${projectPath}`, - ); } /**