From 3aba1a4e7a348801070372c1f87cfffbb9093900 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 2 Oct 2025 11:15:55 +0100 Subject: [PATCH 1/8] Allow vite-plugin e2e test to turn off strict-peers installation --- .../vite-plugin-cloudflare/e2e/basic.test.ts | 2 +- .../e2e/dynamic.test.ts | 2 +- .../vite-plugin-cloudflare/e2e/helpers.ts | 21 ++++++++++++++----- ...invalid-worker-environment-options.test.ts | 4 +++- .../e2e/remote-bindings.test.ts | 2 +- .../e2e/wrangler-configs-validation.test.ts | 6 ++++-- 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/vite-plugin-cloudflare/e2e/basic.test.ts b/packages/vite-plugin-cloudflare/e2e/basic.test.ts index ed048115cf44..2fbc76450707 100644 --- a/packages/vite-plugin-cloudflare/e2e/basic.test.ts +++ b/packages/vite-plugin-cloudflare/e2e/basic.test.ts @@ -13,7 +13,7 @@ const commands = ["dev", "buildAndPreview"] as const; describe("basic e2e tests", () => { describe.each(packageManagers)('with "%s" package manager', async (pm) => { - const projectPath = seed("basic", pm); + const projectPath = seed("basic", { pm }); describe.each(commands)('with "%s" command', (command) => { test.skipIf(isBuildAndPreviewOnWindows(command))( diff --git a/packages/vite-plugin-cloudflare/e2e/dynamic.test.ts b/packages/vite-plugin-cloudflare/e2e/dynamic.test.ts index 07b9cb5c603b..d13b1990ceab 100644 --- a/packages/vite-plugin-cloudflare/e2e/dynamic.test.ts +++ b/packages/vite-plugin-cloudflare/e2e/dynamic.test.ts @@ -5,7 +5,7 @@ const packageManagers = ["pnpm", "npm", "yarn"] as const; describe("prebundling Node.js compatibility", () => { describe.each(packageManagers)('with "%s" package manager', (pm) => { - const projectPath = seed("dynamic", pm); + const projectPath = seed("dynamic", { pm }); test("will not cause a reload on a dynamic import of a Node.js module", async ({ expect, diff --git a/packages/vite-plugin-cloudflare/e2e/helpers.ts b/packages/vite-plugin-cloudflare/e2e/helpers.ts index 3e21e49577b8..275da55cc929 100644 --- a/packages/vite-plugin-cloudflare/e2e/helpers.ts +++ b/packages/vite-plugin-cloudflare/e2e/helpers.ts @@ -40,8 +40,15 @@ const strictPeerDeps = { /** Seed a test project from a fixture. */ export function seed( fixture: string, - pm: "pnpm" | "yarn" | "npm", - replacements: Record = {} + { + pm, + replacements = {}, + useStrictPeerDeps = true, + }: { + pm: "pnpm" | "yarn" | "npm"; + replacements?: Record; + useStrictPeerDeps?: boolean; + } ) { const root = inject("root"); const projectPath = path.resolve(root, fixture, pm); @@ -56,9 +63,13 @@ export function seed( debuglog("Fixing up replacements in seeded files"); await fixupReplacements(projectPath, replacements); debuglog("Updated vite-plugin version in package.json"); - runCommand(`${pm} install ${strictPeerDeps[pm]}`, projectPath, { - attempts: 2, - }); + runCommand( + `${pm} install ${useStrictPeerDeps ? strictPeerDeps[pm] : ""}`, + projectPath, + { + attempts: 2, + } + ); debuglog("Installed node modules"); }, 200_000); diff --git a/packages/vite-plugin-cloudflare/e2e/invalid-worker-environment-options.test.ts b/packages/vite-plugin-cloudflare/e2e/invalid-worker-environment-options.test.ts index f8949d27f112..8e299cea272b 100644 --- a/packages/vite-plugin-cloudflare/e2e/invalid-worker-environment-options.test.ts +++ b/packages/vite-plugin-cloudflare/e2e/invalid-worker-environment-options.test.ts @@ -4,7 +4,9 @@ import { runLongLived, seed } from "./helpers"; // The test here just makes sure that the validation takes place. // Unit tests for the validation are in `src/__tests__/validate-worker-environment-options.spec.ts` describe("validate Worker environment options", () => { - const projectPath = seed("invalid-worker-environment-options", "pnpm"); + const projectPath = seed("invalid-worker-environment-options", { + pm: "pnpm", + }); test("throws an error for invalid environment options", async ({ expect, diff --git a/packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts b/packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts index 95d9d4e4a491..07f7f09c5db9 100644 --- a/packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts +++ b/packages/vite-plugin-cloudflare/e2e/remote-bindings.test.ts @@ -25,7 +25,7 @@ if (!process.env.CLOUDFLARE_ACCOUNT_ID || !process.env.CLOUDFLARE_API_TOKEN) { "<>": `preserve-e2e-vite-remote-alt`, }; - const projectPath = seed("remote-bindings", "pnpm", replacements); + const projectPath = seed("remote-bindings", { pm: "pnpm", replacements }); beforeAll(async () => { try { diff --git a/packages/vite-plugin-cloudflare/e2e/wrangler-configs-validation.test.ts b/packages/vite-plugin-cloudflare/e2e/wrangler-configs-validation.test.ts index 30aeea6c715e..cd45a554edd8 100644 --- a/packages/vite-plugin-cloudflare/e2e/wrangler-configs-validation.test.ts +++ b/packages/vite-plugin-cloudflare/e2e/wrangler-configs-validation.test.ts @@ -5,7 +5,9 @@ import { runLongLived, seed } from "./helpers"; // testing regarding the validation there are unit tests in src/__tests__/get-validated-wrangler-config-path.spec.ts describe("during development wrangler config files are validated", () => { - const noWranglerConfigProjectPath = seed("no-wrangler-config", "pnpm"); + const noWranglerConfigProjectPath = seed("no-wrangler-config", { + pm: "pnpm", + }); test("for the entry worker", async ({ expect }) => { const proc = await runLongLived("pnpm", "dev", noWranglerConfigProjectPath); expect(await proc.exitCode).not.toBe(0); @@ -16,7 +18,7 @@ describe("during development wrangler config files are validated", () => { const noWranglerConfigAuxProjectPath = seed( "no-wrangler-config-for-auxiliary-worker", - "pnpm" + { pm: "pnpm" } ); test("for auxiliary workers", async ({ expect }) => { const proc = await runLongLived( From 49e6a20b494655e30e4d5fce845b4dcbf58cbea7 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 2 Oct 2025 11:17:58 +0100 Subject: [PATCH 2/8] refactor: vite-plugin to use wrangler default export This change delays confusing "missing export" failures to allow us to run a more user friendly version skew check, for when the user installs a version of Wrangler that breaks the plugin --- .../src/deploy-config.ts | 4 +- .../vite-plugin-cloudflare/src/dev-vars.ts | 4 +- .../src/miniflare-options.ts | 56 +++++++++---------- .../src/workers-configs.ts | 4 +- 4 files changed, 32 insertions(+), 36 deletions(-) diff --git a/packages/vite-plugin-cloudflare/src/deploy-config.ts b/packages/vite-plugin-cloudflare/src/deploy-config.ts index c37affb171b0..c261bb082840 100644 --- a/packages/vite-plugin-cloudflare/src/deploy-config.ts +++ b/packages/vite-plugin-cloudflare/src/deploy-config.ts @@ -2,7 +2,7 @@ import assert from "node:assert"; import * as fs from "node:fs"; import * as path from "node:path"; import * as vite from "vite"; -import { unstable_readConfig } from "wrangler"; +import wrangler from "wrangler"; import type { AssetsOnlyResolvedConfig, WorkersResolvedConfig, @@ -31,7 +31,7 @@ export function getWorkerConfigs(root: string) { path.dirname(deployConfigPath), configPath ); - return unstable_readConfig({ config: resolvedConfigPath }); + return wrangler.unstable_readConfig({ config: resolvedConfigPath }); }); } diff --git a/packages/vite-plugin-cloudflare/src/dev-vars.ts b/packages/vite-plugin-cloudflare/src/dev-vars.ts index 0dd4e4cfaca2..4473fca1b5f5 100644 --- a/packages/vite-plugin-cloudflare/src/dev-vars.ts +++ b/packages/vite-plugin-cloudflare/src/dev-vars.ts @@ -1,5 +1,5 @@ import * as path from "node:path"; -import { unstable_getVarsForDev } from "wrangler"; +import wrangler from "wrangler"; import type { AssetsOnlyResolvedConfig, WorkersResolvedConfig, @@ -14,7 +14,7 @@ export function getLocalDevVarsForPreview( configPath: string | undefined, cloudflareEnv: string | undefined ): string | undefined { - const dotDevDotVars = unstable_getVarsForDev( + const dotDevDotVars = wrangler.unstable_getVarsForDev( configPath, undefined, // We don't currently support setting a list of custom `.env` files. {}, // Don't pass actual vars since these will be loaded from the wrangler.json. diff --git a/packages/vite-plugin-cloudflare/src/miniflare-options.ts b/packages/vite-plugin-cloudflare/src/miniflare-options.ts index 45a1ef1b95e9..3920f5c95a3e 100644 --- a/packages/vite-plugin-cloudflare/src/miniflare-options.ts +++ b/packages/vite-plugin-cloudflare/src/miniflare-options.ts @@ -18,11 +18,7 @@ import { import colors from "picocolors"; import { globSync } from "tinyglobby"; import * as vite from "vite"; -import { - maybeStartOrUpdateRemoteProxySession, - unstable_convertConfigBindingsToStartWorkerBindings, - unstable_getMiniflareWorkerOptions, -} from "wrangler"; +import wrangler from "wrangler"; import { getAssetsConfig } from "./asset-config"; import { ASSET_WORKER_NAME, @@ -401,7 +397,7 @@ export async function getDevMiniflareOptions(config: { Object.entries(resolvedPluginConfig.workers).map( async ([environmentName, workerConfig]) => { const bindings = - unstable_convertConfigBindingsToStartWorkerBindings( + wrangler.unstable_convertConfigBindingsToStartWorkerBindings( workerConfig ); @@ -411,7 +407,7 @@ export async function getDevMiniflareOptions(config: { const remoteProxySessionData = resolvedPluginConfig.experimental.remoteBindings ?? true - ? await maybeStartOrUpdateRemoteProxySession( + ? await wrangler.maybeStartOrUpdateRemoteProxySession( { name: workerConfig.name, bindings: bindings ?? {}, @@ -447,21 +443,22 @@ export async function getDevMiniflareOptions(config: { } } - const miniflareWorkerOptions = unstable_getMiniflareWorkerOptions( - { - ...workerConfig, - assets: undefined, - }, - resolvedPluginConfig.cloudflareEnv, - { - remoteProxyConnectionString: - remoteProxySessionData?.session - ?.remoteProxyConnectionString, - remoteBindingsEnabled: - resolvedPluginConfig.experimental.remoteBindings ?? true, - containerBuildId, - } - ); + const miniflareWorkerOptions = + wrangler.unstable_getMiniflareWorkerOptions( + { + ...workerConfig, + assets: undefined, + }, + resolvedPluginConfig.cloudflareEnv, + { + remoteProxyConnectionString: + remoteProxySessionData?.session + ?.remoteProxyConnectionString, + remoteBindingsEnabled: + resolvedPluginConfig.experimental.remoteBindings ?? true, + containerBuildId, + } + ); const { externalWorkers } = miniflareWorkerOptions; @@ -763,7 +760,9 @@ export async function getPreviewMiniflareOptions(config: { await Promise.all( resolvedPluginConfig.workers.map(async (workerConfig, i) => { const bindings = - unstable_convertConfigBindingsToStartWorkerBindings(workerConfig); + wrangler.unstable_convertConfigBindingsToStartWorkerBindings( + workerConfig + ); const preExistingRemoteProxySessionData = workerConfig.configPath ? remoteProxySessionsDataMap.get(workerConfig.configPath) @@ -771,7 +770,7 @@ export async function getPreviewMiniflareOptions(config: { const remoteProxySessionData = resolvedPluginConfig.experimental.remoteBindings ?? true - ? await maybeStartOrUpdateRemoteProxySession( + ? await wrangler.maybeStartOrUpdateRemoteProxySession( { name: workerConfig.name, bindings: bindings ?? {}, @@ -806,17 +805,14 @@ export async function getPreviewMiniflareOptions(config: { } } - const miniflareWorkerOptions = unstable_getMiniflareWorkerOptions( - workerConfig, - undefined, - { + const miniflareWorkerOptions = + wrangler.unstable_getMiniflareWorkerOptions(workerConfig, undefined, { remoteProxyConnectionString: remoteProxySessionData?.session?.remoteProxyConnectionString, remoteBindingsEnabled: resolvedPluginConfig.experimental.remoteBindings ?? true, containerBuildId, - } - ); + }); const { externalWorkers } = miniflareWorkerOptions; diff --git a/packages/vite-plugin-cloudflare/src/workers-configs.ts b/packages/vite-plugin-cloudflare/src/workers-configs.ts index 3c8f7d717709..dcad78dad2aa 100644 --- a/packages/vite-plugin-cloudflare/src/workers-configs.ts +++ b/packages/vite-plugin-cloudflare/src/workers-configs.ts @@ -1,7 +1,7 @@ import assert from "node:assert"; import * as fs from "node:fs"; import * as path from "node:path"; -import { unstable_readConfig } from "wrangler"; +import wrangler from "wrangler"; import type { AssetsOnlyConfig, WorkerConfig } from "./plugin-config"; import type { Optional } from "./utils"; import type { Unstable_Config as RawWorkerConfig } from "wrangler"; @@ -123,7 +123,7 @@ function readWorkerConfig( notRelevant: new Set(), }; const config: Optional = - unstable_readConfig( + wrangler.unstable_readConfig( { config: configPath, env }, // Preserve the original `main` value so that Vite can resolve it { preserveOriginalMain: true } From 05eeeb807bec3b3719bfaf66f63c4bbf04627b0b Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Thu, 2 Oct 2025 11:31:43 +0100 Subject: [PATCH 3/8] Add a check to vite-plugin that ensures that the version of Wrangler being used internally is correct This is necessary because some package managers will incorrectly merge peer dependencies with actual dependencies (often ignoring peer dependency constraints). --- .changeset/tidy-doors-repeat.md | 5 ++++ .../invalid-wrangler-version/package.json | 26 +++++++++++++++++++ .../invalid-wrangler-version/src/index.ts | 5 ++++ .../invalid-wrangler-version/tsconfig.json | 7 +++++ .../tsconfig.node.json | 24 +++++++++++++++++ .../tsconfig.worker.json | 8 ++++++ .../invalid-wrangler-version/vite.config.ts | 6 +++++ .../invalid-wrangler-version/wrangler.jsonc | 6 +++++ .../e2e/invalid-wrangler-version.test.ts | 22 ++++++++++++++++ packages/vite-plugin-cloudflare/package.json | 3 ++- .../src/assert-wrangler-version.ts | 20 ++++++++++++++ packages/vite-plugin-cloudflare/src/index.ts | 3 +++ 12 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 .changeset/tidy-doors-repeat.md create mode 100644 packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/package.json create mode 100644 packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/src/index.ts create mode 100644 packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.json create mode 100644 packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.node.json create mode 100644 packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.worker.json create mode 100644 packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/vite.config.ts create mode 100644 packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/wrangler.jsonc create mode 100644 packages/vite-plugin-cloudflare/e2e/invalid-wrangler-version.test.ts create mode 100644 packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts diff --git a/.changeset/tidy-doors-repeat.md b/.changeset/tidy-doors-repeat.md new file mode 100644 index 000000000000..ac711d43418a --- /dev/null +++ b/.changeset/tidy-doors-repeat.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/vite-plugin": patch +--- + +Add a check to vite-plugin that ensures that the version of Wrangler being used internally is correct diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/package.json b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/package.json new file mode 100644 index 000000000000..522302c00438 --- /dev/null +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/package.json @@ -0,0 +1,26 @@ +{ + "name": "@cloudflare/vite-plugin-e2e-invalid-wrangler-version", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "buildAndPreview": "vite build && vite preview", + "dev": "vite", + "lint": "eslint .", + "preview": "vite preview" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "*", + "@cloudflare/workers-types": "^4.20250204.0", + "@eslint/js": "^9.19.0", + "eslint": "^9.19.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.22.0", + "vite": "^6.1.0", + "wrangler": "4.20.0" + } +} diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/src/index.ts b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/src/index.ts new file mode 100644 index 000000000000..1cee2bb8eb12 --- /dev/null +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/src/index.ts @@ -0,0 +1,5 @@ +export default { + async fetch() { + return new Response("OK"); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.json b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.json new file mode 100644 index 000000000000..b52af703bdc2 --- /dev/null +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.node.json b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.node.json new file mode 100644 index 000000000000..50130b3a269b --- /dev/null +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.worker.json b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.worker.json new file mode 100644 index 000000000000..ff69f9652d65 --- /dev/null +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/tsconfig.worker.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.node.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.worker.tsbuildinfo", + "types": ["@cloudflare/workers-types/2023-07-01", "vite/client"] + }, + "include": ["src"] +} diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/vite.config.ts b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/vite.config.ts new file mode 100644 index 000000000000..9c6b158cb564 --- /dev/null +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/vite.config.ts @@ -0,0 +1,6 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [cloudflare({ inspectorPort: false, persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/wrangler.jsonc b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/wrangler.jsonc new file mode 100644 index 000000000000..a7d9be8cf3f1 --- /dev/null +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/invalid-wrangler-version/wrangler.jsonc @@ -0,0 +1,6 @@ +{ + "name": "cloudflare-vite-e2e-invalid-wrangler-version", + "main": "./src/index.ts", + "compatibility_date": "2024-12-30", + "compatibility_flags": ["nodejs_compat"], +} diff --git a/packages/vite-plugin-cloudflare/e2e/invalid-wrangler-version.test.ts b/packages/vite-plugin-cloudflare/e2e/invalid-wrangler-version.test.ts new file mode 100644 index 000000000000..0da6b4ec1d4a --- /dev/null +++ b/packages/vite-plugin-cloudflare/e2e/invalid-wrangler-version.test.ts @@ -0,0 +1,22 @@ +import { describe, test } from "vitest"; +import { runLongLived, seed } from "./helpers.js"; + +describe("invalid Wrangler version e2e tests", () => { + // npm and yarn don't hoist peer dependencies in the same way as pnpm + // so having a different peer Wrangler doesn't mess up the internal dependency + describe("with `pnpm`", () => { + const projectPath = seed("invalid-wrangler-version", { + pm: "pnpm", + useStrictPeerDeps: false, + }); + test("`vite dev` will error when peer installed wrangler version overrides the expected internal dependency", async ({ + expect, + }) => { + const proc = await runLongLived("pnpm", "dev", projectPath); + expect(await proc.exitCode).not.toBe(0); + expect(proc.stderr).toMatch( + /The installed version of Wrangler \(4\.20\.0\) doesn't match the version that @cloudflare\/vite-plugin requires \(\d+\.\d+\.\d+\)./ + ); + }); + }); +}); diff --git a/packages/vite-plugin-cloudflare/package.json b/packages/vite-plugin-cloudflare/package.json index c50b0c928ee4..12806842bafe 100644 --- a/packages/vite-plugin-cloudflare/package.json +++ b/packages/vite-plugin-cloudflare/package.json @@ -25,7 +25,8 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" - } + }, + "./package.json": "./package.json" }, "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts new file mode 100644 index 000000000000..dc816bc277ee --- /dev/null +++ b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts @@ -0,0 +1,20 @@ +import { createRequire } from "node:module"; + +export function assertWranglerVersion() { + const require = createRequire(import.meta.url); + const installedVersion = require("wrangler/package.json").version as string; + const dependencyVersion = require("@cloudflare/vite-plugin/package.json") + .dependencies["wrangler"] as string; + + if ( + // If running in the monorepo, the version is a workspace reference, so skip the check. + dependencyVersion !== "workspace:*" && + dependencyVersion !== installedVersion + ) { + throw new Error( + `The installed version of Wrangler (${installedVersion}) doesn't match the version that @cloudflare/vite-plugin requires (${dependencyVersion}).\n` + + `This can happen if your package manager has merged version of Wrangler in your project with the one that @cloudflare/vite-plugin depends upon.\n` + + `To fix this, ensure that the version of Wrangler installed in your project matches the version that @cloudflare/vite-plugin requires.` + ); + } +} diff --git a/packages/vite-plugin-cloudflare/src/index.ts b/packages/vite-plugin-cloudflare/src/index.ts index d76c118ff002..2f6bf6d8a8fd 100644 --- a/packages/vite-plugin-cloudflare/src/index.ts +++ b/packages/vite-plugin-cloudflare/src/index.ts @@ -6,6 +6,7 @@ import { generateStaticRoutingRuleMatcher } from "@cloudflare/workers-shared/ass import { CoreHeaders, Miniflare } from "miniflare"; import colors from "picocolors"; import * as vite from "vite"; +import { assertWranglerVersion } from "./assert-wrangler-version"; import { hasAssetsConfigChanged } from "./asset-config"; import { createBuildApp } from "./build"; import { @@ -68,6 +69,8 @@ let workersConfigsWarningShown = false; let restartingServer = false; let miniflare: Miniflare | undefined; +assertWranglerVersion(); + /** * Vite plugin that enables a full-featured integration between Vite and the Cloudflare Workers runtime. * From b5f6d2ffa7af9b35afdfc3742ce3d5eddb256c13 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 3 Oct 2025 11:59:27 +0100 Subject: [PATCH 4/8] fixup! Add a check to vite-plugin that ensures that the version of Wrangler being used internally is correct --- .../vite-plugin-cloudflare/src/assert-wrangler-version.ts | 6 ++++++ packages/vite-plugin-cloudflare/src/dev-vars.ts | 2 +- packages/vite-plugin-cloudflare/src/index.ts | 2 +- packages/vite-plugin-cloudflare/src/miniflare-options.ts | 2 +- packages/vite-plugin-cloudflare/src/workers-configs.ts | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts index dc816bc277ee..c2821bd33c7a 100644 --- a/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts +++ b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts @@ -1,5 +1,11 @@ import { createRequire } from "node:module"; +/** + * Asserts that the installed version of Wrangler that gets pulled in at runtime by the `@cloudflare/vite-plugin` + * matches the version that `@cloudflare/vite-plugin` actually depends upon. + * + * This can sometime be broken by package managers that deduplicate dependencies, such as `pnpm`. + */ export function assertWranglerVersion() { const require = createRequire(import.meta.url); const installedVersion = require("wrangler/package.json").version as string; diff --git a/packages/vite-plugin-cloudflare/src/dev-vars.ts b/packages/vite-plugin-cloudflare/src/dev-vars.ts index 4473fca1b5f5..37460037e1fa 100644 --- a/packages/vite-plugin-cloudflare/src/dev-vars.ts +++ b/packages/vite-plugin-cloudflare/src/dev-vars.ts @@ -1,5 +1,5 @@ import * as path from "node:path"; -import wrangler from "wrangler"; +import * as wrangler from "wrangler"; import type { AssetsOnlyResolvedConfig, WorkersResolvedConfig, diff --git a/packages/vite-plugin-cloudflare/src/index.ts b/packages/vite-plugin-cloudflare/src/index.ts index 2f6bf6d8a8fd..eea64e4ce3f1 100644 --- a/packages/vite-plugin-cloudflare/src/index.ts +++ b/packages/vite-plugin-cloudflare/src/index.ts @@ -69,7 +69,7 @@ let workersConfigsWarningShown = false; let restartingServer = false; let miniflare: Miniflare | undefined; -assertWranglerVersion(); +await assertWranglerVersion(); /** * Vite plugin that enables a full-featured integration between Vite and the Cloudflare Workers runtime. diff --git a/packages/vite-plugin-cloudflare/src/miniflare-options.ts b/packages/vite-plugin-cloudflare/src/miniflare-options.ts index 3920f5c95a3e..18d0d2b14db9 100644 --- a/packages/vite-plugin-cloudflare/src/miniflare-options.ts +++ b/packages/vite-plugin-cloudflare/src/miniflare-options.ts @@ -18,7 +18,7 @@ import { import colors from "picocolors"; import { globSync } from "tinyglobby"; import * as vite from "vite"; -import wrangler from "wrangler"; +import * as wrangler from "wrangler"; import { getAssetsConfig } from "./asset-config"; import { ASSET_WORKER_NAME, diff --git a/packages/vite-plugin-cloudflare/src/workers-configs.ts b/packages/vite-plugin-cloudflare/src/workers-configs.ts index dcad78dad2aa..b09559faf8d9 100644 --- a/packages/vite-plugin-cloudflare/src/workers-configs.ts +++ b/packages/vite-plugin-cloudflare/src/workers-configs.ts @@ -1,7 +1,7 @@ import assert from "node:assert"; import * as fs from "node:fs"; import * as path from "node:path"; -import wrangler from "wrangler"; +import * as wrangler from "wrangler"; import type { AssetsOnlyConfig, WorkerConfig } from "./plugin-config"; import type { Optional } from "./utils"; import type { Unstable_Config as RawWorkerConfig } from "wrangler"; From 4acb2f05c5d6e0ff864fa678972a3ba33c23a9c4 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 3 Oct 2025 11:59:27 +0100 Subject: [PATCH 5/8] fixup! Add a check to vite-plugin that ensures that the version of Wrangler being used internally is correct --- packages/vite-plugin-cloudflare/src/deploy-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite-plugin-cloudflare/src/deploy-config.ts b/packages/vite-plugin-cloudflare/src/deploy-config.ts index c261bb082840..288162c75f84 100644 --- a/packages/vite-plugin-cloudflare/src/deploy-config.ts +++ b/packages/vite-plugin-cloudflare/src/deploy-config.ts @@ -2,7 +2,7 @@ import assert from "node:assert"; import * as fs from "node:fs"; import * as path from "node:path"; import * as vite from "vite"; -import wrangler from "wrangler"; +import * as wrangler from "wrangler"; import type { AssetsOnlyResolvedConfig, WorkersResolvedConfig, From d18a533cfc44ea572e8e8a80136efa986bf9bdb5 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 3 Oct 2025 11:59:27 +0100 Subject: [PATCH 6/8] fixup! Add a check to vite-plugin that ensures that the version of Wrangler being used internally is correct --- .../e2e/invalid-wrangler-version.test.ts | 30 +++++++-------- packages/vite-plugin-cloudflare/package.json | 2 + .../src/assert-wrangler-version.ts | 37 +++++++++++++------ pnpm-lock.yaml | 18 ++++++--- 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/packages/vite-plugin-cloudflare/e2e/invalid-wrangler-version.test.ts b/packages/vite-plugin-cloudflare/e2e/invalid-wrangler-version.test.ts index 0da6b4ec1d4a..949571066695 100644 --- a/packages/vite-plugin-cloudflare/e2e/invalid-wrangler-version.test.ts +++ b/packages/vite-plugin-cloudflare/e2e/invalid-wrangler-version.test.ts @@ -2,21 +2,21 @@ import { describe, test } from "vitest"; import { runLongLived, seed } from "./helpers.js"; describe("invalid Wrangler version e2e tests", () => { - // npm and yarn don't hoist peer dependencies in the same way as pnpm + // Only test with pnpm; + // npm and yarn don't hoist peer dependencies in the same way as pnpm; // so having a different peer Wrangler doesn't mess up the internal dependency - describe("with `pnpm`", () => { - const projectPath = seed("invalid-wrangler-version", { - pm: "pnpm", - useStrictPeerDeps: false, - }); - test("`vite dev` will error when peer installed wrangler version overrides the expected internal dependency", async ({ - expect, - }) => { - const proc = await runLongLived("pnpm", "dev", projectPath); - expect(await proc.exitCode).not.toBe(0); - expect(proc.stderr).toMatch( - /The installed version of Wrangler \(4\.20\.0\) doesn't match the version that @cloudflare\/vite-plugin requires \(\d+\.\d+\.\d+\)./ - ); - }); + const projectPath = seed("invalid-wrangler-version", { + pm: "pnpm", + useStrictPeerDeps: false, + }); + + test("`vite dev` will error when peer installed wrangler version overrides the expected internal dependency", async ({ + expect, + }) => { + const proc = await runLongLived("pnpm", "dev", projectPath); + expect(await proc.exitCode).not.toBe(0); + expect(proc.stderr).toMatch( + /The installed version of Wrangler \(4\.20\.0\) is older than the version required by @cloudflare\/vite-plugin \(\d+\.\d+\.\d+\)/ + ); }); }); diff --git a/packages/vite-plugin-cloudflare/package.json b/packages/vite-plugin-cloudflare/package.json index 12806842bafe..429f28cfd6af 100644 --- a/packages/vite-plugin-cloudflare/package.json +++ b/packages/vite-plugin-cloudflare/package.json @@ -60,9 +60,11 @@ "@cloudflare/workers-tsconfig": "workspace:*", "@cloudflare/workers-types": "catalog:default", "@types/node": "catalog:vite-plugin", + "@types/semver": "^7.5.1", "@types/ws": "^8.5.13", "magic-string": "^0.30.12", "mlly": "^1.7.4", + "semver": "^7.7.1", "tsdown": "^0.15.4", "typescript": "catalog:default", "vite": "catalog:vite-plugin", diff --git a/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts index c2821bd33c7a..3d7334e918a4 100644 --- a/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts +++ b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts @@ -1,4 +1,5 @@ import { createRequire } from "node:module"; +import { compare, Range, satisfies, SemVer } from "semver"; /** * Asserts that the installed version of Wrangler that gets pulled in at runtime by the `@cloudflare/vite-plugin` @@ -8,19 +9,33 @@ import { createRequire } from "node:module"; */ export function assertWranglerVersion() { const require = createRequire(import.meta.url); - const installedVersion = require("wrangler/package.json").version as string; - const dependencyVersion = require("@cloudflare/vite-plugin/package.json") - .dependencies["wrangler"] as string; + const installedVersion = new SemVer(require("wrangler/package.json").version); + const vitePackage = require("@cloudflare/vite-plugin/package.json"); - if ( - // If running in the monorepo, the version is a workspace reference, so skip the check. - dependencyVersion !== "workspace:*" && - dependencyVersion !== installedVersion - ) { + if (vitePackage.dependencies.wrangler.startsWith("workspace:")) { + // We are running in the monorepo, so these deps are not yet computed to specific semver strings. + // We don't need to worry in this case and can skip the check. + return; + } + + const wranglerDependency = new SemVer(vitePackage.dependencies.wrangler); + const wranglerPeerDependency = new Range( + vitePackage.peerDependencies.wrangler + ); + + if (compare(installedVersion, wranglerDependency) < 0) { throw new Error( - `The installed version of Wrangler (${installedVersion}) doesn't match the version that @cloudflare/vite-plugin requires (${dependencyVersion}).\n` + - `This can happen if your package manager has merged version of Wrangler in your project with the one that @cloudflare/vite-plugin depends upon.\n` + - `To fix this, ensure that the version of Wrangler installed in your project matches the version that @cloudflare/vite-plugin requires.` + `The installed version of Wrangler (${installedVersion.format()}) is older than the version required by @cloudflare/vite-plugin (${wranglerDependency.format()}).\n` + + `Since the current package manager is flattening the versions of Wrangler, the @cloudflare/vite-plugin is using this version to read and write config files.\n` + + `Please upgrade your installation of Wrangler to at least ${wranglerDependency}.` + ); + } + + if (!satisfies(installedVersion, wranglerPeerDependency)) { + console.warn( + `The installed version of Wrangler (${installedVersion.format()}) does not satisfy the peer dependency required by @cloudflare/vite-plugin (${wranglerDependency.format()}).\n` + + `This may lead to unexpected issues when you come to deploy the application.\n` + + `Please install a version of Wrangler that satisfies the peer dependency: ${wranglerPeerDependency}.` ); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d13df5e6f1b8..2da13cab695d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2233,6 +2233,9 @@ importers: '@types/node': specifier: ^20.19.9 version: 20.19.9 + '@types/semver': + specifier: ^7.5.1 + version: 7.5.1 '@types/ws': specifier: ^8.5.13 version: 8.5.13 @@ -2242,6 +2245,9 @@ importers: mlly: specifier: ^1.7.4 version: 1.7.4 + semver: + specifier: ^7.7.1 + version: 7.7.2 tsdown: specifier: ^0.15.4 version: 0.15.4(typescript@5.8.3) @@ -14434,7 +14440,7 @@ snapshots: package-manager-detector: 0.2.9 picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 7.7.1 + semver: 7.7.2 spawndamnit: 3.0.1 term-size: 2.2.1 @@ -14835,7 +14841,7 @@ snapshots: devalue: 4.3.3 esbuild: 0.17.19 miniflare: 3.20250310.0 - semver: 7.7.1 + semver: 7.7.2 vitest: 2.1.9(@types/node@20.19.9)(@vitest/ui@2.1.9)(lightningcss@1.29.2) wrangler: 3.114.1(@cloudflare/workers-types@4.20251011.0) zod: 3.22.3 @@ -15554,7 +15560,7 @@ snapshots: '@babel/traverse': 7.25.9 '@babel/types': 7.26.3 prettier: 3.2.5 - semver: 7.7.1 + semver: 7.7.2 optionalDependencies: '@vue/compiler-sfc': 3.3.4 transitivePeerDependencies: @@ -15712,7 +15718,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.0': {} @@ -15726,7 +15732,7 @@ snapshots: '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping@0.3.31': dependencies: @@ -15748,7 +15754,7 @@ snapshots: parse-github-url: 1.0.2 picocolors: 1.1.1 sembear: 0.7.0 - semver: 7.7.1 + semver: 7.7.2 tinyexec: 0.3.2 validate-npm-package-name: 5.0.1 From f18381e9c83f7b9d8b6b8a408e153f65e9da49d2 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 3 Oct 2025 18:44:05 +0100 Subject: [PATCH 7/8] Apply suggestion from @jamesopstad Co-authored-by: James Opstad <13586373+jamesopstad@users.noreply.github.com> --- packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts index 3d7334e918a4..db70b28945c7 100644 --- a/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts +++ b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts @@ -26,7 +26,6 @@ export function assertWranglerVersion() { if (compare(installedVersion, wranglerDependency) < 0) { throw new Error( `The installed version of Wrangler (${installedVersion.format()}) is older than the version required by @cloudflare/vite-plugin (${wranglerDependency.format()}).\n` + - `Since the current package manager is flattening the versions of Wrangler, the @cloudflare/vite-plugin is using this version to read and write config files.\n` + `Please upgrade your installation of Wrangler to at least ${wranglerDependency}.` ); } From f09b8cd68e82d4133c4a13805fc49aa9b174e50f Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 3 Oct 2025 18:44:23 +0100 Subject: [PATCH 8/8] Apply suggestion from @jamesopstad Co-authored-by: James Opstad <13586373+jamesopstad@users.noreply.github.com> --- packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts index db70b28945c7..9051b53b22ec 100644 --- a/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts +++ b/packages/vite-plugin-cloudflare/src/assert-wrangler-version.ts @@ -26,7 +26,7 @@ export function assertWranglerVersion() { if (compare(installedVersion, wranglerDependency) < 0) { throw new Error( `The installed version of Wrangler (${installedVersion.format()}) is older than the version required by @cloudflare/vite-plugin (${wranglerDependency.format()}).\n` + - `Please upgrade your installation of Wrangler to at least ${wranglerDependency}.` + `Please install Wrangler version ${wranglerDependency} in your project.` ); }