diff --git a/.changeset/cyan-rivers-happen.md b/.changeset/cyan-rivers-happen.md new file mode 100644 index 0000000000..958f35f5c1 --- /dev/null +++ b/.changeset/cyan-rivers-happen.md @@ -0,0 +1,7 @@ +--- +"@cloudflare/vite-plugin": patch +--- + +Allow internal Wrangler config path overrides via env + +`@cloudflare/vite-plugin` now checks `CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH` when `configPath` is not set explicitly. This lets integrators provide generated Wrangler configs outside the project root without requiring users to thread `configPath` through framework config. diff --git a/packages/vite-plugin-cloudflare/e2e/generated-wrangler-config-path.test.ts b/packages/vite-plugin-cloudflare/e2e/generated-wrangler-config-path.test.ts new file mode 100644 index 0000000000..c4c5cefe48 --- /dev/null +++ b/packages/vite-plugin-cloudflare/e2e/generated-wrangler-config-path.test.ts @@ -0,0 +1,35 @@ +import { mkdir, rm, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { describe, test } from "vitest"; +import { fetchJson, runLongLived, seed, waitForReady } from "./helpers.js"; + +describe("generated Wrangler config path", () => { + const projectPath = seed("dynamic", { pm: "pnpm" }); + + test("can serve a Vite app using CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH", async ({ + expect, + }) => { + await mkdir(path.join(projectPath, ".generated"), { recursive: true }); + await writeFile( + path.join(projectPath, ".generated/wrangler.jsonc"), + JSON.stringify( + { + name: "cloudflare-vite-e2e-generated-config-path", + main: "../src/index.ts", + compatibility_date: "2024-12-30", + compatibility_flags: ["nodejs_compat"], + }, + null, + 2 + ) + ); + await rm(path.join(projectPath, "wrangler.jsonc")); + + const proc = await runLongLived("pnpm", "dev", projectPath, { + CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH: ".generated/wrangler.jsonc", + }); + const url = await waitForReady(proc); + + expect(await fetchJson(url)).toEqual("OK!"); + }); +}); diff --git a/packages/vite-plugin-cloudflare/src/__tests__/resolve-plugin-config.spec.ts b/packages/vite-plugin-cloudflare/src/__tests__/resolve-plugin-config.spec.ts index 0f7bcadd2f..03bcf7bb24 100644 --- a/packages/vite-plugin-cloudflare/src/__tests__/resolve-plugin-config.spec.ts +++ b/packages/vite-plugin-cloudflare/src/__tests__/resolve-plugin-config.spec.ts @@ -2,7 +2,7 @@ import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { removeDirSync } from "@cloudflare/workers-utils"; -import { afterEach, assert, beforeEach, describe, test } from "vitest"; +import { afterEach, assert, beforeEach, describe, test, vi } from "vitest"; import { resolvePluginConfig } from "../plugin-config"; import type { AssetsOnlyResolvedConfig, @@ -588,6 +588,127 @@ describe("resolvePluginConfig - zero-config mode", () => { }); }); +describe("resolvePluginConfig - internal config path env fallback", () => { + let tempDir: string; + let originalConfigPathEnvVar: string | undefined; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "vite-plugin-test-")); + originalConfigPathEnvVar = process.env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH; + }); + + afterEach(() => { + vi.unstubAllEnvs(); + if (originalConfigPathEnvVar === undefined) { + delete process.env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH; + } else { + process.env.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH = + originalConfigPathEnvVar; + } + removeDirSync(tempDir); + }); + + const viteEnv = { mode: "development", command: "serve" as const }; + + function createWorkerConfig(dir: string, name: string) { + const configPath = path.join(dir, "wrangler.jsonc"); + fs.mkdirSync(path.join(dir, "src"), { recursive: true }); + fs.writeFileSync( + configPath, + JSON.stringify({ + name, + main: "./src/index.ts", + compatibility_date: "2024-01-01", + }) + ); + fs.writeFileSync(path.join(dir, "src/index.ts"), "export default {}"); + return configPath; + } + + test("should resolve entry worker config from CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH loaded from env files", ({ + expect, + }) => { + const hiddenConfigPath = createWorkerConfig( + path.join(tempDir, ".sst"), + "hidden-worker" + ); + fs.writeFileSync( + path.join(tempDir, ".env.development"), + "CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH=.sst/wrangler.jsonc\n" + ); + + const result = resolvePluginConfig( + {}, + { root: tempDir }, + viteEnv + ) as WorkersResolvedConfig; + + expect(result.type).toBe("workers"); + const entryWorker = result.environmentNameToWorkerMap.get( + result.entryWorkerEnvironmentName + ); + assert(entryWorker); + expect(entryWorker.config.name).toBe("hidden-worker"); + expect(entryWorker.config.main).toBe( + path.join(tempDir, ".sst", "src/index.ts") + ); + expect([...result.configPaths]).toEqual([hiddenConfigPath]); + }); + + test("should prefer CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH over auto-discovered config", ({ + expect, + }) => { + createWorkerConfig(tempDir, "root-worker"); + const hiddenConfigPath = createWorkerConfig( + path.join(tempDir, ".sst"), + "hidden-worker" + ); + vi.stubEnv("CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH", ".sst/wrangler.jsonc"); + + const result = resolvePluginConfig( + {}, + { root: tempDir }, + viteEnv + ) as WorkersResolvedConfig; + + expect(result.type).toBe("workers"); + const entryWorker = result.environmentNameToWorkerMap.get( + result.entryWorkerEnvironmentName + ); + assert(entryWorker); + expect(entryWorker.config.name).toBe("hidden-worker"); + expect([...result.configPaths]).toEqual([hiddenConfigPath]); + }); + + test("should prefer explicit configPath over CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH", ({ + expect, + }) => { + createWorkerConfig(path.join(tempDir, ".sst"), "hidden-worker"); + const explicitConfigPath = createWorkerConfig( + path.join(tempDir, "visible"), + "explicit-worker" + ); + vi.stubEnv("CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH", ".sst/wrangler.jsonc"); + + const result = resolvePluginConfig( + { configPath: explicitConfigPath }, + { root: tempDir }, + viteEnv + ) as WorkersResolvedConfig; + + expect(result.type).toBe("workers"); + const entryWorker = result.environmentNameToWorkerMap.get( + result.entryWorkerEnvironmentName + ); + assert(entryWorker); + expect(entryWorker.config.name).toBe("explicit-worker"); + expect(entryWorker.config.main).toBe( + path.join(tempDir, "visible", "src/index.ts") + ); + expect([...result.configPaths]).toEqual([explicitConfigPath]); + }); +}); + describe("resolvePluginConfig - defaults fill in missing fields", () => { let tempDir: string; diff --git a/packages/vite-plugin-cloudflare/src/plugin-config.ts b/packages/vite-plugin-cloudflare/src/plugin-config.ts index 63947d335f..fe3306c23a 100644 --- a/packages/vite-plugin-cloudflare/src/plugin-config.ts +++ b/packages/vite-plugin-cloudflare/src/plugin-config.ts @@ -298,9 +298,11 @@ export function resolvePluginConfig( const configPaths = new Set(); const cloudflareEnv = prefixedEnv.CLOUDFLARE_ENV; const validateAndAddEnvironmentName = createEnvironmentNameValidator(); + const requestedEntryWorkerConfigPath = + pluginConfig.configPath ?? prefixedEnv.CLOUDFLARE_VITE_WRANGLER_CONFIG_PATH; const configPath = getValidatedWranglerConfigPath( root, - pluginConfig.configPath + requestedEntryWorkerConfigPath ); // Build entry worker config: defaults → file config → config()