diff --git a/.changeset/moody-breads-pump.md b/.changeset/moody-breads-pump.md new file mode 100644 index 000000000000..b1fd2a437728 --- /dev/null +++ b/.changeset/moody-breads-pump.md @@ -0,0 +1,20 @@ +--- +"@cloudflare/vite-plugin": minor +"wrangler": minor +--- + +Add support for loading local dev vars from .env files + +If there are no `.dev.vars` or `.dev.vars.` files, when running Wrangler or the Vite plugin in local development mode, +they will now try to load additional local dev vars from `.env`, `.env.local`, `.env.` and `.env..local` files. + +These loaded vars are only for local development and have no effect in production to the vars in a deployed Worker. +Wrangler and Vite will continue to load `.env` files in order to configure themselves as a tool. + +Further details: + +- In `vite build` the local vars will be computed and stored in a `.dev.vars` file next to the compiled Worker code, so that `vite preview` can use them. +- The `wrangler types` command will similarly read the `.env` files (if no `.dev.vars` files) in order to generate the `Env` interface. +- If the `CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV` environment variable is `"false"` then local dev variables will not be loaded from `.env` files. +- If the `CLOUDFLARE_INCLUDE_PROCESS_ENV` environment variable is `"true"` then all the environment variables found on `process.env` will be included as local dev vars. +- Wrangler (but not Vite plugin) also now supports the `--env-file=` global CLI option. This affects both loading `.env` to configure Wrangler the tool as well as loading local dev vars. diff --git a/.github/workflows/test-and-check.yml b/.github/workflows/test-and-check.yml index f50f1824c61d..b6023b3e1fd0 100644 --- a/.github/workflows/test-and-check.yml +++ b/.github/workflows/test-and-check.yml @@ -168,6 +168,7 @@ jobs: WRANGLER_LOG_PATH: ${{ runner.temp }}/wrangler-debug-logs/ TEST_REPORT_PATH: ${{ runner.temp }}/test-report/index.html CI_OS: ${{ matrix.description }} + NODE_DEBUG: "@cloudflare:vite-plugin" - name: Upload turbo logs if: always() diff --git a/fixtures/nodejs-hybrid-app/.env b/fixtures/nodejs-hybrid-app/.env new file mode 100644 index 000000000000..d81f8ff0f9a1 --- /dev/null +++ b/fixtures/nodejs-hybrid-app/.env @@ -0,0 +1 @@ +DEV_VAR_FROM_DOT_ENV="dev-var-from-dot-env" \ No newline at end of file diff --git a/fixtures/nodejs-hybrid-app/.gitignore b/fixtures/nodejs-hybrid-app/.gitignore new file mode 100644 index 000000000000..1e18f275e97c --- /dev/null +++ b/fixtures/nodejs-hybrid-app/.gitignore @@ -0,0 +1 @@ +!.env \ No newline at end of file diff --git a/fixtures/nodejs-hybrid-app/src/index.ts b/fixtures/nodejs-hybrid-app/src/index.ts index e8be3d3d4275..2bb97e3eef38 100644 --- a/fixtures/nodejs-hybrid-app/src/index.ts +++ b/fixtures/nodejs-hybrid-app/src/index.ts @@ -22,6 +22,10 @@ export default { return testGetRandomValues(); case "/test-process": return testProcessBehavior(); + case "/env": + return Response.json(env); + case "/process-env": + return Response.json(process.env); case "/query": return testPostgresLibrary(env, ctx); case "/test-x509-certificate": diff --git a/fixtures/nodejs-hybrid-app/tests/index.test.ts b/fixtures/nodejs-hybrid-app/tests/index.test.ts index 85a9ac56cdb8..e2e6a5964e3f 100644 --- a/fixtures/nodejs-hybrid-app/tests/index.test.ts +++ b/fixtures/nodejs-hybrid-app/tests/index.test.ts @@ -88,4 +88,22 @@ describe("nodejs compat", () => { const response = await fetch(`http://${ip}:${port}/test-http`); await expect(response.text()).resolves.toBe("OK"); }); + + test("process.env contains vars", async ({ expect }) => { + const { ip, port } = wrangler; + const response = await fetch(`http://${ip}:${port}/process-env`); + await expect(response.json()).resolves.toMatchObject({ + DB_HOSTNAME: "hh-pgsql-public.ebi.ac.uk", + DEV_VAR_FROM_DOT_ENV: "dev-var-from-dot-env", + }); + }); + + test("env contains vars", async ({ expect }) => { + const { ip, port } = wrangler; + const response = await fetch(`http://${ip}:${port}/env`); + await expect(response.json()).resolves.toMatchObject({ + DB_HOSTNAME: "hh-pgsql-public.ebi.ac.uk", + DEV_VAR_FROM_DOT_ENV: "dev-var-from-dot-env", + }); + }); }); diff --git a/fixtures/nodejs-hybrid-app/wrangler.jsonc b/fixtures/nodejs-hybrid-app/wrangler.jsonc index ac6d7d7b2e87..940dc869f77b 100644 --- a/fixtures/nodejs-hybrid-app/wrangler.jsonc +++ b/fixtures/nodejs-hybrid-app/wrangler.jsonc @@ -1,8 +1,9 @@ { "name": "nodejs-hybrid-app", "main": "src/index.ts", - // Setting compat date to 2024/09/23 means we don't need to use `nodejs_compat_v2` - "compatibility_date": "2024-09-23", + // Setting compat date after 2024/09/23 means we don't need to use `nodejs_compat_v2` + // Setting compat date after 2025/04/01 means we don't need to use `nodejs_compat_populate_process_env` + "compatibility_date": "2025-07-01", "compatibility_flags": ["nodejs_compat"], /* These DB connection values are to a public database containing information about diff --git a/fixtures/pages-workerjs-app/.env b/fixtures/pages-workerjs-app/.env new file mode 100644 index 000000000000..1566bb1d76a7 --- /dev/null +++ b/fixtures/pages-workerjs-app/.env @@ -0,0 +1 @@ +FOO=bar \ No newline at end of file diff --git a/fixtures/pages-workerjs-app/.gitignore b/fixtures/pages-workerjs-app/.gitignore new file mode 100644 index 000000000000..1e18f275e97c --- /dev/null +++ b/fixtures/pages-workerjs-app/.gitignore @@ -0,0 +1 @@ +!.env \ No newline at end of file diff --git a/fixtures/pages-workerjs-app/package.json b/fixtures/pages-workerjs-app/package.json index b9dcbdcbbb7a..ff4c20e311cf 100644 --- a/fixtures/pages-workerjs-app/package.json +++ b/fixtures/pages-workerjs-app/package.json @@ -4,7 +4,7 @@ "sideEffects": false, "scripts": { "check:type": "tsc", - "dev": "wrangler pages dev ./workerjs-test --port 8792", + "dev": "wrangler pages dev ./workerjs-test --port 8792 --compatibility-date=2025-07-15", "test:ci": "vitest run", "test:watch": "vitest", "type:tests": "tsc -p ./tests/tsconfig.json" diff --git a/fixtures/pages-workerjs-app/tests/index.test.ts b/fixtures/pages-workerjs-app/tests/index.test.ts index 7222233764f0..452b05eb7d46 100644 --- a/fixtures/pages-workerjs-app/tests/index.test.ts +++ b/fixtures/pages-workerjs-app/tests/index.test.ts @@ -35,7 +35,12 @@ describe("Pages _worker.js", () => { const { ip, port, stop } = await runWranglerPagesDev( resolve(__dirname, ".."), "./workerjs-test", - ["--no-bundle=false", "--port=0", "--inspector-port=0"] + [ + "--no-bundle=false", + "--port=0", + "--inspector-port=0", + "--compatibility-date=2025-07-15", + ] ); try { await expect( @@ -52,7 +57,12 @@ describe("Pages _worker.js", () => { const { ip, port, stop } = await runWranglerPagesDev( resolve(__dirname, ".."), "./workerjs-test", - ["--bundle", "--port=0", "--inspector-port=0"] + [ + "--bundle", + "--port=0", + "--inspector-port=0", + "--compatibility-date=2025-07-15", + ] ); try { await expect( @@ -71,8 +81,9 @@ describe("Pages _worker.js", () => { await runWranglerPagesDev(resolve(__dirname, ".."), "./workerjs-test", [ "--port=0", "--inspector-port=0", + "--compatibility-date=2025-07-15", ]); - vi.waitFor( + await vi.waitFor( () => { expect(getOutput()).toContain("Ready on"); }, @@ -118,7 +129,7 @@ describe("Pages _worker.js", () => { "--port=0", "--inspector-port=0", ]); - vi.waitFor( + await vi.waitFor( () => { expect(getOutput()).toContain("Ready on"); }, @@ -157,6 +168,22 @@ describe("Pages _worker.js", () => { } }); + // Serendipitously, this .env reading also works for `wrangler pages dev`. + it("should read local dev vars from the .env file", async ({ expect }) => { + const { ip, port, stop } = await runWranglerPagesDev( + resolve(__dirname, ".."), + "./workerjs-test", + ["--port=0", "--inspector-port=0", "--compatibility-date=2025-07-15"] + ); + try { + const response = await fetch(`http://${ip}:${port}/env`); + const env = (await response.json()) as { FOO: string }; + expect(env.FOO).toBe("bar"); + } finally { + await stop(); + } + }); + async function tryRename( basePath: string, from: string, diff --git a/fixtures/pages-workerjs-app/workerjs-test/_worker.js b/fixtures/pages-workerjs-app/workerjs-test/_worker.js index 7e6b44790b7b..c5174509965c 100644 --- a/fixtures/pages-workerjs-app/workerjs-test/_worker.js +++ b/fixtures/pages-workerjs-app/workerjs-test/_worker.js @@ -1,7 +1,11 @@ import text from "./other-script"; export default { - async fetch() { - return new Response(text); + async fetch(request, env) { + if (request.url.endsWith("/env")) { + return Response.json(env); + } else { + return new Response(text); + } }, }; diff --git a/fixtures/worker-app/.env b/fixtures/worker-app/.env new file mode 100644 index 000000000000..1566bb1d76a7 --- /dev/null +++ b/fixtures/worker-app/.env @@ -0,0 +1 @@ +FOO=bar \ No newline at end of file diff --git a/fixtures/worker-app/.gitignore b/fixtures/worker-app/.gitignore index 1521c8b7652b..b67a5dd0610b 100644 --- a/fixtures/worker-app/.gitignore +++ b/fixtures/worker-app/.gitignore @@ -1 +1,2 @@ dist +!.env \ No newline at end of file diff --git a/fixtures/worker-app/src/index.js b/fixtures/worker-app/src/index.js index f8d72b34abed..f97b90b1efe6 100644 --- a/fixtures/worker-app/src/index.js +++ b/fixtures/worker-app/src/index.js @@ -25,6 +25,7 @@ export default { const { pathname, origin, hostname, host } = new URL(request.url); if (pathname.startsWith("/fav")) return new Response("Not found", { status: 404 }); + if (pathname === "/env") return Response.json(env.FOO); if (pathname === "/version_metadata") return Response.json(env.METADATA); if (pathname === "/random") return new Response(hexEncode(randomBytes(8))); if (pathname === "/error") throw new Error("Oops!"); diff --git a/fixtures/worker-app/tests/env-vars.test.ts b/fixtures/worker-app/tests/build-conditions.test.ts similarity index 100% rename from fixtures/worker-app/tests/env-vars.test.ts rename to fixtures/worker-app/tests/build-conditions.test.ts diff --git a/fixtures/worker-app/tests/index.test.ts b/fixtures/worker-app/tests/index.test.ts index f4d78ea09c0a..f3bd1b553b4d 100644 --- a/fixtures/worker-app/tests/index.test.ts +++ b/fixtures/worker-app/tests/index.test.ts @@ -203,4 +203,10 @@ describe("'wrangler dev' correctly renders pages", () => { ] `); }); + + it("reads local dev vars from the .env file", async ({ expect }) => { + const response = await fetch(`http://${ip}:${port}/env`); + const env = await response.text(); + expect(env).toBe(`"bar"`); + }); }); diff --git a/packages/miniflare/src/index.ts b/packages/miniflare/src/index.ts index cc80774fdc95..3153bd4bbc76 100644 --- a/packages/miniflare/src/index.ts +++ b/packages/miniflare/src/index.ts @@ -776,56 +776,6 @@ export function _transformsForContentEncodingAndContentType( return encoders; } -async function writeResponse(response: Response, res: http.ServerResponse) { - // Convert headers into Node-friendly format - const headers: http.OutgoingHttpHeaders = {}; - for (const entry of response.headers) { - const key = entry[0].toLowerCase(); - const value = entry[1]; - if (key === "set-cookie") { - headers[key] = response.headers.getSetCookie(); - } else { - headers[key] = value; - } - } - - // If a `Content-Encoding` header is set, we'll need to encode the body - // (likely only set by custom service bindings) - const encoding = headers["content-encoding"]?.toString(); - const type = headers["content-type"]?.toString(); - const encoders = _transformsForContentEncodingAndContentType(encoding, type); - if (encoders.length > 0) { - // `Content-Length` if set, will be wrong as it's for the decoded length - delete headers["content-length"]; - } - - res.writeHead(response.status, response.statusText, headers); - - // `initialStream` is the stream we'll write the response to. It - // should end up as the first encoder, piping to the next encoder, - // and finally piping to the response: - // - // encoders[0] (initialStream) -> encoders[1] -> res - // - // Not using `pipeline(passThrough, ...encoders, res)` here as that - // gives a premature close error with server sent events. This also - // avoids creating an extra stream even when we're not encoding. - let initialStream: Writable = res; - for (let i = encoders.length - 1; i >= 0; i--) { - encoders[i].pipe(initialStream); - initialStream = encoders[i]; - } - - // Response body may be null if empty - if (response.body) { - for await (const chunk of response.body) { - if (chunk) initialStream.write(chunk); - } - } - - initialStream.end(); -} - function safeReadableStreamFrom(iterable: AsyncIterable) { // Adapted from `undici`, catches errors from `next()` to avoid unhandled // rejections from aborted request body streams: @@ -1344,7 +1294,7 @@ export class Miniflare { res.writeHead(404); res.end(); } else { - await writeResponse(response, res); + await this.#writeResponse(response, res); } } @@ -1402,7 +1352,7 @@ export class Miniflare { } // Otherwise, send the response as is (e.g. unauthorised) - await writeResponse(response, res); + await this.#writeResponse(response, res); }; #handleLoopbackConnect = async ( @@ -1508,6 +1458,65 @@ export class Miniflare { }); }; + async #writeResponse(response: Response, res: http.ServerResponse) { + // Convert headers into Node-friendly format + const headers: http.OutgoingHttpHeaders = {}; + for (const entry of response.headers) { + const key = entry[0].toLowerCase(); + const value = entry[1]; + if (key === "set-cookie") { + headers[key] = response.headers.getSetCookie(); + } else { + headers[key] = value; + } + } + + // If a `Content-Encoding` header is set, we'll need to encode the body + // (likely only set by custom service bindings) + const encoding = headers["content-encoding"]?.toString(); + const type = headers["content-type"]?.toString(); + const encoders = _transformsForContentEncodingAndContentType( + encoding, + type + ); + if (encoders.length > 0) { + // `Content-Length` if set, will be wrong as it's for the decoded length + delete headers["content-length"]; + } + + res.writeHead(response.status, response.statusText, headers); + + // `initialStream` is the stream we'll write the response to. It + // should end up as the first encoder, piping to the next encoder, + // and finally piping to the response: + // + // encoders[0] (initialStream) -> encoders[1] -> res + // + // Not using `pipeline(passThrough, ...encoders, res)` here as that + // gives a premature close error with server sent events. This also + // avoids creating an extra stream even when we're not encoding. + let initialStream: Writable = res; + for (let i = encoders.length - 1; i >= 0; i--) { + encoders[i].pipe(initialStream); + initialStream = encoders[i]; + } + + // Response body may be null if empty + if (response.body) { + try { + for await (const chunk of response.body) { + if (chunk) initialStream.write(chunk); + } + } catch (error) { + this.#log.debug( + `Error writing response body, closing response early: ${error}` + ); + } + } + + initialStream.end(); + } + async #getLoopbackPort(): Promise { // This function must be run with `#runtimeMutex` held diff --git a/packages/vite-plugin-cloudflare/e2e/basic.test.ts b/packages/vite-plugin-cloudflare/e2e/basic.test.ts index 1fb6ede017cf..ce566f06e640 100644 --- a/packages/vite-plugin-cloudflare/e2e/basic.test.ts +++ b/packages/vite-plugin-cloudflare/e2e/basic.test.ts @@ -1,8 +1,9 @@ +import { rm, writeFile } from "node:fs/promises"; import { describe, test } from "vitest"; import { fetchJson, runLongLived, seed, waitForReady } from "./helpers.js"; const isWindows = process.platform === "win32"; -const packageManagers = ["pnpm", , "npm", "yarn"] as const; +const packageManagers = ["pnpm", "npm", "yarn"] as const; const commands = ["dev", "buildAndPreview"] as const; describe("basic e2e tests", () => { @@ -10,18 +11,160 @@ describe("basic e2e tests", () => { const projectPath = seed("basic", pm); describe.each(commands)('with "%s" command', (command) => { - describe("node compatibility", () => { - test.skipIf(command === "buildAndPreview")( - "can serve a Worker request", - async ({ expect }) => { + test.skipIf(isWindows && command === "buildAndPreview")( + "can serve a Worker that uses a Node.js API (crypto)", + async ({ expect }) => { + const proc = await runLongLived(pm, command, projectPath); + const url = await waitForReady(proc); + expect(await fetchJson(url + "/api/")).toEqual({ + name: "Cloudflare", + }); + } + ); + + describe.skipIf(isWindows && command === "buildAndPreview")( + "environment variables", + () => { + test("can read vars from wrangler configuration and .env", async ({ + expect, + onTestFinished, + }) => { + await writeFile( + projectPath + "/.env", + "SECRET_A=dev-1\nSECRET_B=dev-2" + ); + onTestFinished(async () => { + await rm(projectPath + "/.env"); + }); const proc = await runLongLived(pm, command, projectPath); const url = await waitForReady(proc); - expect(await fetchJson(url + "/api/")).toEqual({ - name: "Cloudflare", + expect(await fetchJson(url + "/env/")).toMatchObject({ + SECRET_A: "dev-1", + SECRET_B: "dev-2", + VAR_1: "var-1", }); - } - ); - }); + }); + + test("will not load local dev vars from .env if there is a .dev.vars file", async ({ + expect, + onTestFinished, + }) => { + await writeFile( + projectPath + "/.env", + "SECRET_A=dot-env-1\nSECRET_B=dot-env-2" + ); + await writeFile( + projectPath + "/.dev.vars", + "SECRET_A=dev-dot-vars-1" + ); + onTestFinished(async () => { + await rm(projectPath + "/.env"); + await rm(projectPath + "/.dev.vars"); + }); + const proc = await runLongLived(pm, command, projectPath); + const url = await waitForReady(proc); + expect(await fetchJson(url + "/env/")).toMatchObject({ + SECRET_A: "dev-dot-vars-1", + VAR_1: "var-1", + }); + }); + + test("will not load local dev vars from .env if CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV is set to false", async ({ + expect, + onTestFinished, + }) => { + await writeFile( + projectPath + "/.env", + "SECRET_A=dot-env-1\nSECRET_B=dot-env-2" + ); + onTestFinished(async () => { + await rm(projectPath + "/.env"); + }); + const proc = await runLongLived(pm, command, projectPath, { + CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV: "false", + }); + const url = await waitForReady(proc); + expect(await fetchJson(url + "/env/")).toMatchObject({ + VAR_1: "var-1", + }); + }); + + test("can merge vars from wrangler configuration, .env, and .env.local", async ({ + expect, + onTestFinished, + }) => { + await writeFile( + projectPath + "/.env", + "SECRET_A=dev-1\nSECRET_B=dev-2" + ); + await writeFile( + projectPath + "/.env.local", + "SECRET_A=local-dev-1" + ); + onTestFinished(async () => { + await rm(projectPath + "/.env"); + await rm(projectPath + "/.env.local"); + }); + const proc = await runLongLived(pm, command, projectPath); + const url = await waitForReady(proc); + expect(await fetchJson(url + "/env/")).toMatchObject({ + SECRET_A: "local-dev-1", + SECRET_B: "dev-2", + VAR_1: "var-1", + }); + }); + + test("can merge vars from wrangler configuration, .env, and .env.local, and environment specific files", async ({ + expect, + onTestFinished, + }) => { + await writeFile( + projectPath + "/.env", + "SECRET_A=dev-1\nSECRET_B=dev-2" + ); + await writeFile( + projectPath + "/.env.local", + "SECRET_A=local-dev-1" + ); + await writeFile( + projectPath + "/.env.staging", + "SECRET_B=staging-2\nSECRET_C=staging-3" + ); + await writeFile( + projectPath + "/.env.staging.local", + "SECRET_C=local-staging-3" + ); + onTestFinished(async () => { + await rm(projectPath + "/.env"); + await rm(projectPath + "/.env.local"); + await rm(projectPath + "/.env.staging"); + await rm(projectPath + "/.env.staging.local"); + }); + const proc = await runLongLived(pm, command, projectPath, { + CLOUDFLARE_ENV: "staging", + }); + const url = await waitForReady(proc); + expect(await fetchJson(url + "/env/")).toMatchObject({ + SECRET_A: "local-dev-1", + SECRET_B: "staging-2", + SECRET_C: "local-staging-3", + VAR_1: "var-1", + }); + }); + + test("can read vars from process.env if CLOUDFLARE_INCLUDE_PROCESS_ENV is set", async ({ + expect, + }) => { + const proc = await runLongLived(pm, command, projectPath, { + CLOUDFLARE_INCLUDE_PROCESS_ENV: "true", + }); + const url = await waitForReady(proc); + expect(await fetchJson(url + "/env/")).toMatchObject({ + CLOUDFLARE_INCLUDE_PROCESS_ENV: "true", // this proves we read the process.env + }); + }); + } + ); // This test checks that wrapped bindings which rely on additional workers with an authed connection to the CF API work // They are skipped if you have not provided the necessary account id and api token. diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/basic/api/index.ts b/packages/vite-plugin-cloudflare/e2e/fixtures/basic/api/index.ts index 285ee3304c1a..d7e420302ad1 100644 --- a/packages/vite-plugin-cloudflare/e2e/fixtures/basic/api/index.ts +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/basic/api/index.ts @@ -17,6 +17,10 @@ export default { }); } + if (url.pathname === "/env/") { + return Response.json(env); + } + if (url.pathname.startsWith("/ai/")) { const messages = [ { diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/basic/tsconfig.worker.json b/packages/vite-plugin-cloudflare/e2e/fixtures/basic/tsconfig.worker.json index a0819b950a6e..2c0ec94c20f0 100644 --- a/packages/vite-plugin-cloudflare/e2e/fixtures/basic/tsconfig.worker.json +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/basic/tsconfig.worker.json @@ -2,7 +2,7 @@ "extends": "./tsconfig.node.json", "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.worker.tsbuildinfo", - "types": ["@cloudflare/workers-types/2023-07-01", "vite/client"] + "types": ["@cloudflare/workers-types/2023-07-01", "vite/client", "node"] }, "include": ["api"] } diff --git a/packages/vite-plugin-cloudflare/e2e/fixtures/basic/wrangler.jsonc b/packages/vite-plugin-cloudflare/e2e/fixtures/basic/wrangler.jsonc index 41cd5113e133..625d6aab622f 100644 --- a/packages/vite-plugin-cloudflare/e2e/fixtures/basic/wrangler.jsonc +++ b/packages/vite-plugin-cloudflare/e2e/fixtures/basic/wrangler.jsonc @@ -10,4 +10,7 @@ "ai": { "binding": "AI", }, + "vars": { + "VAR_1": "var-1", + }, } diff --git a/packages/vite-plugin-cloudflare/playground/__test-utils__/index.ts b/packages/vite-plugin-cloudflare/playground/__test-utils__/index.ts index cc37aaf32e10..246ba055a0af 100644 --- a/packages/vite-plugin-cloudflare/playground/__test-utils__/index.ts +++ b/packages/vite-plugin-cloudflare/playground/__test-utils__/index.ts @@ -1,4 +1,5 @@ -import { test } from "vitest"; +import fs from "node:fs"; +import { onTestFinished, test } from "vitest"; export * from "../vitest-setup"; export * from "./responses"; @@ -6,3 +7,21 @@ export * from "./responses"; export function failsIf(condition: boolean) { return condition ? test.fails : test; } + +/** + * Makes a change to a file and restores it after the test is finished. + */ +export function mockFileChange( + /** The path to the file to change */ + filePath: string, + /** A function that modifies the original content of the file */ + mutateFn: (originalContent: string) => string +) { + const originalContent = fs.readFileSync(filePath, "utf-8"); + onTestFinished(() => { + console.log("Restoring file change for", filePath); + fs.writeFileSync(filePath, originalContent); + }); + console.log("Mocking file change for", filePath); + fs.writeFileSync(filePath, mutateFn(originalContent)); +} diff --git a/packages/vite-plugin-cloudflare/playground/__test-utils__/responses.ts b/packages/vite-plugin-cloudflare/playground/__test-utils__/responses.ts index e0453aad190b..c9d600ddcc2b 100644 --- a/packages/vite-plugin-cloudflare/playground/__test-utils__/responses.ts +++ b/packages/vite-plugin-cloudflare/playground/__test-utils__/responses.ts @@ -1,5 +1,8 @@ import { page, viteTestUrl } from "./index"; +/** Common options to use with `vi.waitFor()` */ +export const WAIT_FOR_OPTIONS = { timeout: 5_000, interval: 500 }; + export async function getTextResponse(path = "/"): Promise { const response = await getResponse(path); return response.text(); @@ -7,19 +10,18 @@ export async function getTextResponse(path = "/"): Promise { export async function getJsonResponse( path = "/" -): Promise> { +): Promise | Array> { const response = await getResponse(path); const text = await response.text(); try { return JSON.parse(text); - } catch (e) { + } catch { throw new Error("Invalid JSON response:\n" + text); } } export async function getResponse(path = "/") { const url = `${viteTestUrl}${path}`; - const response = page.waitForResponse(url); await page.goto(url); return response; diff --git a/packages/vite-plugin-cloudflare/playground/config-changes/__tests__/config-changes.spec.ts b/packages/vite-plugin-cloudflare/playground/config-changes/__tests__/config-changes.spec.ts index a794cbc80f3f..ea99764e67ed 100644 --- a/packages/vite-plugin-cloudflare/playground/config-changes/__tests__/config-changes.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/config-changes/__tests__/config-changes.spec.ts @@ -1,84 +1,73 @@ -import * as fs from "node:fs"; import * as path from "node:path"; -import { expect, test, vi } from "vitest"; -import { getTextResponse, isBuild, serverLogs } from "../../__test-utils__"; +import { describe, expect, test, vi } from "vitest"; +import { + getTextResponse, + isBuild, + mockFileChange, + serverLogs, + WAIT_FOR_OPTIONS, +} from "../../__test-utils__"; -test.runIf(!isBuild)( - "successfully updates when a var is updated in the Worker config", - async ({ onTestFinished }) => { - const workerConfigPath = path.join(__dirname, "../wrangler.json"); - const originalWorkerConfig = fs.readFileSync(workerConfigPath, "utf-8"); - - onTestFinished(async () => { - fs.writeFileSync(workerConfigPath, originalWorkerConfig); - // We need to ensure that the original config is restored before the next test runs +describe("config-changes", () => { + test.runIf(!isBuild)( + "successfully updates when a var is updated in the Worker config", + async () => { await vi.waitFor( - async () => { - const revertedResponse = await getTextResponse(); - expect(revertedResponse).toContain('The value of MY_VAR is "one"'); - }, - { timeout: 5000 } + async () => + expect(await getTextResponse()).toContain( + 'The value of MY_VAR is "one"' + ), + WAIT_FOR_OPTIONS ); - }); - - const originalResponse = await getTextResponse(); - expect(originalResponse).toContain('The value of MY_VAR is "one"'); - const updatedWorkerConfig = JSON.stringify({ - ...JSON.parse(originalWorkerConfig), - vars: { - MY_VAR: "two", - }, - }); - fs.writeFileSync(workerConfigPath, updatedWorkerConfig); - await vi.waitFor( - async () => { - const updatedResponse = await getTextResponse(); - expect(updatedResponse).toContain('The value of MY_VAR is "two"'); - }, - { timeout: 5000 } - ); - } -); + mockFileChange(path.join(__dirname, "../wrangler.json"), (content) => + JSON.stringify({ + ...JSON.parse(content), + vars: { + MY_VAR: "two", + }, + }) + ); -test.runIf(!isBuild)( - "reports errors in updates to the Worker config", - async ({ onTestFinished }) => { - const workerConfigPath = path.join(__dirname, "../wrangler.json"); - const originalWorkerConfig = fs.readFileSync(workerConfigPath, "utf-8"); + await vi.waitFor( + async () => + expect(await getTextResponse()).toContain( + 'The value of MY_VAR is "two"' + ), + WAIT_FOR_OPTIONS + ); + } + ); - onTestFinished(async () => { - fs.writeFileSync(workerConfigPath, originalWorkerConfig); - // We need to ensure that the original config is restored before the next test runs + test.runIf(!isBuild)( + "reports errors in updates to the Worker config", + async () => { await vi.waitFor( - async () => { - const revertedResponse = await getTextResponse(); - expect(revertedResponse).toContain('The value of MY_VAR is "one"'); - }, - { timeout: 5000 } + async () => + expect(await getTextResponse()).toContain( + 'The value of MY_VAR is "one"' + ), + WAIT_FOR_OPTIONS ); - }); - const originalResponse = await getTextResponse(); - expect(originalResponse).toContain('The value of MY_VAR is "one"'); + mockFileChange(path.join(__dirname, "../wrangler.json"), (content) => + JSON.stringify({ + ...JSON.parse(content), + main: "./src/non-existing-file.ts", + vars: { + MY_VAR: "two", + }, + }) + ); - const updatedWorkerConfig = JSON.stringify({ - ...JSON.parse(originalWorkerConfig), - main: "./src/non-existing-file.ts", - vars: { - MY_VAR: "two", - }, - }); - fs.writeFileSync(workerConfigPath, updatedWorkerConfig); - await vi.waitFor( - async () => { - const newResponse = await getTextResponse(); + await vi.waitFor(async () => { expect(serverLogs.errors.join()).toMatch( /.*The provided Wrangler config main field .+? doesn't point to an existing file.*/ ); - expect(newResponse).toContain('The value of MY_VAR is "one"'); - }, - { timeout: 5000 } - ); - } -); + expect(await getTextResponse()).toContain( + 'The value of MY_VAR is "one"' + ); + }, WAIT_FOR_OPTIONS); + } + ); +}); diff --git a/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/dev-vars-loading.spec.ts b/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/dev-vars-loading.spec.ts index 270ca6870aa2..d4ee32f28971 100644 --- a/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/dev-vars-loading.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/dev-vars-loading.spec.ts @@ -1,15 +1,24 @@ import fs from "node:fs"; -import { describe, expect, test } from "vitest"; -import { getJsonResponse, isBuild, testDir } from "../../__test-utils__"; +import { describe, expect, test, vi } from "vitest"; +import { + getJsonResponse, + isBuild, + testDir, + WAIT_FOR_OPTIONS, +} from "../../__test-utils__"; test("reading variables from a standard .dev.vars file", async () => { - expect(await getJsonResponse()).toEqual({ - "variables present in .dev.vars": { - MY_DEV_VAR_A: "my .dev.vars variable A", - MY_DEV_VAR_B: "my .dev.vars variable B", - MY_DEV_VAR_C: "my .dev.vars variable C", - }, - }); + await vi.waitFor( + async () => + expect(await getJsonResponse()).toEqual({ + "variables present in .dev.vars": { + MY_DEV_VAR_A: "my .dev.vars variable A", + MY_DEV_VAR_B: "my .dev.vars variable B", + MY_DEV_VAR_C: "my .dev.vars variable C", + }, + }), + WAIT_FOR_OPTIONS + ); }); describe.runIf(isBuild)("build output files", () => { diff --git a/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/vars-changes.spec.ts b/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/vars-changes.spec.ts index e1898bba5424..dfdfefdd79c5 100644 --- a/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/vars-changes.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/vars-changes.spec.ts @@ -1,61 +1,43 @@ -import * as fs from "node:fs"; import * as path from "node:path"; import { expect, test, vi } from "vitest"; -import { getJsonResponse, isBuild } from "../../__test-utils__"; +import { + getJsonResponse, + isBuild, + mockFileChange, + WAIT_FOR_OPTIONS, +} from "../../__test-utils__"; test.runIf(!isBuild)( "successfully updates when a var is updated in a .dev.vars file", - async ({ onTestFinished }) => { - const dotDevDotVarsFilePath = path.join(__dirname, "../.dev.vars"); - const originalDotDevDotVars = fs.readFileSync( - dotDevDotVarsFilePath, - "utf-8" - ); - - onTestFinished(async () => { - fs.writeFileSync(dotDevDotVarsFilePath, originalDotDevDotVars); - // We need to ensure that the original config is restored before the next test runs - await vi.waitFor( - async () => { - expect(await getJsonResponse()).toEqual({ - "variables present in .dev.vars": { - MY_DEV_VAR_A: "my .dev.vars variable A", - MY_DEV_VAR_B: "my .dev.vars variable B", - MY_DEV_VAR_C: "my .dev.vars variable C", - }, - }); - }, - { timeout: 5000 } - ); - }); - - const originalResponse = await getJsonResponse(); - expect(originalResponse).toEqual({ - "variables present in .dev.vars": { - MY_DEV_VAR_A: "my .dev.vars variable A", - MY_DEV_VAR_B: "my .dev.vars variable B", - MY_DEV_VAR_C: "my .dev.vars variable C", - }, - }); - - const updatedDotDevDotVars = originalDotDevDotVars.replace( - /my \.dev\.vars variable/g, - "my .dev.vars UPDATED variable" - ); - - fs.writeFileSync(dotDevDotVarsFilePath, updatedDotDevDotVars); + async () => { await vi.waitFor( - async () => { - const updatedResponse = await getJsonResponse(); - expect(updatedResponse).toEqual({ + async () => + expect(await getJsonResponse()).toEqual({ "variables present in .dev.vars": { - MY_DEV_VAR_A: "my .dev.vars UPDATED variable A", - MY_DEV_VAR_B: "my .dev.vars UPDATED variable B", - MY_DEV_VAR_C: "my .dev.vars UPDATED variable C", + MY_DEV_VAR_A: "my .dev.vars variable A", + MY_DEV_VAR_B: "my .dev.vars variable B", + MY_DEV_VAR_C: "my .dev.vars variable C", }, - }); - }, - { timeout: 5000 } + }), + WAIT_FOR_OPTIONS + ); + + mockFileChange(path.join(__dirname, "../.dev.vars"), (content) => + content.replace( + /my \.dev\.vars variable/g, + "my .dev.vars UPDATED variable" + ) ); + + await vi.waitFor(async () => { + const updatedResponse = await getJsonResponse(); + expect(updatedResponse).toEqual({ + "variables present in .dev.vars": { + MY_DEV_VAR_A: "my .dev.vars UPDATED variable A", + MY_DEV_VAR_B: "my .dev.vars UPDATED variable B", + MY_DEV_VAR_C: "my .dev.vars UPDATED variable C", + }, + }); + }, WAIT_FOR_OPTIONS); } ); diff --git a/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/with-specified-env/dev-vars-loading.spec.ts b/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/with-specified-env/dev-vars-loading.spec.ts index 9dc8216fc048..e10fb0306491 100644 --- a/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/with-specified-env/dev-vars-loading.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/with-specified-env/dev-vars-loading.spec.ts @@ -1,14 +1,23 @@ import fs from "node:fs"; -import { describe, expect, test } from "vitest"; -import { getJsonResponse, isBuild, testDir } from "../../../__test-utils__"; +import { describe, expect, test, vi } from "vitest"; +import { + getJsonResponse, + isBuild, + testDir, + WAIT_FOR_OPTIONS, +} from "../../../__test-utils__"; test("reading variables from a staging .dev.vars file", async () => { - expect(await getJsonResponse()).toEqual({ - "variables present in .dev.vars.staging": { - MY_DEV_VAR_A: "my .dev.vars staging variable A", - MY_DEV_VAR_B: "my .dev.vars staging variable B", - }, - }); + await vi.waitFor( + async () => + expect(await getJsonResponse()).toEqual({ + "variables present in .dev.vars.staging": { + MY_DEV_VAR_A: "my .dev.vars staging variable A", + MY_DEV_VAR_B: "my .dev.vars staging variable B", + }, + }), + WAIT_FOR_OPTIONS + ); }); describe.runIf(isBuild)("build output files", () => { diff --git a/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/with-specified-env/vars-changes.spec.ts b/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/with-specified-env/vars-changes.spec.ts index ce42e6ae712b..0a72dc11f921 100644 --- a/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/with-specified-env/vars-changes.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/dev-vars/__tests__/with-specified-env/vars-changes.spec.ts @@ -1,61 +1,41 @@ -import * as fs from "node:fs"; import * as path from "node:path"; import { expect, test, vi } from "vitest"; -import { getJsonResponse, isBuild } from "../../../__test-utils__"; +import { + getJsonResponse, + isBuild, + mockFileChange, + WAIT_FOR_OPTIONS, +} from "../../../__test-utils__"; test.runIf(!isBuild)( "successfully updates when a var is updated in a .dev.vars.staging file", - async ({ onTestFinished }) => { - const dotDevDotVarsFilePath = path.join( - __dirname, - "../../.dev.vars.staging" - ); - const originalDotDevDotVars = fs.readFileSync( - dotDevDotVarsFilePath, - "utf-8" - ); - - onTestFinished(async () => { - fs.writeFileSync(dotDevDotVarsFilePath, originalDotDevDotVars); - // We need to ensure that the original config is restored before the next test runs - await vi.waitFor( - async () => { - expect(await getJsonResponse()).toEqual({ - "variables present in .dev.vars.staging": { - MY_DEV_VAR_A: "my .dev.vars staging variable A", - MY_DEV_VAR_B: "my .dev.vars staging variable B", - }, - }); - }, - { timeout: 5000 } - ); - }); - - const originalResponse = await getJsonResponse(); - expect(originalResponse).toEqual({ - "variables present in .dev.vars.staging": { - MY_DEV_VAR_A: "my .dev.vars staging variable A", - MY_DEV_VAR_B: "my .dev.vars staging variable B", - }, - }); - - const updatedDotDevDotVars = originalDotDevDotVars.replace( - /my \.dev\.vars staging variable/g, - "my .dev.vars UPDATED staging variable" - ); - - fs.writeFileSync(dotDevDotVarsFilePath, updatedDotDevDotVars); + async () => { await vi.waitFor( - async () => { - const updatedResponse = await getJsonResponse(); - expect(updatedResponse).toEqual({ + async () => + expect(await getJsonResponse()).toEqual({ "variables present in .dev.vars.staging": { - MY_DEV_VAR_A: "my .dev.vars UPDATED staging variable A", - MY_DEV_VAR_B: "my .dev.vars UPDATED staging variable B", + MY_DEV_VAR_A: "my .dev.vars staging variable A", + MY_DEV_VAR_B: "my .dev.vars staging variable B", }, - }); - }, - { timeout: 5000 } + }), + WAIT_FOR_OPTIONS + ); + + mockFileChange(path.join(__dirname, "../../.dev.vars.staging"), (content) => + content.replace( + /my \.dev\.vars staging variable/g, + "my .dev.vars UPDATED staging variable" + ) ); + + await vi.waitFor(async () => { + const updatedResponse = await getJsonResponse(); + expect(updatedResponse).toEqual({ + "variables present in .dev.vars.staging": { + MY_DEV_VAR_A: "my .dev.vars UPDATED staging variable A", + MY_DEV_VAR_B: "my .dev.vars UPDATED staging variable B", + }, + }); + }, WAIT_FOR_OPTIONS); } ); diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/.env b/packages/vite-plugin-cloudflare/playground/dot-env/.env new file mode 100644 index 000000000000..fd451e9d186b --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/.env @@ -0,0 +1,4 @@ +ENV_NAME = "" +MY_DEV_VAR_A = "my .env variable A" +MY_DEV_VAR_B = "my .env variable B" +MY_DEV_VAR_C = "my .env variable C" diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/.env.staging b/packages/vite-plugin-cloudflare/playground/dot-env/.env.staging new file mode 100644 index 000000000000..fc6f6c87db8d --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/.env.staging @@ -0,0 +1,3 @@ +ENV_NAME = "staging" +MY_DEV_VAR_A = "my .env staging variable A" +MY_DEV_VAR_B = "my .env staging variable B" diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/.env.with-specified-env b/packages/vite-plugin-cloudflare/playground/dot-env/.env.with-specified-env new file mode 100644 index 000000000000..69498a438586 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/.env.with-specified-env @@ -0,0 +1 @@ +CLOUDFLARE_ENV=staging diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/.gitignore b/packages/vite-plugin-cloudflare/playground/dot-env/.gitignore new file mode 100644 index 000000000000..cde945449318 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/.gitignore @@ -0,0 +1 @@ +!.env* diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/dot-env-loading.spec.ts b/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/dot-env-loading.spec.ts new file mode 100644 index 000000000000..8f4bbb3f2180 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/dot-env-loading.spec.ts @@ -0,0 +1,39 @@ +import fs from "node:fs"; +import { describe, expect, test } from "vitest"; +import { getJsonResponse, isBuild, testDir } from "../../__test-utils__"; + +const expectedVars = { + MY_DEV_VAR_A: "my .env variable A", + MY_DEV_VAR_B: "my .env variable B", + MY_DEV_VAR_C: "my .env variable C", +}; + +test("reading variables from a standard .env file", async () => { + expect(await getJsonResponse()).toEqual({ + "variables loaded from .env": expectedVars, + }); +}); + +describe.runIf(isBuild)("build output files", () => { + test("the .dev.vars file has been created in the build directory", async () => { + const distDevVarsPath = `${testDir}/dist/worker/.dev.vars`; + const distDevVarsExists = fs.existsSync(distDevVarsPath); + expect(distDevVarsExists).toBe(true); + + const distDevVarsContent = fs.readFileSync(distDevVarsPath, "utf-8"); + expect(distDevVarsContent).toMatchInlineSnapshot(` + "ENV_NAME = "" + MY_DEV_VAR_A = "my .env variable A" + MY_DEV_VAR_B = "my .env variable B" + MY_DEV_VAR_C = "my .env variable C" + " + `); + }); + + test("secrets from .env haven't been inlined in the js output file", async () => { + const distIndexPath = `${testDir}/dist/worker/index.js`; + + const distIndexContent = fs.readFileSync(distIndexPath, "utf-8"); + expect(distIndexContent).not.toContain("my .env variable"); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/vars-changes.spec.ts b/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/vars-changes.spec.ts new file mode 100644 index 000000000000..07042b22396b --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/vars-changes.spec.ts @@ -0,0 +1,41 @@ +import * as path from "node:path"; +import { expect, test, vi } from "vitest"; +import { + getJsonResponse, + isBuild, + mockFileChange, + WAIT_FOR_OPTIONS, +} from "../../__test-utils__"; + +test.runIf(!isBuild)( + "successfully updates when a var is updated in a .env file", + async () => { + await vi.waitFor( + async () => + expect(await getJsonResponse()).toEqual({ + "variables loaded from .env": { + MY_DEV_VAR_A: "my .env variable A", + MY_DEV_VAR_B: "my .env variable B", + MY_DEV_VAR_C: "my .env variable C", + }, + }), + WAIT_FOR_OPTIONS + ); + + mockFileChange(path.join(__dirname, "../.env"), (content) => + content.replace(/my \.env/g, "my .env UPDATED") + ); + + await vi.waitFor( + async () => + expect(await getJsonResponse()).toEqual({ + "variables loaded from .env": { + MY_DEV_VAR_A: "my .env UPDATED variable A", + MY_DEV_VAR_B: "my .env UPDATED variable B", + MY_DEV_VAR_C: "my .env UPDATED variable C", // Note that unlike .dev.vars, we merge .env files + }, + }), + WAIT_FOR_OPTIONS + ); + } +); diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/with-specified-env/dot-env-loading.spec.ts b/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/with-specified-env/dot-env-loading.spec.ts new file mode 100644 index 000000000000..a1407ad8a804 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/with-specified-env/dot-env-loading.spec.ts @@ -0,0 +1,36 @@ +import fs from "node:fs"; +import { describe, expect, test } from "vitest"; +import { getJsonResponse, isBuild, testDir } from "../../../__test-utils__"; + +test("reading variables from a staging .env file", async () => { + expect(await getJsonResponse()).toEqual({ + "variables loaded from .env and .env.staging": { + MY_DEV_VAR_A: "my .env staging variable A", + MY_DEV_VAR_B: "my .env staging variable B", + MY_DEV_VAR_C: "my .env variable C", // Note that unlike .dev.vars, we merge .env files + }, + }); +}); +describe.runIf(isBuild)("build output files", () => { + test("the .dev.vars file has been created in the build directory", async () => { + const distDevVarsPath = `${testDir}/dist/worker/.dev.vars`; + const distDevVarsExists = fs.existsSync(distDevVarsPath); + expect(distDevVarsExists).toBe(true); + + const distDevVarsContent = fs.readFileSync(distDevVarsPath, "utf-8"); + expect(distDevVarsContent).toMatchInlineSnapshot(` + "ENV_NAME = "staging" + MY_DEV_VAR_A = "my .env staging variable A" + MY_DEV_VAR_B = "my .env staging variable B" + MY_DEV_VAR_C = "my .env variable C" + " + `); + }); + + test("secrets from .env haven't been inlined in the js output file", async () => { + const distIndexPath = `${testDir}/dist/worker/index.js`; + + const distIndexContent = fs.readFileSync(distIndexPath, "utf-8"); + expect(distIndexContent).not.toContain("my .env"); + }); +}); diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/with-specified-env/vars-changes.spec.ts b/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/with-specified-env/vars-changes.spec.ts new file mode 100644 index 000000000000..6512a1b234e8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/__tests__/with-specified-env/vars-changes.spec.ts @@ -0,0 +1,41 @@ +import * as path from "node:path"; +import { expect, test, vi } from "vitest"; +import { + getJsonResponse, + isBuild, + mockFileChange, + WAIT_FOR_OPTIONS, +} from "../../../__test-utils__"; + +test.runIf(!isBuild)( + "successfully updates when a var is updated in a .env.staging file", + async () => { + const originalResponseContent = { + "variables loaded from .env and .env.staging": { + MY_DEV_VAR_A: "my .env staging variable A", + MY_DEV_VAR_B: "my .env staging variable B", + MY_DEV_VAR_C: "my .env variable C", // Note that unlike .dev.vars, we merge .env files + }, + }; + const originalResponse = await getJsonResponse(); + expect(originalResponse).toEqual(originalResponseContent); + + mockFileChange(path.join(__dirname, "../../.env"), (content) => + content.replace(/my \.env/g, "my .env UPDATED") + ); + mockFileChange(path.join(__dirname, "../../.env.staging"), (content) => + content.replace(/my \.env staging/g, "my .env UPDATED staging") + ); + + await vi.waitFor(async () => { + const updatedResponse = await getJsonResponse(); + expect(updatedResponse).toEqual({ + "variables loaded from .env and .env.staging": { + MY_DEV_VAR_A: "my .env UPDATED staging variable A", + MY_DEV_VAR_B: "my .env UPDATED staging variable B", + MY_DEV_VAR_C: "my .env UPDATED variable C", // Note that unlike .dev.vars, we merge .env files + }, + }); + }, WAIT_FOR_OPTIONS); + } +); diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/package.json b/packages/vite-plugin-cloudflare/playground/dot-env/package.json new file mode 100644 index 000000000000..06bb8385c0d4 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/package.json @@ -0,0 +1,19 @@ +{ + "name": "@playground/dot-env", + "private": true, + "type": "module", + "scripts": { + "build": "vite build --app", + "check:types": "tsc --build", + "dev": "vite dev", + "preview": "vite preview" + }, + "devDependencies": { + "@cloudflare/vite-plugin": "workspace:*", + "@cloudflare/workers-tsconfig": "workspace:*", + "@cloudflare/workers-types": "^4.20250711.0", + "typescript": "catalog:default", + "vite": "catalog:vite-plugin", + "wrangler": "workspace:*" + } +} diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/src/index.ts b/packages/vite-plugin-cloudflare/playground/dot-env/src/index.ts new file mode 100644 index 000000000000..eff1f85a5cbd --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/src/index.ts @@ -0,0 +1,9 @@ +export default { + async fetch(_req, env) { + const { ENV_NAME, ...variables } = env; + const extra = ENV_NAME ? ` and .env.${ENV_NAME}` : ""; + return Response.json({ + [`variables loaded from .env${extra}`]: variables, + }); + }, +} satisfies ExportedHandler; diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/tsconfig.json b/packages/vite-plugin-cloudflare/playground/dot-env/tsconfig.json new file mode 100644 index 000000000000..b52af703bdc2 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.node.json" }, + { "path": "./tsconfig.worker.json" } + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/tsconfig.node.json b/packages/vite-plugin-cloudflare/playground/dot-env/tsconfig.node.json new file mode 100644 index 000000000000..57214ece0e56 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/base.json"], + "include": [ + "vite.config.ts", + "vite.config.with-specified-env.ts", + "__tests__" + ] +} diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/tsconfig.worker.json b/packages/vite-plugin-cloudflare/playground/dot-env/tsconfig.worker.json new file mode 100644 index 000000000000..c22c55c577c8 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/tsconfig.worker.json @@ -0,0 +1,4 @@ +{ + "extends": ["@cloudflare/workers-tsconfig/worker.json"], + "include": ["src", "worker-configuration.d.ts"] +} diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/turbo.json b/packages/vite-plugin-cloudflare/playground/dot-env/turbo.json new file mode 100644 index 000000000000..6556dcf3e5e5 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/vite.config.ts b/packages/vite-plugin-cloudflare/playground/dot-env/vite.config.ts new file mode 100644 index 000000000000..9c6b158cb564 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/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/playground/dot-env/vite.config.with-specified-env.ts b/packages/vite-plugin-cloudflare/playground/dot-env/vite.config.with-specified-env.ts new file mode 100644 index 000000000000..d998083274ad --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/vite.config.with-specified-env.ts @@ -0,0 +1,7 @@ +import { cloudflare } from "@cloudflare/vite-plugin"; +import { defineConfig } from "vite"; + +export default defineConfig({ + mode: "with-specified-env", + plugins: [cloudflare({ inspectorPort: false, persistState: false })], +}); diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/worker-configuration.d.ts b/packages/vite-plugin-cloudflare/playground/dot-env/worker-configuration.d.ts new file mode 100644 index 000000000000..913b9d5e9e99 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/worker-configuration.d.ts @@ -0,0 +1,8 @@ +// Generated by Wrangler by running `wrangler types` + +interface Env { + ENV_NAME: string; + MY_DEV_VAR_A: string; + MY_DEV_VAR_B: string; + MY_DEV_VAR_C: string; +} diff --git a/packages/vite-plugin-cloudflare/playground/dot-env/wrangler.jsonc b/packages/vite-plugin-cloudflare/playground/dot-env/wrangler.jsonc new file mode 100644 index 000000000000..22c5bdd32389 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/dot-env/wrangler.jsonc @@ -0,0 +1,8 @@ +{ + "name": "worker", + "main": "./src/index.ts", + "compatibility_date": "2024-12-30", + "env": { + "staging": {}, + }, +} diff --git a/packages/vite-plugin-cloudflare/playground/hot-channel/__tests__/hot-channel.spec.ts b/packages/vite-plugin-cloudflare/playground/hot-channel/__tests__/hot-channel.spec.ts index cd167dabedd5..6f57db94fec6 100644 --- a/packages/vite-plugin-cloudflare/playground/hot-channel/__tests__/hot-channel.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/hot-channel/__tests__/hot-channel.spec.ts @@ -1,10 +1,12 @@ import { expect, test } from "vitest"; -import { isBuild, serverLogs } from "../../__test-utils__"; +import { isBuild, page, serverLogs, viteTestUrl } from "../../__test-utils__"; test.runIf(!isBuild)("client receives custom events", async () => { + await page.goto(viteTestUrl); expect(serverLogs.info.join()).toContain("__server-event-data-received__"); }); test.runIf(!isBuild)("server receives custom events", async () => { + await page.goto(viteTestUrl); expect(serverLogs.info.join()).toContain("__client-event-data-received__"); }); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-als/als.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-als/als.spec.ts index d8b1836b206c..1b4bc730ca9a 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-als/als.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-als/als.spec.ts @@ -1,9 +1,15 @@ -import { expect, test } from "vitest"; -import { getTextResponse, serverLogs } from "../../../__test-utils__"; +import { expect, test, vi } from "vitest"; +import { + getTextResponse, + serverLogs, + WAIT_FOR_OPTIONS, +} from "../../../__test-utils__"; test("supports Node.js ALS mode", async () => { - const result = await getTextResponse(); + await vi.waitFor( + async () => expect(await getTextResponse()).toEqual("OK!"), + WAIT_FOR_OPTIONS + ); // It won't log any node.js compat warnings expect(serverLogs.warns).toEqual([]); - expect(result).toEqual("OK!"); }); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-basic/basic.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-basic/basic.spec.ts index fd059d27d15d..0ab97a243fb9 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-basic/basic.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-basic/basic.spec.ts @@ -1,7 +1,9 @@ -import { expect, test } from "vitest"; -import { getTextResponse } from "../../../__test-utils__"; +import { expect, test, vi } from "vitest"; +import { getTextResponse, WAIT_FOR_OPTIONS } from "../../../__test-utils__"; test("basic nodejs properties", async () => { - const result = await getTextResponse(); - expect(result).toBe(`"OK!"`); + await vi.waitFor( + async () => expect(await getTextResponse()).toEqual(`"OK!"`), + WAIT_FOR_OPTIONS + ); }); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-cross-env/cross-env.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-cross-env/cross-env.spec.ts index 2d8bbcb996a2..1324cc5ecd8a 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-cross-env/cross-env.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-cross-env/cross-env.spec.ts @@ -1,7 +1,9 @@ -import { expect, test } from "vitest"; -import { getTextResponse } from "../../../__test-utils__"; +import { expect, test, vi } from "vitest"; +import { getTextResponse, WAIT_FOR_OPTIONS } from "../../../__test-utils__"; test("import unenv aliased 3rd party packages (e.g. cross-env)", async () => { - const result = await getTextResponse(); - expect(result).toBe(`"OK!"`); + await vi.waitFor( + async () => expect(await getTextResponse()).toBe(`"OK!"`), + WAIT_FOR_OPTIONS + ); }); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-crypto/crypto.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-crypto/crypto.spec.ts index 0746066a9239..b97d4fdd4ed9 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-crypto/crypto.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-crypto/crypto.spec.ts @@ -1,24 +1,25 @@ -import { expect, test } from "vitest"; +import { expect, test, vi } from "vitest"; import { getTextResponse } from "../../../__test-utils__"; test("crypto.X509Certificate is implemented", async () => { - const result = await getTextResponse(); - expect(result).toMatchInlineSnapshot(` - ""OK!": -----BEGIN CERTIFICATE----- - MIICZjCCAc+gAwIBAgIUOsv8Y+x40C+gdNuu40N50KpGUhEwDQYJKoZIhvcNAQEL - BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM - GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA5MjAwOTA4MTNaFw0yNTA5 - MjAwOTA4MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw - HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEB - BQADgY0AMIGJAoGBALpJn3dUrNmZhZV02RbjZKTd5j3hpgTncF4lG4Y3sQA18k0l - 7pt6xpZuXYSFH7v2zTAxYy+uYyYwX2NZur48dZc76FSzIeuQdoTCkT0NacwFRTR5 - fEEqPvvB85ozYuyk8Bl3vSsonivOH3WftEDp9mjkHROQzS4wAZbIj7Cp+is/AgMB - AAGjUzBRMB0GA1UdDgQWBBSzFJSiPAw2tJOg8oUXrFBdqWI6zDAfBgNVHSMEGDAW - gBSzFJSiPAw2tJOg8oUXrFBdqWI6zDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 - DQEBCwUAA4GBACbto0+Ds40F7faRFFMwg5nPyh7gsiX+ZK3FYcrO3oxh5ejfzwow - DKOOje4Ncaw0rIkVpxacPyjg+wANuK2Nv/Z4CVAD3mneE4gwgRdn38q8IYN9AtSv - GzEf4UxiLBbUB6WRBgyVyquGfUMlKl/tnm4q0yeYQloYKSoHpGeHVJuN - -----END CERTIFICATE----- - " + await vi.waitFor(async () => { + expect(await getTextResponse()).toMatchInlineSnapshot(` + ""OK!": -----BEGIN CERTIFICATE----- + MIICZjCCAc+gAwIBAgIUOsv8Y+x40C+gdNuu40N50KpGUhEwDQYJKoZIhvcNAQEL + BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM + GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA5MjAwOTA4MTNaFw0yNTA5 + MjAwOTA4MTNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw + HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEB + BQADgY0AMIGJAoGBALpJn3dUrNmZhZV02RbjZKTd5j3hpgTncF4lG4Y3sQA18k0l + 7pt6xpZuXYSFH7v2zTAxYy+uYyYwX2NZur48dZc76FSzIeuQdoTCkT0NacwFRTR5 + fEEqPvvB85ozYuyk8Bl3vSsonivOH3WftEDp9mjkHROQzS4wAZbIj7Cp+is/AgMB + AAGjUzBRMB0GA1UdDgQWBBSzFJSiPAw2tJOg8oUXrFBdqWI6zDAfBgNVHSMEGDAW + gBSzFJSiPAw2tJOg8oUXrFBdqWI6zDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 + DQEBCwUAA4GBACbto0+Ds40F7faRFFMwg5nPyh7gsiX+ZK3FYcrO3oxh5ejfzwow + DKOOje4Ncaw0rIkVpxacPyjg+wANuK2Nv/Z4CVAD3mneE4gwgRdn38q8IYN9AtSv + GzEf4UxiLBbUB6WRBgyVyquGfUMlKl/tnm4q0yeYQloYKSoHpGeHVJuN + -----END CERTIFICATE----- + " `); + }); }); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-postgres/postgres.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-postgres/postgres.spec.ts index 3a9fe9dca186..8de85e4ba881 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-postgres/postgres.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-postgres/postgres.spec.ts @@ -1,9 +1,18 @@ -import { expect, test } from "vitest"; -import { getJsonResponse, getTextResponse } from "../../../__test-utils__"; +import { expect, test, vi } from "vitest"; +import { + getJsonResponse, + getTextResponse, + WAIT_FOR_OPTIONS, +} from "../../../__test-utils__"; test("should be able to create a pg Client", async () => { - const result = await getTextResponse(); - expect(result).toMatchInlineSnapshot(`"hh-pgsql-public.ebi.ac.uk"`); + await vi.waitFor( + async () => + expect(await getTextResponse()).toMatchInlineSnapshot( + `"hh-pgsql-public.ebi.ac.uk"` + ), + WAIT_FOR_OPTIONS + ); }); // Disabling actually querying the database in CI since we are getting this error: @@ -11,7 +20,12 @@ test("should be able to create a pg Client", async () => { test.runIf(!process.env.CI)( "should be able to use pg library to send a query", async () => { - const result = await getJsonResponse("/send-query"); - expect(result!.id).toEqual("21"); + await vi.waitFor( + async () => + expect(await getJsonResponse("/send-query")).toEqual( + expect.objectContaining({ id: "21" }) + ), + WAIT_FOR_OPTIONS + ); } ); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process-populated-env/process.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process-populated-env/process.spec.ts index 567ddeacd291..86a8d61733e6 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process-populated-env/process.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process-populated-env/process.spec.ts @@ -1,7 +1,9 @@ -import { expect, test } from "vitest"; -import { getTextResponse } from "../../../__test-utils__"; +import { expect, test, vi } from "vitest"; +import { getTextResponse, WAIT_FOR_OPTIONS } from "../../../__test-utils__"; test("should get a populated process.env object", async () => { - const result = await getTextResponse(); - expect(result).toBe(`OK!`); + await vi.waitFor( + async () => expect(await getTextResponse()).toBe(`OK!`), + WAIT_FOR_OPTIONS + ); }); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process/process.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process/process.spec.ts index 28405eb6cc48..ef876de3b472 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process/process.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-process/process.spec.ts @@ -1,7 +1,8 @@ -import { expect, test } from "vitest"; -import { getTextResponse } from "../../../__test-utils__"; +import { expect, test, vi } from "vitest"; +import { getTextResponse, WAIT_FOR_OPTIONS } from "../../../__test-utils__"; test("should support process global", async () => { - const result = await getTextResponse(); - expect(result).toBe(`OK!`); + await vi.waitFor(async () => { + expect(await getTextResponse()).toBe(`OK!`); + }, WAIT_FOR_OPTIONS); }); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-random/random.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-random/random.spec.ts index 4e6789d2675d..8b9d55f7b258 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-random/random.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-random/random.spec.ts @@ -1,12 +1,15 @@ -import { expect, test } from "vitest"; -import { getJsonResponse } from "../../../__test-utils__"; +import { expect, test, vi } from "vitest"; +import { getJsonResponse, WAIT_FOR_OPTIONS } from "../../../__test-utils__"; test("should be able to call `getRandomValues()` bound to any object", async () => { - const result = await getJsonResponse(); - expect(result).toEqual([ - expect.any(String), - expect.any(String), - expect.any(String), - expect.any(String), - ]); + await vi.waitFor( + async () => + expect(await getJsonResponse()).toEqual([ + expect.any(String), + expect.any(String), + expect.any(String), + expect.any(String), + ]), + WAIT_FOR_OPTIONS + ); }); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-resolve-externals/resolve-externals.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-resolve-externals/resolve-externals.spec.ts index 64fb178a7bea..7508d844b3fa 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-resolve-externals/resolve-externals.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-resolve-externals/resolve-externals.spec.ts @@ -1,11 +1,17 @@ -import { expect, test } from "vitest"; -import { getTextResponse, isBuild, serverLogs } from "../../../__test-utils__"; +import { expect, test, vi } from "vitest"; +import { + getTextResponse, + isBuild, + serverLogs, + WAIT_FOR_OPTIONS, +} from "../../../__test-utils__"; test.skipIf(isBuild)( "resolves Node.js external when calling `resolveId` directly", async () => { - const result = await getTextResponse(); - expect(result).toBe(`OK!`); - expect(serverLogs.info.join()).toContain("__node:dns__"); + await vi.waitFor(async () => { + expect(await getTextResponse()).toBe(`OK!`); + expect(serverLogs.info.join()).toContain("__node:dns__"); + }, WAIT_FOR_OPTIONS); } ); diff --git a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-warnings/warnings.spec.ts b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-warnings/warnings.spec.ts index 9e419376e4b7..17c2ac8d0ed4 100644 --- a/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-warnings/warnings.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/node-compat/__tests__/worker-warnings/warnings.spec.ts @@ -1,18 +1,20 @@ import dedent from "ts-dedent"; import { expect, test, vi } from "vitest"; -import { isBuild, serverLogs } from "../../../__test-utils__"; +import { isBuild, serverLogs, WAIT_FOR_OPTIONS } from "../../../__test-utils__"; test.skipIf(isBuild)( "should display warnings if nodejs_compat is missing", async () => { - await vi.waitFor(async () => { - expect(serverLogs.warns.join("").replaceAll("\\", "/")).toContain( - dedent` + await vi.waitFor( + async () => + expect(serverLogs.warns.join("").replaceAll("\\", "/")).toContain( + dedent` Unexpected Node.js imports for environment "worker". Do you need to enable the "nodejs_compat" compatibility flag? Refer to https://developers.cloudflare.com/workers/runtime-apis/nodejs/ for more details. - "node:assert/strict" imported from "worker-warnings/index.ts" - "perf_hooks" imported from "worker-warnings/index.ts" ` - ); - }); + ), + WAIT_FOR_OPTIONS + ); } ); diff --git a/packages/vite-plugin-cloudflare/playground/turbo.json b/packages/vite-plugin-cloudflare/playground/turbo.json new file mode 100644 index 000000000000..3fca6bdb5963 --- /dev/null +++ b/packages/vite-plugin-cloudflare/playground/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "test:ci": { + "env": ["NODE_DEBUG"] + } + } +} diff --git a/packages/vite-plugin-cloudflare/playground/vitest-setup.ts b/packages/vite-plugin-cloudflare/playground/vitest-setup.ts index cc8736bc1beb..1779b38b631d 100644 --- a/packages/vite-plugin-cloudflare/playground/vitest-setup.ts +++ b/packages/vite-plugin-cloudflare/playground/vitest-setup.ts @@ -9,7 +9,7 @@ import { preview, Rollup, } from "vite"; -import { beforeAll, beforeEach, inject } from "vitest"; +import { beforeAll, inject } from "vitest"; import type * as http from "node:http"; import type { Browser, Page } from "playwright-chromium"; import type { @@ -17,6 +17,7 @@ import type { InlineConfig, Logger, PluginOption, + PreviewServer, ResolvedConfig, UserConfig, ViteDevServer, @@ -31,8 +32,6 @@ export const isWindows = process.platform === "win32"; export const isCINonLinux = process.platform !== "linux" && process.env.CI === "true"; -let server: ViteDevServer | http.Server; - /** * Vite Dev Server when testing serve */ @@ -84,6 +83,8 @@ export function resetServerLogs() { } beforeAll(async (s) => { + let server: ViteDevServer | http.Server | PreviewServer | undefined; + const suite = s as RunnerTestFile; testPath = suite.filepath!; @@ -160,7 +161,7 @@ beforeAll(async (s) => { viteTestUrl = mod.viteTestUrl ?? viteTestUrl; } } else { - await startDefaultServe(); + server = await startDefaultServe(); } } } catch (e) { @@ -179,16 +180,10 @@ beforeAll(async (s) => { await page?.close(); await server?.close(); await watcher?.close(); - if (browser) { - await browser.close(); - } + await browser?.close(); }; }, 15_000); -beforeEach(async () => { - await page.goto(viteTestUrl); -}); - export async function loadConfig(configEnv: ConfigEnv) { let config: UserConfig | null = null; let cacheDir = "node_modules/.vite"; @@ -252,19 +247,20 @@ export async function loadConfig(configEnv: ConfigEnv) { } export async function startDefaultServe(): Promise< - ViteDevServer | http.Server + ViteDevServer | http.Server | PreviewServer > { setupConsoleWarnCollector(serverLogs.warns); if (!isBuild) { process.env.VITE_INLINE = "inline-serve"; const config = await loadConfig({ command: "serve", mode: "development" }); - viteServer = server = await (await createServer(config)).listen(); - viteTestUrl = server!.resolvedUrls!.local[0]!; - if (server.config.base === "/") { + viteServer = await (await createServer(config)).listen(); + viteTestUrl = viteServer.resolvedUrls!.local[0]!; + if (viteServer.config.base === "/") { viteTestUrl = viteTestUrl.replace(/\/$/, ""); } await page.goto(viteTestUrl); + return viteServer; } else { process.env.VITE_INLINE = "inline-build"; // determine build watch @@ -303,8 +299,8 @@ export async function startDefaultServe(): Promise< viteTestUrl = viteTestUrl.replace(/\/$/, ""); } await page.goto(viteTestUrl); + return previewServer; } - return server; } /** diff --git a/packages/vite-plugin-cloudflare/playground/vitest.config.e2e.ts b/packages/vite-plugin-cloudflare/playground/vitest.config.e2e.ts index c5268a6ca6b8..3b76e50cb23e 100644 --- a/packages/vite-plugin-cloudflare/playground/vitest.config.e2e.ts +++ b/packages/vite-plugin-cloudflare/playground/vitest.config.e2e.ts @@ -1,16 +1,18 @@ +import util from "node:util"; import { defineConfig } from "vitest/config"; +const debuglog = util.debuglog("@cloudflare:vite-plugin"); + export default defineConfig({ test: { - // We run these tests in a single fork to avoid them running in parallel. - // Otherwise we occasionally get flakes where two tests are overwriting - // the same output files. - poolOptions: { forks: { singleFork: true } }, + // We run these tests one file at a time. + // Otherwise we occasionally get flakes where two playground variants are overwriting the same files. + fileParallelism: false, include: ["./**/__tests__/**/*.spec.[tj]s"], setupFiles: ["./vitest-setup.ts"], globalSetup: ["./vitest-global-setup.ts"], reporters: "dot", - onConsoleLog: () => false, + onConsoleLog: () => debuglog.enabled, testTimeout: 10000, }, publicDir: false, diff --git a/packages/vite-plugin-cloudflare/playground/websockets/__tests__/websockets.spec.ts b/packages/vite-plugin-cloudflare/playground/websockets/__tests__/websockets.spec.ts index 9806dd880a19..d26244f5596b 100644 --- a/packages/vite-plugin-cloudflare/playground/websockets/__tests__/websockets.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/websockets/__tests__/websockets.spec.ts @@ -1,7 +1,8 @@ import { expect, test, vi } from "vitest"; -import { page } from "../../__test-utils__"; +import { page, viteTestUrl } from "../../__test-utils__"; async function openWebSocket() { + await page.goto(viteTestUrl); const openButton = page.getByRole("button", { name: "Open WebSocket" }); const statusTextBefore = await page.textContent("h2"); expect(statusTextBefore).toBe("WebSocket closed"); @@ -15,6 +16,7 @@ async function openWebSocket() { test("opens WebSocket connection", openWebSocket); test("closes WebSocket connection", async () => { + await page.goto(viteTestUrl); await openWebSocket(); const closeButton = page.getByRole("button", { name: "Close WebSocket" }); const statusTextBefore = await page.textContent("h2"); @@ -27,6 +29,7 @@ test("closes WebSocket connection", async () => { }); test("sends and receives WebSocket string messages", async () => { + await page.goto(viteTestUrl); await openWebSocket(); const sendButton = page.getByRole("button", { name: "Send string" }); const messageTextBefore = await page.textContent("p"); @@ -41,6 +44,7 @@ test("sends and receives WebSocket string messages", async () => { }); test("sends and receives WebSocket ArrayBuffer messages", async () => { + await page.goto(viteTestUrl); await openWebSocket(); const sendButton = page.getByRole("button", { name: "Send ArrayBuffer" }); const messageTextBefore = await page.textContent("p"); diff --git a/packages/vite-plugin-cloudflare/playground/worker/__tests__/worker.spec.ts b/packages/vite-plugin-cloudflare/playground/worker/__tests__/worker.spec.ts index dccdd5558650..937c00e52f05 100644 --- a/packages/vite-plugin-cloudflare/playground/worker/__tests__/worker.spec.ts +++ b/packages/vite-plugin-cloudflare/playground/worker/__tests__/worker.spec.ts @@ -7,10 +7,14 @@ import { rootDir, serverLogs, viteTestUrl, + WAIT_FOR_OPTIONS, } from "../../__test-utils__"; test("basic hello-world functionality", async () => { - expect(await getTextResponse()).toEqual("Hello World!"); + await vi.waitFor( + async () => expect(await getTextResponse()).toEqual("Hello World!"), + WAIT_FOR_OPTIONS + ); }); test("basic dev logging", async () => { @@ -21,8 +25,11 @@ test("basic dev logging", async () => { test("receives the original host as the `X-Forwarded-Host` header", async () => { const testUrl = new URL(viteTestUrl); - const response = await getTextResponse("/x-forwarded-host"); - expect(response).toBe(testUrl.host); + await vi.waitFor( + async () => + expect(await getTextResponse("/x-forwarded-host")).toEqual(testUrl.host), + WAIT_FOR_OPTIONS + ); }); test("does not cause unhandled rejection", async () => { @@ -32,12 +39,13 @@ test("does not cause unhandled rejection", async () => { test.runIf(!isBuild)( "updates using HMR code in Worker entry file", async () => { + // Touch the worker entry file to trigger a HMR update. const workerEntryPath = path.join(rootDir, "src", "index.ts"); const originalContent = fs.readFileSync(workerEntryPath, "utf-8"); fs.writeFileSync(workerEntryPath, originalContent); await vi.waitFor(() => { expect(serverLogs.info.join()).toContain("[vite] hot updated"); - }); + }, WAIT_FOR_OPTIONS); } ); diff --git a/packages/vite-plugin-cloudflare/src/cloudflare-environment.ts b/packages/vite-plugin-cloudflare/src/cloudflare-environment.ts index edaf62fe76be..09c88fc16111 100644 --- a/packages/vite-plugin-cloudflare/src/cloudflare-environment.ts +++ b/packages/vite-plugin-cloudflare/src/cloudflare-environment.ts @@ -1,4 +1,5 @@ import assert from "node:assert"; +import util from "node:util"; import * as vite from "vite"; import { isNodeCompat } from "./node-js-compat"; import { INIT_PATH, UNKNOWN_HOST, VITE_DEV_METADATA_HEADER } from "./shared"; @@ -17,6 +18,7 @@ interface WebSocketContainer { } const webSocketUndefinedError = "The WebSocket is undefined"; +const debuglog = util.debuglog("@cloudflare:vite-plugin"); function createHotChannel( webSocketContainer: WebSocketContainer @@ -88,7 +90,9 @@ export class CloudflareDevEnvironment extends vite.DevEnvironment { async initRunner( worker: ReplaceWorkersTypes, - workerConfig: WorkerConfig + workerConfig: WorkerConfig, + /** A unique identifier used for debugging errors when config updates. */ + configId: string ) { this.#worker = worker; @@ -98,6 +102,7 @@ export class CloudflareDevEnvironment extends vite.DevEnvironment { headers: { [VITE_DEV_METADATA_HEADER]: JSON.stringify({ entryPath: workerConfig.main, + configId, }), upgrade: "websocket", }, @@ -196,18 +201,21 @@ export function createCloudflareEnvironmentOptions( export function initRunners( resolvedPluginConfig: WorkersResolvedConfig, viteDevServer: vite.ViteDevServer, - miniflare: Miniflare + miniflare: Miniflare, + /** A unique identifier used for debugging errors when config updates. */ + configId: string ): Promise | undefined { return Promise.all( Object.entries(resolvedPluginConfig.workers).map( async ([environmentName, workerConfig]) => { + debuglog(configId, "Initializing worker:", workerConfig.name); const worker = await miniflare.getWorker(workerConfig.name); return ( viteDevServer.environments[ environmentName ] as CloudflareDevEnvironment - ).initRunner(worker, workerConfig); + ).initRunner(worker, workerConfig, configId); } ) ); diff --git a/packages/vite-plugin-cloudflare/src/dev-vars.ts b/packages/vite-plugin-cloudflare/src/dev-vars.ts index dcb3237ee5ce..0dd4e4cfaca2 100644 --- a/packages/vite-plugin-cloudflare/src/dev-vars.ts +++ b/packages/vite-plugin-cloudflare/src/dev-vars.ts @@ -1,58 +1,56 @@ -import * as fs from "node:fs"; import * as path from "node:path"; +import { unstable_getVarsForDev } from "wrangler"; import type { AssetsOnlyResolvedConfig, WorkersResolvedConfig, } from "./plugin-config"; /** - * Gets the content of the `.dev.vars` target file + * Gets any variables with which to augment the Worker config in preview mode. * - * Note: This resolves the .dev.vars file path following the same logic - * as `loadDotEnv` in `/packages/wrangler/src/config/index.ts` - * the two need to be kept in sync + * Calls `unstable_getVarsForDev` with the current Cloudflare environment to get local dev variables from the `.dev.vars` and `.env` files. */ -export function getDotDevDotVarsContent( - configPath: string, +export function getLocalDevVarsForPreview( + configPath: string | undefined, cloudflareEnv: string | undefined -) { - const configDir = path.dirname(configPath); - - const defaultDotDevDotVarsPath = `${configDir}/.dev.vars`; - const inputDotDevDotVarsPath = `${defaultDotDevDotVarsPath}${cloudflareEnv ? `.${cloudflareEnv}` : ""}`; - - const targetPath = fs.existsSync(inputDotDevDotVarsPath) - ? inputDotDevDotVarsPath - : fs.existsSync(defaultDotDevDotVarsPath) - ? defaultDotDevDotVarsPath - : null; - - if (targetPath) { - const dotDevDotVarsContent = fs.readFileSync(targetPath); +): string | undefined { + const dotDevDotVars = 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. + cloudflareEnv + ); + const dotDevDotVarsEntries = Array.from(Object.entries(dotDevDotVars)); + if (dotDevDotVarsEntries.length > 0) { + const dotDevDotVarsContent = dotDevDotVarsEntries + .map(([key, value]) => { + return `${key} = "${value?.toString().replaceAll(`"`, `\\"`)}"\n`; + }) + .join(""); return dotDevDotVarsContent; } - - return null; } /** - * Returns `true` if the `changedFile` matches a `.dev.vars` file. + * Returns `true` if the `changedFile` matches a `.dev.vars` or `.env` file. */ -export function hasDotDevDotVarsFileChanged( - resolvedPluginConfig: AssetsOnlyResolvedConfig | WorkersResolvedConfig, +export function hasLocalDevVarsFileChanged( + { + configPaths, + cloudflareEnv, + }: AssetsOnlyResolvedConfig | WorkersResolvedConfig, changedFilePath: string ) { - return [...resolvedPluginConfig.configPaths].some((configPath) => { - const dotDevDotVars = path.join(path.dirname(configPath), ".dev.vars"); - if (dotDevDotVars === changedFilePath) { - return true; - } - - if (resolvedPluginConfig.cloudflareEnv) { - const dotDevDotVarsForEnv = `${dotDevDotVars}.${resolvedPluginConfig.cloudflareEnv}`; - return dotDevDotVarsForEnv === changedFilePath; - } - - return false; + return [...configPaths].some((configPath) => { + const configDir = path.dirname(configPath); + return [ + ".dev.vars", + ".env", + ...(cloudflareEnv + ? [`.dev.vars.${cloudflareEnv}`, `.env.${cloudflareEnv}`] + : []), + ].some( + (localDevFile) => changedFilePath === path.join(configDir, localDevFile) + ); }); } diff --git a/packages/vite-plugin-cloudflare/src/index.ts b/packages/vite-plugin-cloudflare/src/index.ts index 384aff6cf505..f3fce71f2672 100644 --- a/packages/vite-plugin-cloudflare/src/index.ts +++ b/packages/vite-plugin-cloudflare/src/index.ts @@ -1,6 +1,8 @@ import assert from "node:assert"; +import { randomUUID } from "node:crypto"; import * as fsp from "node:fs/promises"; import * as path from "node:path"; +import util from "node:util"; import { generateContainerBuildId, getContainerIdsByImageTags, @@ -41,8 +43,8 @@ import { } from "./debugging"; import { writeDeployConfig } from "./deploy-config"; import { - getDotDevDotVarsContent, - hasDotDevDotVarsFileChanged, + getLocalDevVarsForPreview, + hasLocalDevVarsFileChanged, } from "./dev-vars"; import { getDevMiniflareOptions, @@ -83,9 +85,10 @@ import type { Unstable_RawConfig } from "wrangler"; export type { PluginConfig } from "./plugin-config"; +const debuglog = util.debuglog("@cloudflare:vite-plugin"); + // this flag is used to show the workers configs warning only once let workersConfigsWarningShown = false; - let miniflare: Miniflare | undefined; /** @@ -104,6 +107,9 @@ export function cloudflare(pluginConfig: PluginConfig = {}): vite.Plugin[] { let containerImageTagsSeen: Set | undefined; let runningContainerIds: Array; + /** Used to track whether hooks are being called because of a server restart or a server close event. */ + let restartingServer = false; + return [ { name: "vite-plugin-cloudflare", @@ -274,17 +280,16 @@ if (import.meta.hot) { config = workerConfig; if (workerConfig.configPath) { - const dotDevDotVarsContent = getDotDevDotVarsContent( + const localDevVars = getLocalDevVarsForPreview( workerConfig.configPath, resolvedPluginConfig.cloudflareEnv ); - // Save a .dev.vars file to the worker's build output directory - // when it exists so that it will be then detected by `vite preview` - if (dotDevDotVarsContent) { + // Save a .dev.vars file to the worker's build output directory if there are local dev vars, so that it will be then detected by `vite preview`. + if (localDevVars) { this.emitFile({ type: "asset", fileName: ".dev.vars", - source: dotDevDotVarsContent, + source: localDevVars, }); } } @@ -336,37 +341,70 @@ if (import.meta.hot) { writeDeployConfig(resolvedPluginConfig, resolvedViteConfig); } }, - hotUpdate(options) { - assertIsNotPreview(resolvedPluginConfig); - - // Note that we must "resolve" the changed file since the path from Vite will not match Windows backslashes. - const changedFilePath = path.resolve(options.file); - - if ( - resolvedPluginConfig.configPaths.has(changedFilePath) || - hasDotDevDotVarsFileChanged(resolvedPluginConfig, changedFilePath) || - hasAssetsConfigChanged( - resolvedPluginConfig, - resolvedViteConfig, - changedFilePath - ) - ) { - // It's OK for this to be called multiple times as Vite prevents concurrent execution - options.server.restart(); - return []; - } - }, // Vite `configureServer` Hook // see https://vite.dev/guide/api-plugin.html#configureserver async configureServer(viteDevServer) { + // Patch the `server.restart` method to track whether the server is restarting or not. + const restartServer = viteDevServer.restart.bind(viteDevServer); + viteDevServer.restart = async () => { + try { + restartingServer = true; + debuglog(configId, "From server.restart(): Restarting server..."); + await restartServer(); + debuglog(configId, "From server.restart(): Restarted server..."); + } finally { + restartingServer = false; + } + }; + assertIsNotPreview(resolvedPluginConfig); + // It is possible to get into a situation where the dev server is restarted by a config file change + // right in the middle of the Vite server and the supporting Workers being initialized. + // We use an abort controller to signal to the initialization code that it should stop if the config has changed. + const restartAbortController = new AbortController(); + + // We use a `configId` to help debug how the config changes are triggering the restarts. + const configId = randomUUID(); + const inputInspectorPort = await getInputInspectorPortOption( resolvedPluginConfig, viteDevServer, miniflare ); + const configChangedHandler = async (changedFilePath: string) => { + assertIsNotPreview(resolvedPluginConfig); + + if ( + resolvedPluginConfig.configPaths.has(changedFilePath) || + hasLocalDevVarsFileChanged(resolvedPluginConfig, changedFilePath) || + hasAssetsConfigChanged( + resolvedPluginConfig, + resolvedViteConfig, + changedFilePath + ) + ) { + debuglog(configId, "Config changed: " + changedFilePath); + viteDevServer.watcher.off("change", configChangedHandler); + if (!restartAbortController.signal.aborted) { + debuglog( + configId, + "Restarting dev server and aborting previous setup" + ); + restartAbortController.abort(); + await viteDevServer.watcher.close(); + await viteDevServer.restart(); + } else { + debuglog( + configId, + "Config changed but already aborted previous setup, ignoring." + ); + } + } + }; + viteDevServer.watcher.on("change", configChangedHandler); + let containerBuildId: string | undefined; const entryWorkerConfig = getEntryWorkerConfig(resolvedPluginConfig); const hasDevContainers = @@ -387,18 +425,58 @@ if (import.meta.hot) { containerBuildId, }); + if (restartAbortController.signal.aborted) { + debuglog( + configId, + "Aborting setting up miniflare because config has changed." + ); + // The config has changed while we were still trying to setup the server, + // so just abort and allow the new server to be set up instead. + return; + } + + debuglog( + configId, + new Error("").stack?.includes("restartServer") + ? "From stack trace: restarting server..." + : "From stack trace: creating new server..." + ); + if (!miniflare) { + debuglog(configId, "Creating new Miniflare instance"); miniflare = new Miniflare(miniflareDevOptions); } else { + debuglog(configId, "Waiting for Miniflare to be ready before update"); + await miniflare.ready; + debuglog(configId, "Updating the Miniflare instance"); await miniflare.setOptions(miniflareDevOptions); + debuglog(configId, "Waiting for Miniflare to be ready after update"); + await miniflare.ready; + debuglog(configId, "Miniflare is ready"); } let preMiddleware: vite.Connect.NextHandleFunction | undefined; + if (restartAbortController.signal.aborted) { + debuglog( + configId, + "Aborting setting up the dev server because config has changed." + ); + // The config has changes while this was still trying to setup the server. + // So just abort and allow the new server to be set up. + return; + } + if (resolvedPluginConfig.type === "workers") { assert(entryWorkerConfig, `No entry Worker config`); - await initRunners(resolvedPluginConfig, viteDevServer, miniflare); + debuglog(configId, "Initializing the Vite module runners"); + await initRunners( + resolvedPluginConfig, + viteDevServer, + miniflare, + configId + ); const entryWorkerName = entryWorkerConfig.name; @@ -653,6 +731,15 @@ if (import.meta.hot) { containerImageTagsSeen.clear(); runningContainerIds = []; } + + debuglog("buildEnd:", restartingServer ? "restarted" : "disposing"); + if (!restartingServer) { + debuglog("buildEnd: disposing Miniflare instance"); + await miniflare?.dispose().catch((error) => { + debuglog("buildEnd: failed to dispose Miniflare instance:", error); + }); + miniflare = undefined; + } }, }, // Plugin to provide a fallback entry file diff --git a/packages/vite-plugin-cloudflare/src/runner-worker/index.ts b/packages/vite-plugin-cloudflare/src/runner-worker/index.ts index 01ef3db57178..c47c88e8baf9 100644 --- a/packages/vite-plugin-cloudflare/src/runner-worker/index.ts +++ b/packages/vite-plugin-cloudflare/src/runner-worker/index.ts @@ -132,6 +132,8 @@ export function createWorkerEntrypointWrapper( entrypoint: string ): WorkerEntrypointConstructor { class Wrapper extends WorkerEntrypoint { + /** A unique identifier used for debugging errors when config updates. */ + configId?: string; constructor(ctx: ExecutionContext, env: WrapperEnv) { super(ctx, env); @@ -176,7 +178,11 @@ export function createWorkerEntrypointWrapper( entryPath = viteDevMetadata.entryPath; const { 0: client, 1: server } = new WebSocketPair(); webSocket = client; - await createModuleRunner(this.env, server); + await createModuleRunner( + this.env, + server, + viteDevMetadata.configId + ); } catch (e) { return new Response( e instanceof Error ? e.message : JSON.stringify(e), @@ -406,7 +412,7 @@ function getViteDevMetadata(request: Request) { ); } - const { entryPath } = parsedViteDevMetadataHeader; + const { entryPath, configId } = parsedViteDevMetadataHeader; if (entryPath === undefined) { throw new Error( @@ -414,5 +420,5 @@ function getViteDevMetadata(request: Request) { ); } - return { entryPath }; + return { entryPath, configId }; } diff --git a/packages/vite-plugin-cloudflare/src/runner-worker/module-runner.ts b/packages/vite-plugin-cloudflare/src/runner-worker/module-runner.ts index e15e9e342861..ec99da0c3e44 100644 --- a/packages/vite-plugin-cloudflare/src/runner-worker/module-runner.ts +++ b/packages/vite-plugin-cloudflare/src/runner-worker/module-runner.ts @@ -7,13 +7,21 @@ import { stripInternalEnv } from "./env"; import type { WrapperEnv } from "./env"; let moduleRunner: ModuleRunner; +let oldConfigId: string | undefined; export async function createModuleRunner( env: WrapperEnv, - webSocket: WebSocket + webSocket: WebSocket, + /** A unique identifier used for debugging errors when config updates. */ + configId?: string ) { if (moduleRunner) { - throw new Error("Runner already initialized"); + throw new Error( + "Runner already initialized; old configId: " + + oldConfigId + + ", new configId: " + + configId + ); } const transport = createWebSocketModuleRunnerTransport({ @@ -24,6 +32,7 @@ export async function createModuleRunner( }, }); + oldConfigId = configId; moduleRunner = new ModuleRunner( { sourcemapInterceptor: "prepareStackTrace", diff --git a/packages/wrangler/e2e/dev.test.ts b/packages/wrangler/e2e/dev.test.ts index 012cbdd80d1f..a7fe635c809f 100644 --- a/packages/wrangler/e2e/dev.test.ts +++ b/packages/wrangler/e2e/dev.test.ts @@ -2294,3 +2294,317 @@ This is a random email body. `); }); }); + +describe(".env support in local dev", () => { + const seedFiles = { + "wrangler.jsonc": JSON.stringify({ + name: workerName, + main: "src/index.ts", + compatibility_date: "2025-07-01", + vars: { + WRANGLER_ENV_VAR_0: "default-0", + WRANGLER_ENV_VAR_1: "default-1", + WRANGLER_ENV_VAR_2: "default-2", + WRANGLER_ENV_VAR_3: "default-3", + }, + }), + "src/index.ts": dedent` + export default { + fetch(request, env) { + return new Response(JSON.stringify(env, null, 2)); + } + } + `, + }; + + it("should load environment variables from .env file", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + }); + + const worker = helper.runLongLived("wrangler dev"); + const { url } = await worker.waitForReady(); + expect(await (await fetch(url)).text()).toMatchInlineSnapshot(` + "{ + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "env-1", + "WRANGLER_ENV_VAR_2": "env-2", + "WRANGLER_ENV_VAR_3": "default-3" + }" + `); + }); + + it("should not load local dev variables from .env files if there is a .dev.vars file", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + }); + await helper.seed({ + ".dev.vars": dedent` + WRANGLER_ENV_VAR_1=dev-vars-1 + WRANGLER_ENV_VAR_2=dev-vars-2 + `, + }); + + const worker = helper.runLongLived("wrangler dev"); + const { url } = await worker.waitForReady(); + expect(await (await fetch(url)).text()).toMatchInlineSnapshot(` + "{ + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "dev-vars-1", + "WRANGLER_ENV_VAR_2": "dev-vars-2", + "WRANGLER_ENV_VAR_3": "default-3" + }" + `); + }); + + it("should not load dev variables from .env files if CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV is set to false", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + }); + + const worker = helper.runLongLived("wrangler dev", { + env: { CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV: "false" }, + }); + const { url } = await worker.waitForReady(); + expect(await (await fetch(url)).text()).toMatchInlineSnapshot(` + "{ + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "default-1", + "WRANGLER_ENV_VAR_2": "default-2", + "WRANGLER_ENV_VAR_3": "default-3" + }" + `); + }); + + it("should load environment variables from .env.staging if it exists and --env=staging", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env.staging": dedent` + WRANGLER_ENV_VAR_2=staging-2 + WRANGLER_ENV_VAR_3=staging-3 + `, + }); + + const worker = helper.runLongLived("wrangler dev --env=staging"); + const { url } = await worker.waitForReady(); + expect(await (await fetch(url)).text()).toMatchInlineSnapshot(` + "{ + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "default-1", + "WRANGLER_ENV_VAR_2": "staging-2", + "WRANGLER_ENV_VAR_3": "staging-3" + }" + `); + }); + + it("should prefer to load environment variables from .env.staging over .env, if --env=staging", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + }); + await helper.seed({ + ".env.staging": dedent` + WRANGLER_ENV_VAR_2=staging-2 + WRANGLER_ENV_VAR_3=staging-3 + `, + }); + + const worker = helper.runLongLived("wrangler dev --env=staging"); + const { url } = await worker.waitForReady(); + expect(await (await fetch(url)).text()).toMatchInlineSnapshot(` + "{ + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "env-1", + "WRANGLER_ENV_VAR_2": "staging-2", + "WRANGLER_ENV_VAR_3": "staging-3" + }" + `); + }); + + it("should load environment variables from .env file if --env=xxx and .env.xxx does not exist", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + }); + await helper.seed({ + ".env.staging": dedent` + WRANGLER_ENV_VAR_2=staging-2 + WRANGLER_ENV_VAR_3=staging-3 + `, + }); + + const worker = helper.runLongLived("wrangler dev --env=xxx"); + const { url } = await worker.waitForReady(); + expect(await (await fetch(url)).text()).toMatchInlineSnapshot(` + "{ + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "env-1", + "WRANGLER_ENV_VAR_2": "env-2", + "WRANGLER_ENV_VAR_3": "default-3" + }" + `); + }); + + it("should prefer to load environment variables from .env.local over .env", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + }); + await helper.seed({ + ".env.local": dedent` + WRANGLER_ENV_VAR_2=local-2 + WRANGLER_ENV_VAR_3=local-3 + `, + }); + + const worker = helper.runLongLived("wrangler dev"); + const { url } = await worker.waitForReady(); + expect(await (await fetch(url)).text()).toMatchInlineSnapshot(` + "{ + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "env-1", + "WRANGLER_ENV_VAR_2": "local-2", + "WRANGLER_ENV_VAR_3": "local-3" + }" + `); + }); + + it("should prefer to load environment variables from .env.staging.local over .env.staging, etc", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + }); + await helper.seed({ + ".env.local": dedent` + WRANGLER_ENV_VAR_2=local-2 + WRANGLER_ENV_VAR_3=local-3 + `, + }); + await helper.seed({ + ".env.staging": dedent` + WRANGLER_ENV_VAR_3=staging-3 + WRANGLER_ENV_VAR_4=staging-4 + `, + }); + await helper.seed({ + ".env.staging.local": dedent` + WRANGLER_ENV_VAR_4=staging-local-4 + WRANGLER_ENV_VAR_5=staging-local-5 + `, + }); + + const worker = helper.runLongLived("wrangler dev --env=staging"); + const { url } = await worker.waitForReady(); + expect(await (await fetch(url)).text()).toMatchInlineSnapshot(` + "{ + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "env-1", + "WRANGLER_ENV_VAR_2": "local-2", + "WRANGLER_ENV_VAR_3": "staging-3", + "WRANGLER_ENV_VAR_4": "staging-local-4", + "WRANGLER_ENV_VAR_5": "staging-local-5" + }" + `); + }); + + it("should load environment variables from process.env if CLOUDFLARE_INCLUDE_PROCESS_ENV is true", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + }); + + const worker = helper.runLongLived("wrangler dev", { + env: { CLOUDFLARE_INCLUDE_PROCESS_ENV: "true", ...process.env }, + }); + const { url } = await worker.waitForReady(); + // We could dump out all the bindings but that would be a lot of noise, and also may change between OSes and runs. + // Instead, we know that the `CLOUDFLARE_INCLUDE_PROCESS_ENV` variable should be present, so we just check for that. + expect(await (await fetch(url)).text()).contains( + '"CLOUDFLARE_INCLUDE_PROCESS_ENV": "true"' + ); + expect(await (await fetch(url)).text()).contains( + '"WRANGLER_ENV_VAR_0": "default-0"' + ); + expect(await (await fetch(url)).text()).contains( + '"WRANGLER_ENV_VAR_1": "env-1"' + ); + }); + + it("should load environment variables from the .env files pointed to by `--env-file`", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seedFiles); + await helper.seed({ + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + }); + await helper.seed({ + ".env.local": dedent` + WRANGLER_ENV_VAR_2=local-2 + WRANGLER_ENV_VAR_3=local-3 + `, + }); + await helper.seed({ + "other/.env": dedent` + WRANGLER_ENV_VAR_1=other-env-1 + WRANGLER_ENV_VAR_2=other-env-2 + `, + }); + await helper.seed({ + "other/.env.local": dedent` + WRANGLER_ENV_VAR_2=other-local-2 + WRANGLER_ENV_VAR_3=other-local-3 + `, + }); + + const worker = helper.runLongLived( + "wrangler dev --env-file=other/.env --env-file=other/.env.local" + ); + const { url } = await worker.waitForReady(); + expect(await (await fetch(url)).text()).toMatchInlineSnapshot(` + "{ + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "other-env-1", + "WRANGLER_ENV_VAR_2": "other-local-2", + "WRANGLER_ENV_VAR_3": "other-local-3" + }" + `); + }); +}); diff --git a/packages/wrangler/e2e/startWorker.test.ts b/packages/wrangler/e2e/startWorker.test.ts index db6a32a2fdd4..ca341cfeed0b 100644 --- a/packages/wrangler/e2e/startWorker.test.ts +++ b/packages/wrangler/e2e/startWorker.test.ts @@ -44,7 +44,7 @@ function collectMessagesContaining( return collection; } -describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { +describe("DevEnv", () => { let helper: WranglerE2ETestHelper; let wrangler: Wrangler; let startWorker: Wrangler["unstable_startWorker"]; @@ -54,10 +54,11 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { startWorker = wrangler.unstable_startWorker; }); - it("ProxyWorker buffers requests while runtime reloads", async (t) => { - t.onTestFinished(() => worker?.dispose()); + describe.each(OPTIONS)("(remote: $remote)", ({ remote }) => { + it("ProxyWorker buffers requests while runtime reloads", async (t) => { + t.onTestFinished(() => worker?.dispose()); - const script = dedent` + const script = dedent` export default { fetch() { return new Response("body:1"); @@ -65,32 +66,32 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { } `; - await helper.seed({ - "src/index.ts": script, - }); + await helper.seed({ + "src/index.ts": script, + }); - const worker = await startWorker({ - entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + const worker = await startWorker({ + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), - dev: { remote }, - }); + dev: { remote }, + }); - let res = await worker.fetch("http://dummy"); - await expect(res.text()).resolves.toBe("body:1"); + let res = await worker.fetch("http://dummy"); + await expect(res.text()).resolves.toBe("body:1"); - await helper.seed({ - "src/index.ts": script.replace("body:1", "body:2"), - }); - await setTimeout(300); + await helper.seed({ + "src/index.ts": script.replace("body:1", "body:2"), + }); + await setTimeout(300); - res = await worker.fetch("http://dummy"); - await expect(res.text()).resolves.toBe("body:2"); - }); + res = await worker.fetch("http://dummy"); + await expect(res.text()).resolves.toBe("body:2"); + }); - it("InspectorProxyWorker discovery endpoints + devtools websocket connection", async (t) => { - t.onTestFinished(() => worker?.dispose()); + it("InspectorProxyWorker discovery endpoints + devtools websocket connection", async (t) => { + t.onTestFinished(() => worker?.dispose()); - const script = dedent` + const script = dedent` export default { fetch() { console.log('Inside mock user worker'); @@ -100,130 +101,132 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { } `; - await helper.seed({ - "src/index.ts": script, - }); + await helper.seed({ + "src/index.ts": script, + }); - const worker = await startWorker({ - name: "test-worker", - entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + const worker = await startWorker({ + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), - dev: { remote }, - }); + dev: { remote }, + }); - const inspectorUrl = await worker.inspectorUrl; - assert(inspectorUrl, "missing inspectorUrl"); - const res = await undici.fetch(`http://${inspectorUrl.host}/json`); + const inspectorUrl = await worker.inspectorUrl; + assert(inspectorUrl, "missing inspectorUrl"); + const res = await undici.fetch(`http://${inspectorUrl.host}/json`); - await expect(res.json()).resolves.toBeInstanceOf(Array); + await expect(res.json()).resolves.toBeInstanceOf(Array); - assert(inspectorUrl, "missing inspectorUrl"); - const ws = new WebSocket(inspectorUrl.href); - const openPromise = events.once(ws, "open"); + assert(inspectorUrl, "missing inspectorUrl"); + const ws = new WebSocket(inspectorUrl.href); + const openPromise = events.once(ws, "open"); - const consoleApiMessages: DevToolsEvent<"Runtime.consoleAPICalled">[] = - collectMessagesContaining(ws, "Runtime.consoleAPICalled"); - const executionContextCreatedPromise = waitForMessageContaining( - ws, - "Runtime.executionContextCreated" - ); + const consoleApiMessages: DevToolsEvent<"Runtime.consoleAPICalled">[] = + collectMessagesContaining(ws, "Runtime.consoleAPICalled"); + const executionContextCreatedPromise = waitForMessageContaining( + ws, + "Runtime.executionContextCreated" + ); - await openPromise; - await worker.fetch("http://dummy"); + await openPromise; + await worker.fetch("http://dummy"); - await expect(executionContextCreatedPromise).resolves.toMatchObject({ - method: "Runtime.executionContextCreated", - params: { - context: { id: expect.any(Number) }, - }, - }); - await vi.waitFor( - () => { - expect(consoleApiMessages).toContainMatchingObject({ - method: "Runtime.consoleAPICalled", - params: expect.objectContaining({ - args: [{ type: "string", value: "Inside mock user worker" }], - }), - }); - }, - { timeout: 5_000 } - ); - - // Ensure execution contexts cleared on reload - const executionContextClearedPromise = waitForMessageContaining( - ws, - "Runtime.executionContextsCleared" - ); - await helper.seed({ - "src/index.ts": script.replace("body:1", "body:2"), - }); - await setTimeout(300); + await expect(executionContextCreatedPromise).resolves.toMatchObject({ + method: "Runtime.executionContextCreated", + params: { + context: { id: expect.any(Number) }, + }, + }); + await vi.waitFor( + () => { + expect(consoleApiMessages).toContainMatchingObject({ + method: "Runtime.consoleAPICalled", + params: expect.objectContaining({ + args: [{ type: "string", value: "Inside mock user worker" }], + }), + }); + }, + { timeout: 5_000 } + ); - await executionContextClearedPromise; - }); + // Ensure execution contexts cleared on reload + const executionContextClearedPromise = waitForMessageContaining( + ws, + "Runtime.executionContextsCleared" + ); + await helper.seed({ + "src/index.ts": script.replace("body:1", "body:2"), + }); + await setTimeout(300); - it("InspectorProxyWorker rejects unauthorised requests", async (t) => { - t.onTestFinished(() => worker?.dispose()); + await executionContextClearedPromise; + }); - await helper.seed({ - "src/index.ts": dedent` + it("InspectorProxyWorker rejects unauthorised requests", async (t) => { + t.onTestFinished(() => worker?.dispose()); + + await helper.seed({ + "src/index.ts": dedent` export default { fetch() { return new Response("body:1"); } } `, - }); - - const worker = await startWorker({ - name: "test-worker", - entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + }); - dev: { remote }, - }); + const worker = await startWorker({ + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), - const inspectorUrl = await worker.inspectorUrl; - assert(inspectorUrl); + dev: { remote }, + }); - assert(inspectorUrl, "missing inspectorUrl"); - let ws = new WebSocket(inspectorUrl.href, { - setHost: false, - headers: { Host: "example.com" }, - }); + const inspectorUrl = await worker.inspectorUrl; + assert(inspectorUrl); - let openPromise = events.once(ws, "open"); - await expect(openPromise).rejects.toThrow("Unexpected server response"); + assert(inspectorUrl, "missing inspectorUrl"); + let ws = new WebSocket(inspectorUrl.href, { + setHost: false, + headers: { Host: "example.com" }, + }); - // Check validates `Origin` header - assert(inspectorUrl, "missing inspectorUrl"); - ws = new WebSocket(inspectorUrl.href, { origin: "https://example.com" }); - openPromise = events.once(ws, "open"); - await expect(openPromise).rejects.toThrow("Unexpected server response"); - ws.close(); - }); + let openPromise = events.once(ws, "open"); + await expect(openPromise).rejects.toThrow("Unexpected server response"); - // Regression test for https://github.com/cloudflare/workers-sdk/issues/5297 - // The runtime inspector can send messages larger than 1MB limit websocket message permitted by UserWorkers. - // In the real-world, this is encountered when debugging large source files (source maps) - // or inspecting a variable that serializes to a large string. - // Connecting devtools directly to the inspector would work fine, but we proxy the inspector messages - // through a worker (InspectorProxyWorker) which hits the limit (without the fix, compatibilityFlags:["increase_websocket_message_size"]) - // By logging a large string we can verify that the inspector messages are being proxied successfully. - it("InspectorProxyWorker can proxy messages > 1MB", async (t) => { - const consoleInfoSpy = vi - .spyOn(console, "info") - .mockImplementation(() => {}); - const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {}); - - t.onTestFinished(() => { - consoleInfoSpy.mockRestore(); - consoleLogSpy.mockRestore(); - return worker?.dispose(); + // Check validates `Origin` header + assert(inspectorUrl, "missing inspectorUrl"); + ws = new WebSocket(inspectorUrl.href, { origin: "https://example.com" }); + openPromise = events.once(ws, "open"); + await expect(openPromise).rejects.toThrow("Unexpected server response"); + ws.close(); }); - const LARGE_STRING = "This is a large string" + "z".repeat(2 ** 20); + // Regression test for https://github.com/cloudflare/workers-sdk/issues/5297 + // The runtime inspector can send messages larger than 1MB limit websocket message permitted by UserWorkers. + // In the real-world, this is encountered when debugging large source files (source maps) + // or inspecting a variable that serializes to a large string. + // Connecting devtools directly to the inspector would work fine, but we proxy the inspector messages + // through a worker (InspectorProxyWorker) which hits the limit (without the fix, compatibilityFlags:["increase_websocket_message_size"]) + // By logging a large string we can verify that the inspector messages are being proxied successfully. + it("InspectorProxyWorker can proxy messages > 1MB", async (t) => { + const consoleInfoSpy = vi + .spyOn(console, "info") + .mockImplementation(() => {}); + const consoleLogSpy = vi + .spyOn(console, "log") + .mockImplementation(() => {}); + + t.onTestFinished(() => { + consoleInfoSpy.mockRestore(); + consoleLogSpy.mockRestore(); + return worker?.dispose(); + }); + + const LARGE_STRING = "This is a large string" + "z".repeat(2 ** 20); - const script = dedent` + const script = dedent` export default { fetch() { console.log("${LARGE_STRING}"); @@ -233,177 +236,96 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { } `; - await helper.seed({ - "src/index.ts": script, - }); - - const worker = await startWorker({ - name: "test-worker", - entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), - - dev: { remote }, - }); - - const inspectorUrl = await worker.inspectorUrl; - assert(inspectorUrl, "missing inspectorUrl"); - const ws = new WebSocket(inspectorUrl.href); - - const consoleApiMessages: DevToolsEvent<"Runtime.consoleAPICalled">[] = - collectMessagesContaining(ws, "Runtime.consoleAPICalled"); - - await worker.fetch("http://dummy"); - - await vi.waitFor( - () => { - expect(consoleApiMessages).toContainMatchingObject({ - method: "Runtime.consoleAPICalled", - params: expect.objectContaining({ - args: [{ type: "string", value: LARGE_STRING }], - }), - }); - }, - { timeout: 5_000 } - ); - }); - - // local only: miniflare catches error responses and pretty-prints them - it.skipIf(remote)("User worker exception", async (t) => { - t.onTestFinished(() => worker?.dispose()); - - await helper.seed({ - "src/index.ts": dedent` - export default { - fetch() { - throw new Error('Boom!'); - } - } - `, - }); - - const worker = await startWorker({ - name: "test-worker", - entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), - - dev: { remote }, - }); - - await expect(worker.fetch("http://dummy")).rejects.toThrowError("Boom!"); + await helper.seed({ + "src/index.ts": script, + }); - await helper.seed({ - "src/index.ts": dedent` - export default { - fetch() { - throw new Error('Boom 2!'); - } - } - `, - }); - await setTimeout(300); + const worker = await startWorker({ + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), - await expect(worker.fetch("http://dummy")).rejects.toThrowError("Boom 2!"); + dev: { remote }, + }); - // test eyeball requests receive the pretty error page - await helper.seed({ - "src/index.ts": dedent` - export default { - fetch() { - const e = new Error('Boom 3!'); + const inspectorUrl = await worker.inspectorUrl; + assert(inspectorUrl, "missing inspectorUrl"); + const ws = new WebSocket(inspectorUrl.href); - // this is how errors are serialised after they are caught by wrangler/miniflare3 middlewares - const error = { name: e.name, message: e.message, stack: e.stack }; - return Response.json(error, { - status: 500, - headers: { "MF-Experimental-Error-Stack": "true" }, - }); - } - } - `, - }); - await setTimeout(300); + const consoleApiMessages: DevToolsEvent<"Runtime.consoleAPICalled">[] = + collectMessagesContaining(ws, "Runtime.consoleAPICalled"); - const undiciRes = await undici.fetch(await worker.url, { - headers: { Accept: "text/html" }, - }); - await expect(undiciRes.text()).resolves.toEqual( - expect.stringContaining(`Boom 3!`) // pretty error page html snippet - ); + await worker.fetch("http://dummy"); - // test further changes that fix the code - await helper.seed({ - "src/index.ts": dedent` - export default { - fetch() { - return new Response("body:3"); - } - } - `, + await vi.waitFor( + () => { + expect(consoleApiMessages).toContainMatchingObject({ + method: "Runtime.consoleAPICalled", + params: expect.objectContaining({ + args: [{ type: "string", value: LARGE_STRING }], + }), + }); + }, + { timeout: 5_000 } + ); }); - await setTimeout(300); - - let res = await worker.fetch("http://dummy"); - await expect(res.text()).resolves.toBe("body:3"); - - res = await worker.fetch("http://dummy"); - await expect(res.text()).resolves.toBe("body:3"); - }); - it("config.dev.{server,inspector} changes, restart the server instance", async (t) => { - t.onTestFinished(() => worker?.dispose()); + it("config.dev.{server,inspector} changes, restart the server instance", async (t) => { + t.onTestFinished(() => worker?.dispose()); - await helper.seed({ - "src/index.ts": dedent` + await helper.seed({ + "src/index.ts": dedent` export default { fetch() { return new Response("body:1"); } } `, - }); + }); - const worker = await startWorker({ - name: "test-worker", - entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + const worker = await startWorker({ + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), - dev: { - remote, - server: { port: await getPort() }, - inspector: false, - }, - }); + dev: { + remote, + server: { port: await getPort() }, + inspector: false, + }, + }); - let res = await worker.fetch("http://dummy"); - await expect(res.text()).resolves.toBe("body:1"); + let res = await worker.fetch("http://dummy"); + await expect(res.text()).resolves.toBe("body:1"); - const oldPort = worker.config.dev?.server?.port; - let undiciRes = await undici.fetch(`http://127.0.0.1:${oldPort}`); - await expect(undiciRes.text()).resolves.toBe("body:1"); + const oldPort = worker.config.dev?.server?.port; + let undiciRes = await undici.fetch(`http://127.0.0.1:${oldPort}`); + await expect(undiciRes.text()).resolves.toBe("body:1"); - await worker.patchConfig({ - dev: { - ...worker.config.dev, - remote, - server: { port: await getPort() }, - inspector: false, - }, - }); - const newPort = worker.config.dev?.server?.port; + await worker.patchConfig({ + dev: { + ...worker.config.dev, + remote, + server: { port: await getPort() }, + inspector: false, + }, + }); + const newPort = worker.config.dev?.server?.port; - res = await worker.fetch("http://dummy"); - await expect(res.text()).resolves.toBe("body:1"); + res = await worker.fetch("http://dummy"); + await expect(res.text()).resolves.toBe("body:1"); - undiciRes = await undici.fetch(`http://127.0.0.1:${newPort}`); - await expect(undiciRes.text()).resolves.toBe("body:1"); + undiciRes = await undici.fetch(`http://127.0.0.1:${newPort}`); + await expect(undiciRes.text()).resolves.toBe("body:1"); - await expect( - undici.fetch(`http://127.0.0.1:${oldPort}`) - ).rejects.toThrowError("fetch failed"); - }); + await expect( + undici.fetch(`http://127.0.0.1:${oldPort}`) + ).rejects.toThrowError("fetch failed"); + }); - it("liveReload", async (t) => { - t.onTestFinished(() => worker?.dispose()); + it("liveReload", async (t) => { + t.onTestFinished(() => worker?.dispose()); - await helper.seed({ - "src/index.ts": dedent` + await helper.seed({ + "src/index.ts": dedent` export default { fetch() { return new Response("body:1", { @@ -412,28 +334,28 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { } } `, - }); + }); - const worker = await startWorker({ - name: "test-worker", - entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + const worker = await startWorker({ + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), - dev: { - remote, - liveReload: true, - }, - }); + dev: { + remote, + liveReload: true, + }, + }); - const scriptRegex = - / `); - await helper.seed({ - "src/index.ts": dedent` + await helper.seed({ + "src/index.ts": dedent` export default { fetch() { return new Response("body:2"); } } `, - }); - await setTimeout(300); + }); + await setTimeout(300); - // test liveReload does nothing when the response Content-Type is not html - res = await worker.fetch("http://dummy"); - resText = await res.text(); - expect(resText).toBe("body:2"); - expect(resText).not.toEqual(expect.stringMatching(scriptRegex)); + // test liveReload does nothing when the response Content-Type is not html + res = await worker.fetch("http://dummy"); + resText = await res.text(); + expect(resText).toBe("body:2"); + expect(resText).not.toEqual(expect.stringMatching(scriptRegex)); - await helper.seed({ - "src/index.ts": dedent` + await helper.seed({ + "src/index.ts": dedent` export default { fetch() { return new Response("body:3", { @@ -481,25 +403,104 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { } } `, - }); - await worker.patchConfig({ - dev: { - ...worker.config.dev, - liveReload: false, - }, - }); + }); + await worker.patchConfig({ + dev: { + ...worker.config.dev, + liveReload: false, + }, + }); - // test liveReload: false does nothing even when the response Content-Type is html - res = await worker.fetch("http://dummy"); - resText = await res.text(); - expect(resText).toBe("body:3"); - expect(resText).not.toEqual(expect.stringMatching(scriptRegex)); + // test liveReload: false does nothing even when the response Content-Type is html + res = await worker.fetch("http://dummy"); + resText = await res.text(); + expect(resText).toBe("body:3"); + expect(resText).not.toEqual(expect.stringMatching(scriptRegex)); + }); }); - // local only: origin overrides cannot be applied in remote mode - it.skipIf(remote)( - "origin override takes effect in the UserWorker", - async (t) => { + describe("DevEnv (local-only)", () => { + it("User worker exception", async (t) => { + t.onTestFinished(() => worker?.dispose()); + + await helper.seed({ + "src/index.ts": dedent` + export default { + fetch() { + throw new Error('Boom!'); + } + } + `, + }); + + const worker = await startWorker({ + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + }); + + await expect(worker.fetch("http://dummy")).rejects.toThrowError("Boom!"); + + await helper.seed({ + "src/index.ts": dedent` + export default { + fetch() { + throw new Error('Boom 2!'); + } + } + `, + }); + await setTimeout(300); + + await expect(worker.fetch("http://dummy")).rejects.toThrowError( + "Boom 2!" + ); + + // test eyeball requests receive the pretty error page + await helper.seed({ + "src/index.ts": dedent` + export default { + fetch() { + const e = new Error('Boom 3!'); + + // this is how errors are serialised after they are caught by wrangler/miniflare3 middlewares + const error = { name: e.name, message: e.message, stack: e.stack }; + return Response.json(error, { + status: 500, + headers: { "MF-Experimental-Error-Stack": "true" }, + }); + } + } + `, + }); + await setTimeout(300); + + const undiciRes = await undici.fetch(await worker.url, { + headers: { Accept: "text/html" }, + }); + await expect(undiciRes.text()).resolves.toEqual( + expect.stringContaining(`Boom 3!`) // pretty error page html snippet + ); + + // test further changes that fix the code + await helper.seed({ + "src/index.ts": dedent` + export default { + fetch() { + return new Response("body:3"); + } + } + `, + }); + await setTimeout(300); + + let res = await worker.fetch("http://dummy"); + await expect(res.text()).resolves.toBe("body:3"); + + res = await worker.fetch("http://dummy"); + await expect(res.text()).resolves.toBe("body:3"); + }); + + it("origin override takes effect in the UserWorker", async (t) => { t.onTestFinished(() => worker?.dispose()); await helper.seed({ @@ -517,7 +518,6 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), dev: { - remote, origin: { hostname: "www.google.com", }, @@ -543,13 +543,9 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { await expect(res.text()).resolves.toBe( "URL: https://mybank.co.uk/test/path/2" ); - } - ); + }); - // local only: remote workers are not terminated during reloads - it.skipIf(remote)( - "inflight requests are retried during UserWorker reloads", - async (t) => { + it("inflight requests are retried during UserWorker reloads", async (t) => { // to simulate inflight requests failing during UserWorker reloads, // we will use a UserWorker with a longish `await setTimeout(...)` // so that we can guarantee the race condition is hit when workerd is eventually terminated @@ -578,8 +574,6 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { const worker = await startWorker({ name: "test-worker", entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), - - dev: { remote }, }); let res = await worker.fetch("http://dummy/short"); @@ -605,6 +599,218 @@ describe.each(OPTIONS)("DevEnv (remote: $remote)", ({ remote }) => { res = await inflightDuringReloads; await expect(res.text()).resolves.toBe("UserWorker:3"); - } - ); + }); + + it("vars from .env (next to config file) override vars from Wrangler config file", async (t) => { + t.onTestFinished(() => worker?.dispose()); + await helper.seed({ + "src/index.ts": dedent` + export default { + fetch(request, env) { + return Response.json(env); + } + } + `, + "wrangler.jsonc": JSON.stringify({ + vars: { + WRANGLER_ENV_VAR_0: "default-0", + WRANGLER_ENV_VAR_1: "default-1", + WRANGLER_ENV_VAR_2: "default-2", + WRANGLER_ENV_VAR_3: "default-3", + }, + }), + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + ".env.local": dedent` + WRANGLER_ENV_VAR_2=local-2 + WRANGLER_ENV_VAR_3=local-3 + `, + ".env.staging": dedent` + WRANGLER_ENV_VAR_3=staging-3 + WRANGLER_ENV_VAR_4=staging-4 + `, + ".env.staging.local": dedent` + WRANGLER_ENV_VAR_4=staging-local-4 + WRANGLER_ENV_VAR_5=staging-local-5 + `, + }); + + const worker = await startWorker({ + config: path.resolve(helper.tmpPath, "wrangler.jsonc"), + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + }); + + const res = await worker.fetch("http://dummy/test/path/1"); + expect(await res.json()).toMatchInlineSnapshot(` + { + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "env-1", + "WRANGLER_ENV_VAR_2": "local-2", + "WRANGLER_ENV_VAR_3": "local-3", + } + `); + }); + + it("vars are not loaded from .env if there is a .dev.vars file (next to config file)", async (t) => { + t.onTestFinished(() => worker?.dispose()); + await helper.seed({ + "src/index.ts": dedent` + export default { + fetch(request, env) { + return Response.json(env); + } + } + `, + "wrangler.jsonc": JSON.stringify({ + vars: { + WRANGLER_ENV_VAR_0: "default-0", + WRANGLER_ENV_VAR_1: "default-1", + WRANGLER_ENV_VAR_2: "default-2", + WRANGLER_ENV_VAR_3: "default-3", + }, + }), + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + ".dev.vars": dedent` + WRANGLER_ENV_VAR_2=dev-vars-2 + WRANGLER_ENV_VAR_3=dev-vars-3 + `, + }); + + const worker = await startWorker({ + config: path.resolve(helper.tmpPath, "wrangler.jsonc"), + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + }); + + const res = await worker.fetch("http://dummy/test/path/1"); + expect(await res.json()).toMatchInlineSnapshot(` + { + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "default-1", + "WRANGLER_ENV_VAR_2": "dev-vars-2", + "WRANGLER_ENV_VAR_3": "dev-vars-3", + } + `); + }); + + it("vars from inline config override vars from both .env and config file", async (t) => { + t.onTestFinished(() => worker?.dispose()); + await helper.seed({ + "src/index.ts": dedent` + export default { + fetch(request, env) { + return Response.json(env); + } + } + `, + "wrangler.jsonc": JSON.stringify({ + vars: { + WRANGLER_ENV_VAR_0: "default-0", + WRANGLER_ENV_VAR_1: "default-1", + WRANGLER_ENV_VAR_2: "default-2", + WRANGLER_ENV_VAR_3: "default-3", + }, + }), + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + ".env.local": dedent` + WRANGLER_ENV_VAR_2=local-2 + WRANGLER_ENV_VAR_3=local-3 + `, + ".env.staging": dedent` + WRANGLER_ENV_VAR_3=staging-3 + WRANGLER_ENV_VAR_4=staging-4 + `, + ".env.staging.local": dedent` + WRANGLER_ENV_VAR_4=staging-local-4 + WRANGLER_ENV_VAR_5=staging-local-5 + `, + }); + + const worker = await startWorker({ + config: path.resolve(helper.tmpPath, "wrangler.jsonc"), + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + bindings: { + WRANGLER_ENV_VAR_3: { type: "plain_text", value: "inline-3" }, + WRANGLER_ENV_VAR_4: { type: "plain_text", value: "inline-4" }, + }, + }); + + const res = await worker.fetch("http://dummy/test/path/1"); + expect(await res.json()).toMatchInlineSnapshot(` + { + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "env-1", + "WRANGLER_ENV_VAR_2": "local-2", + "WRANGLER_ENV_VAR_3": "inline-3", + "WRANGLER_ENV_VAR_4": "inline-4", + } + `); + }); + + it("vars from .env pointed at by `envFile` override vars from Wrangler config file and .env files local to the config file", async (t) => { + t.onTestFinished(() => worker?.dispose()); + await helper.seed({ + "src/index.ts": dedent` + export default { + fetch(request, env) { + return Response.json(env); + } + } + `, + "wrangler.jsonc": JSON.stringify({ + vars: { + WRANGLER_ENV_VAR_0: "default-0", + WRANGLER_ENV_VAR_1: "default-1", + WRANGLER_ENV_VAR_2: "default-2", + WRANGLER_ENV_VAR_3: "default-3", + }, + }), + ".env": dedent` + WRANGLER_ENV_VAR_1=env-1 + WRANGLER_ENV_VAR_2=env-2 + `, + ".env.local": dedent` + WRANGLER_ENV_VAR_2=local-2 + WRANGLER_ENV_VAR_3=local-3 + `, + "other/.env": dedent` + WRANGLER_ENV_VAR_3=other-3 + WRANGLER_ENV_VAR_4=other-4 + `, + "other/.env.local": dedent` + WRANGLER_ENV_VAR_4=other-local-4 + WRANGLER_ENV_VAR_5=other-local-5 + `, + }); + + const worker = await startWorker({ + config: path.resolve(helper.tmpPath, "wrangler.jsonc"), + name: "test-worker", + entrypoint: path.resolve(helper.tmpPath, "src/index.ts"), + envFiles: ["other/.env", "other/.env.local"], + }); + + const res = await worker.fetch("http://dummy/test/path/1"); + expect(await res.json()).toMatchInlineSnapshot(` + { + "WRANGLER_ENV_VAR_0": "default-0", + "WRANGLER_ENV_VAR_1": "default-1", + "WRANGLER_ENV_VAR_2": "default-2", + "WRANGLER_ENV_VAR_3": "other-3", + "WRANGLER_ENV_VAR_4": "other-local-4", + "WRANGLER_ENV_VAR_5": "other-local-5", + } + `); + }); + }); }); diff --git a/packages/wrangler/e2e/types.test.ts b/packages/wrangler/e2e/types.test.ts index 7c0d4582eed6..09e5cb2e3c77 100644 --- a/packages/wrangler/e2e/types.test.ts +++ b/packages/wrangler/e2e/types.test.ts @@ -188,4 +188,33 @@ describe("types", () => { await helper.seed(seed); await worker.readUntil(/❓ Your types might be out of date./); }); + + it("should read .env files for secret env vars", async () => { + const helper = new WranglerE2ETestHelper(); + await helper.seed(seed); + await helper.seed({ + ".env": dedent` + MY_VAR=secret-value + `, + }); + const output = await helper.run(`wrangler types --include-runtime=false`); + expect(output.stdout).not.toContain("Generating runtime types..."); + const file = readFileSync( + path.join(helper.tmpPath, "./worker-configuration.d.ts"), + "utf8" + ); + expect(file).toMatchInlineSnapshot(` + "/* eslint-disable */ + // Generated by Wrangler by running \`wrangler types --include-runtime=false\` (hash: e5830092ae2a4ea3789f0d1c1fc31ff8) + declare namespace Cloudflare { + interface Env { + BEEP: "BOOP"; + ASDf: "ADSfadsf"; + MY_VAR: string; + } + } + interface Env extends Cloudflare.Env {} + " + `); + }); }); diff --git a/packages/wrangler/package.json b/packages/wrangler/package.json index ee0792f6d027..be424d0948b1 100644 --- a/packages/wrangler/package.json +++ b/packages/wrangler/package.json @@ -116,6 +116,7 @@ "date-fns": "^4.1.0", "devtools-protocol": "^0.0.1182435", "dotenv": "^16.3.1", + "dotenv-expand": "^12.0.2", "execa": "^6.1.0", "find-up": "^6.3.0", "get-port": "^7.0.0", diff --git a/packages/wrangler/src/__tests__/ai.test.ts b/packages/wrangler/src/__tests__/ai.test.ts index b082a23af7ae..17f270b9b0d6 100644 --- a/packages/wrangler/src/__tests__/ai.test.ts +++ b/packages/wrangler/src/__tests__/ai.test.ts @@ -26,11 +26,12 @@ describe("ai help", () => { wrangler ai finetune Interact with finetune files GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -55,11 +56,12 @@ describe("ai help", () => { wrangler ai finetune Interact with finetune files GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); }); diff --git a/packages/wrangler/src/__tests__/cert.test.ts b/packages/wrangler/src/__tests__/cert.test.ts index ee54cba2795f..702e1b269776 100644 --- a/packages/wrangler/src/__tests__/cert.test.ts +++ b/packages/wrangler/src/__tests__/cert.test.ts @@ -484,11 +484,12 @@ describe("wrangler", () => { wrangler cert delete Delete an mTLS certificate GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); }); diff --git a/packages/wrangler/src/__tests__/cloudchamber/create.test.ts b/packages/wrangler/src/__tests__/cloudchamber/create.test.ts index 7ff776c5cd60..7029c59a3c9d 100644 --- a/packages/wrangler/src/__tests__/cloudchamber/create.test.ts +++ b/packages/wrangler/src/__tests__/cloudchamber/create.test.ts @@ -105,11 +105,12 @@ describe("cloudchamber create", () => { Create a new deployment GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --image Image to use for your deployment [string] diff --git a/packages/wrangler/src/__tests__/cloudchamber/curl.test.ts b/packages/wrangler/src/__tests__/cloudchamber/curl.test.ts index a401912a4a80..3969c54e122c 100644 --- a/packages/wrangler/src/__tests__/cloudchamber/curl.test.ts +++ b/packages/wrangler/src/__tests__/cloudchamber/curl.test.ts @@ -39,11 +39,12 @@ describe("cloudchamber curl", () => { path [string] [required] [default: \\"/\\"] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS -H, --header Add headers in the form of --header : [array] diff --git a/packages/wrangler/src/__tests__/cloudchamber/delete.test.ts b/packages/wrangler/src/__tests__/cloudchamber/delete.test.ts index 4048b970394d..9eaffeacdb1f 100644 --- a/packages/wrangler/src/__tests__/cloudchamber/delete.test.ts +++ b/packages/wrangler/src/__tests__/cloudchamber/delete.test.ts @@ -35,11 +35,12 @@ describe("cloudchamber delete", () => { deploymentId deployment you want to delete [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); diff --git a/packages/wrangler/src/__tests__/cloudchamber/images.test.ts b/packages/wrangler/src/__tests__/cloudchamber/images.test.ts index 0fc9608ef115..c36692afcaf2 100644 --- a/packages/wrangler/src/__tests__/cloudchamber/images.test.ts +++ b/packages/wrangler/src/__tests__/cloudchamber/images.test.ts @@ -42,11 +42,12 @@ describe("cloudchamber image", () => { wrangler cloudchamber registries list list registries configured for this account GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -188,11 +189,12 @@ describe("cloudchamber image list", () => { List images in the Cloudflare managed registry GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --filter Regex to filter results [string] diff --git a/packages/wrangler/src/__tests__/cloudchamber/list.test.ts b/packages/wrangler/src/__tests__/cloudchamber/list.test.ts index d9eaae850229..38761fee5d8d 100644 --- a/packages/wrangler/src/__tests__/cloudchamber/list.test.ts +++ b/packages/wrangler/src/__tests__/cloudchamber/list.test.ts @@ -36,11 +36,12 @@ describe("cloudchamber list", () => { This means that 'list' will only showcase deployments that contain this ID prefix [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --location Filter deployments by location [string] diff --git a/packages/wrangler/src/__tests__/cloudchamber/modify.test.ts b/packages/wrangler/src/__tests__/cloudchamber/modify.test.ts index 0722f2c068fd..ff0a745a571d 100644 --- a/packages/wrangler/src/__tests__/cloudchamber/modify.test.ts +++ b/packages/wrangler/src/__tests__/cloudchamber/modify.test.ts @@ -73,11 +73,12 @@ describe("cloudchamber modify", () => { deploymentId The deployment you want to modify [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --var Container environment variables [array] diff --git a/packages/wrangler/src/__tests__/containers/delete.test.ts b/packages/wrangler/src/__tests__/containers/delete.test.ts index ac2b752abcfd..63ace1eed61c 100644 --- a/packages/wrangler/src/__tests__/containers/delete.test.ts +++ b/packages/wrangler/src/__tests__/containers/delete.test.ts @@ -34,11 +34,12 @@ describe("containers delete", () => { ID id of the containers to delete [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); diff --git a/packages/wrangler/src/__tests__/containers/info.test.ts b/packages/wrangler/src/__tests__/containers/info.test.ts index 12111d872ee6..330c7e9e1c35 100644 --- a/packages/wrangler/src/__tests__/containers/info.test.ts +++ b/packages/wrangler/src/__tests__/containers/info.test.ts @@ -35,11 +35,12 @@ describe("containers info", () => { ID id of the containers to view [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); diff --git a/packages/wrangler/src/__tests__/containers/list.test.ts b/packages/wrangler/src/__tests__/containers/list.test.ts index db5b822d9479..d2ba943228de 100644 --- a/packages/wrangler/src/__tests__/containers/list.test.ts +++ b/packages/wrangler/src/__tests__/containers/list.test.ts @@ -30,11 +30,12 @@ describe("containers list", () => { List containers GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); diff --git a/packages/wrangler/src/__tests__/containers/push.test.ts b/packages/wrangler/src/__tests__/containers/push.test.ts index 54d1d08a4615..bbc60f04fd3e 100644 --- a/packages/wrangler/src/__tests__/containers/push.test.ts +++ b/packages/wrangler/src/__tests__/containers/push.test.ts @@ -38,11 +38,12 @@ describe("containers push", () => { TAG [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --path-to-docker Path to your docker binary if it's not on $PATH [string] [default: \\"docker\\"]" diff --git a/packages/wrangler/src/__tests__/d1/d1.test.ts b/packages/wrangler/src/__tests__/d1/d1.test.ts index 7c800d656b67..67873c458088 100644 --- a/packages/wrangler/src/__tests__/d1/d1.test.ts +++ b/packages/wrangler/src/__tests__/d1/d1.test.ts @@ -28,11 +28,12 @@ describe("d1", () => { wrangler d1 migrations Interact with D1 migrations GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -64,11 +65,12 @@ describe("d1", () => { wrangler d1 migrations Interact with D1 migrations GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -87,11 +89,12 @@ describe("d1", () => { wrangler d1 migrations apply Apply D1 migrations GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -108,11 +111,12 @@ describe("d1", () => { wrangler d1 time-travel restore Restore a database back to a specific point-in-time GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); }); diff --git a/packages/wrangler/src/__tests__/deployments.test.ts b/packages/wrangler/src/__tests__/deployments.test.ts index db1f16d82ba0..0a50835b3ca9 100644 --- a/packages/wrangler/src/__tests__/deployments.test.ts +++ b/packages/wrangler/src/__tests__/deployments.test.ts @@ -60,11 +60,12 @@ describe("deployments", () => { wrangler deployments status View the current state of your production GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); diff --git a/packages/wrangler/src/__tests__/dev.test.ts b/packages/wrangler/src/__tests__/dev.test.ts index 373d4eae9ad9..b41373492799 100644 --- a/packages/wrangler/src/__tests__/dev.test.ts +++ b/packages/wrangler/src/__tests__/dev.test.ts @@ -89,31 +89,29 @@ async function expectedHostAndZone( configPath: config.config, }); - expect(ctx).toEqual( - expect.objectContaining({ - workerContext: { - host, - zone, - routes: config.triggers - ?.filter( - (trigger): trigger is Extract => - trigger.type === "route" - ) - .map((trigger) => { - const { type: _, ...route } = trigger; - if ( - "custom_domain" in route || - "zone_id" in route || - "zone_name" in route - ) { - return route; - } else { - return route.pattern; - } - }), - }, - }) - ); + expect(ctx).toMatchObject({ + workerContext: { + host, + zone, + routes: config.triggers + ?.filter( + (trigger): trigger is Extract => + trigger.type === "route" + ) + .map((trigger) => { + const { type: _, ...route } = trigger; + if ( + "custom_domain" in route || + "zone_id" in route || + "zone_name" in route + ) { + return route; + } else { + return route.pattern; + } + }), + }, + }); return config; } @@ -958,44 +956,167 @@ describe.sequential("wrangler dev", () => { afterEach(() => (process.env = processEnv)); beforeEach(() => { - fs.writeFileSync(".env", "CUSTOM_BUILD_VAR=default"); - fs.writeFileSync(".env.custom", "CUSTOM_BUILD_VAR=custom"); + fs.writeFileSync( + ".env", + dedent` + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=default-1 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=default-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=default-3 + ` + ); + fs.writeFileSync( + ".env.local", + dedent` + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=default-local-1 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_LOCAL=default-local + ` + ); + fs.writeFileSync( + ".env.custom", + dedent` + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=custom-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=custom-3 + ` + ); + fs.writeFileSync( + ".env.custom.local", + dedent` + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=custom-local-1 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=custom-local-3 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_LOCAL=custom-local + ` + ); + fs.writeFileSync( + "build.js", + dedent` + const customFields = Object.entries(process.env).filter(([key]) => key.startsWith('__DOT_ENV_TEST_CUSTOM_BUILD_VAR')); + console.log(customFields.map(([key, value]) => key + "=" + value).join('\\n')); + ` + ); fs.writeFileSync("index.js", `export default {};`); writeWranglerConfig({ main: "index.js", - env: { custom: {} }, - build: { - // Ideally, we'd just log the var here and match it in `std.out`, - // but stdout from custom builds is piped directly to - // `process.stdout` which we don't capture. - command: `node -e "require('fs').writeFileSync('var.txt', process.env.CUSTOM_BUILD_VAR)"`, - }, + env: { custom: {}, noEnv: {} }, + build: { command: `node ./build.js` }, }); - - // We won't overwrite existing process.env keys with .env values (to - // allow .env overrides to be specified on the shell), so make sure this - // key definitely doesn't exist. - vi.stubEnv("CUSTOM_BUILD_VAR", ""); - delete process.env.CUSTOM_BUILD_VAR; }); - it("should load environment variables from `.env`", async () => { + function extractCustomBuildLogs(stdout: string) { + return stdout + .split("\n") + .filter((line) => line.startsWith("[custom build]")) + .map((line) => line.replace(/\[custom build\]( |$)/, "")) + .sort() + .join("\n"); + } + + it("should pass environment variables from `.env` to custom builds", async () => { await runWranglerUntilConfig("dev"); - const output = fs.readFileSync("var.txt", "utf8"); - expect(output).toMatch("default"); + expect(extractCustomBuildLogs(std.out)).toMatchInlineSnapshot(` + " + Running: node ./build.js + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=default-local-1 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=default-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=default-3 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_LOCAL=default-local" + `); }); + it("should prefer to load environment variables from `.env.` if `--env ` is set", async () => { await runWranglerUntilConfig("dev --env custom"); - const output = fs.readFileSync("var.txt", "utf8"); - expect(output).toMatch("custom"); + expect(extractCustomBuildLogs(std.out)).toMatchInlineSnapshot(` + " + Running: node ./build.js + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=custom-local-1 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=custom-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=custom-local-3 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_LOCAL=custom-local" + `); }); - it("should show reasonable debug output if `.env` does not exist", async () => { - fs.rmSync(".env"); - writeWranglerConfig({ - main: "index.js", - }); - await runWranglerUntilConfig("dev --log-level debug"); - expect(std.debug).toContain(".env file not found at"); + + it("should use default `.env` if `.env.` does not exist", async () => { + await runWranglerUntilConfig("dev --env=noEnv"); + expect(extractCustomBuildLogs(std.out)).toMatchInlineSnapshot(` + " + Running: node ./build.js + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=default-local-1 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=default-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=default-3 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_LOCAL=default-local" + `); + }); + + it("should not override environment variables already on process.env", async () => { + vi.stubEnv("__DOT_ENV_TEST_CUSTOM_BUILD_VAR_1", "process-env"); + await runWranglerUntilConfig("dev"); + expect(extractCustomBuildLogs(std.out)).toMatchInlineSnapshot(` + " + Running: node ./build.js + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=process-env + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=default-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=default-3 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_LOCAL=default-local" + `); + }); + + it("should prefer to load environment variables from a custom path `.env` if `--env-file` is set", async () => { + fs.mkdirSync("other", { recursive: true }); + fs.writeFileSync( + "other/.env", + dedent` + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=other-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=other-3 + ` + ); + + // This file will not be loaded because `--env-file` is set for it. + fs.writeFileSync( + "other/.env.local", + dedent` + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=other-local-1 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=other-local-3 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_LOCAL=other-local + ` + ); + + await runWranglerUntilConfig("dev --env-file other/.env"); + expect(extractCustomBuildLogs(std.out)).toMatchInlineSnapshot(` + " + Running: node ./build.js + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=other-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=other-3" + `); + }); + + it("should prefer to load environment variables from a custom path `.env` if multiple `--env-file` is set", async () => { + fs.mkdirSync("other", { recursive: true }); + fs.writeFileSync( + "other/.env", + dedent` + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=other-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=other-3 + ` + ); + fs.writeFileSync( + "other/.env.local", + dedent` + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=other-local-1 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=other-local-3 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_LOCAL=other-local + ` + ); + + await runWranglerUntilConfig( + "dev --env-file other/.env --env-file other/.env.local" + ); + expect(extractCustomBuildLogs(std.out)).toMatchInlineSnapshot(` + " + Running: node ./build.js + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_1=other-local-1 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_2=other-2 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_3=other-local-3 + __DOT_ENV_TEST_CUSTOM_BUILD_VAR_LOCAL=other-local" + `); }); }); }); @@ -1470,6 +1591,214 @@ describe.sequential("wrangler dev", () => { }); }); + describe(".env in local dev", () => { + const processEnv = process.env; + beforeEach(() => (process.env = { ...processEnv })); + afterEach(() => (process.env = processEnv)); + + beforeEach(() => { + fs.writeFileSync( + ".env", + dedent` + __DOT_ENV_LOCAL_DEV_VAR_1=default-1 + __DOT_ENV_LOCAL_DEV_VAR_2=default-2 + __DOT_ENV_LOCAL_DEV_VAR_3=default-3 + ` + ); + fs.writeFileSync( + ".env.local", + dedent` + __DOT_ENV_LOCAL_DEV_VAR_1=default-local-1 + __DOT_ENV_LOCAL_DEV_VAR_LOCAL=default-local + ` + ); + fs.writeFileSync( + ".env.custom", + dedent` + __DOT_ENV_LOCAL_DEV_VAR_2=custom-2 + __DOT_ENV_LOCAL_DEV_VAR_3=custom-3 + ` + ); + fs.writeFileSync( + ".env.custom.local", + dedent` + __DOT_ENV_LOCAL_DEV_VAR_1=custom-local-1 + __DOT_ENV_LOCAL_DEV_VAR_3=custom-local-3 + __DOT_ENV_LOCAL_DEV_VAR_LOCAL=custom-local + ` + ); + fs.writeFileSync("index.js", `export default {};`); + writeWranglerConfig({ + main: "index.js", + }); + }); + + function extractUsingVars(stdout: string) { + return stdout + .split("\n") + .filter((line) => line.startsWith("Using vars")) + .sort() + .join("\n"); + } + + function extractBindings(stdout: string) { + return stdout + .split("\n") + .filter((line) => line.startsWith("env.")) + .sort() + .join("\n"); + } + + it("should get local dev `vars` from `.env`", async () => { + await runWranglerUntilConfig("dev"); + expect(extractUsingVars(std.out)).toMatchInlineSnapshot(` + "Using vars defined in .env + Using vars defined in .env.local" + `); + expect(extractBindings(std.out)).toMatchInlineSnapshot(` + "env.__DOT_ENV_LOCAL_DEV_VAR_1 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_2 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_3 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_LOCAL (\\"(hidden)\\") Environment Variable local" + `); + }); + + it("should not load local dev `vars` from `.env` if there is a `.dev.vars` file", async () => { + fs.writeFileSync( + ".dev.vars", + dedent` + __DOT_DEV_DOT_VARS_LOCAL_DEV_VAR_1=dot-dev-var-1 + __DOT_DEV_DOT_VARS_LOCAL_DEV_VAR_2=dot-dev-var-2 + ` + ); + await runWranglerUntilConfig("dev"); + expect(extractUsingVars(std.out)).toMatchInlineSnapshot(` + "Using vars defined in .dev.vars" + `); + expect(extractBindings(std.out)).toMatchInlineSnapshot(` + "env.__DOT_DEV_DOT_VARS_LOCAL_DEV_VAR_1 (\\"(hidden)\\") Environment Variable local + env.__DOT_DEV_DOT_VARS_LOCAL_DEV_VAR_2 (\\"(hidden)\\") Environment Variable local" + `); + }); + + it("should not load local dev `vars` from `.env` if CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV is set to false", async () => { + await runWranglerUntilConfig("dev", { + CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV: "false", + }); + expect(extractUsingVars(std.out)).toMatchInlineSnapshot(`""`); + expect(extractBindings(std.out)).toMatchInlineSnapshot(`""`); + }); + + it("should get local dev `vars` from appropriate `.env.` files when --env= is set", async () => { + await runWranglerUntilConfig("dev --env custom"); + expect(extractUsingVars(std.out)).toMatchInlineSnapshot(` + "Using vars defined in .env + Using vars defined in .env.custom + Using vars defined in .env.custom.local + Using vars defined in .env.local" + `); + expect(extractBindings(std.out)).toMatchInlineSnapshot(` + "env.__DOT_ENV_LOCAL_DEV_VAR_1 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_2 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_3 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_LOCAL (\\"(hidden)\\") Environment Variable local" + `); + }); + + it("should get local dev vars from appropriate `.env` files when --env= is set but no .env. file exists", async () => { + await runWranglerUntilConfig("dev --env noEnv"); + expect(extractUsingVars(std.out)).toMatchInlineSnapshot(` + "Using vars defined in .env + Using vars defined in .env.local" + `); + expect(extractBindings(std.out)).toMatchInlineSnapshot(` + "env.__DOT_ENV_LOCAL_DEV_VAR_1 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_2 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_3 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_LOCAL (\\"(hidden)\\") Environment Variable local" + `); + }); + + it("should get local dev `vars` from `process.env` when `CLOUDFLARE_INCLUDE_PROCESS_ENV` is true", async () => { + await runWranglerUntilConfig("dev --env custom", { + CLOUDFLARE_INCLUDE_PROCESS_ENV: "true", + }); + expect(extractUsingVars(std.out)).toMatchInlineSnapshot(` + "Using vars defined in .env + Using vars defined in .env.custom + Using vars defined in .env.custom.local + Using vars defined in .env.local + Using vars defined in process.env" + `); + // We could dump out all the bindings but that would be a lot of noise, and also may change between OSes and runs. + // Instead, we know that the `CLOUDFLARE_INCLUDE_PROCESS_ENV` variable should be present, so we just check for that. + expect(extractBindings(std.out)).contains( + 'env.CLOUDFLARE_INCLUDE_PROCESS_ENV ("(hidden)")' + ); + }); + + it("should get local dev `vars` from appropriate `.env.` files when --env-file is set", async () => { + fs.mkdirSync("other", { recursive: true }); + fs.writeFileSync( + "other/.env", + dedent` + __DOT_ENV_LOCAL_DEV_VAR_2=custom-2 + __DOT_ENV_LOCAL_DEV_VAR_3=custom-3 + ` + ); + fs.writeFileSync( + "other/.env.local", + dedent` + __DOT_ENV_LOCAL_DEV_VAR_1=custom-local-1 + __DOT_ENV_LOCAL_DEV_VAR_3=custom-local-3 + __DOT_ENV_LOCAL_DEV_VAR_LOCAL=custom-local + ` + ); + + await runWranglerUntilConfig("dev --env-file=other/.env"); + expect(extractUsingVars(std.out)).toMatchInlineSnapshot( + `"Using vars defined in other/.env"` + ); + expect(extractBindings(std.out)).toMatchInlineSnapshot(` + "env.__DOT_ENV_LOCAL_DEV_VAR_2 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_3 (\\"(hidden)\\") Environment Variable local" + `); + }); + + it("should get local dev `vars` from appropriate `.env.` files when multiple --env-file options are set", async () => { + fs.mkdirSync("other", { recursive: true }); + fs.writeFileSync( + "other/.env", + dedent` + __DOT_ENV_LOCAL_DEV_VAR_2=custom-2 + __DOT_ENV_LOCAL_DEV_VAR_3=custom-3 + ` + ); + fs.writeFileSync( + "other/.env.local", + dedent` + __DOT_ENV_LOCAL_DEV_VAR_1=custom-local-1 + __DOT_ENV_LOCAL_DEV_VAR_3=custom-local-3 + __DOT_ENV_LOCAL_DEV_VAR_LOCAL=custom-local + ` + ); + + await runWranglerUntilConfig( + "dev --env-file=other/.env --env-file=other/.env.local" + ); + expect(extractUsingVars(std.out)).toMatchInlineSnapshot(` + "Using vars defined in other/.env + Using vars defined in other/.env.local" + `); + expect(extractBindings(std.out)).toMatchInlineSnapshot(` + "env.__DOT_ENV_LOCAL_DEV_VAR_1 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_2 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_3 (\\"(hidden)\\") Environment Variable local + env.__DOT_ENV_LOCAL_DEV_VAR_LOCAL (\\"(hidden)\\") Environment Variable local" + `); + }); + }); + describe("serve static assets", () => { it("should error if --site is used with no value", async () => { await expect( diff --git a/packages/wrangler/src/__tests__/docs.test.ts b/packages/wrangler/src/__tests__/docs.test.ts index 99c3c24433c0..d1c272628626 100644 --- a/packages/wrangler/src/__tests__/docs.test.ts +++ b/packages/wrangler/src/__tests__/docs.test.ts @@ -47,11 +47,12 @@ describe("wrangler docs", () => { search Enter search terms (e.g. the wrangler command) you want to know more about [array] [default: []] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS -y, --yes Takes you to the docs, even if search fails [boolean]" diff --git a/packages/wrangler/src/__tests__/hyperdrive.test.ts b/packages/wrangler/src/__tests__/hyperdrive.test.ts index 0b343fe1b0c2..acf652e7332c 100644 --- a/packages/wrangler/src/__tests__/hyperdrive.test.ts +++ b/packages/wrangler/src/__tests__/hyperdrive.test.ts @@ -37,11 +37,12 @@ describe("hyperdrive help", () => { wrangler hyperdrive update Update a Hyperdrive config GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -69,11 +70,12 @@ describe("hyperdrive help", () => { wrangler hyperdrive update Update a Hyperdrive config GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); }); diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index cc0c83573b0f..2dcb7f059b3c 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -70,11 +70,12 @@ describe("wrangler", () => { wrangler whoami 🕵️ Retrieve your user information GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" `); @@ -130,11 +131,12 @@ describe("wrangler", () => { wrangler whoami 🕵️ Retrieve your user information GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose" `); @@ -201,11 +203,12 @@ describe("wrangler", () => { wrangler secret bulk [file] Bulk upload secrets for a Worker GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -224,11 +227,12 @@ describe("wrangler", () => { wrangler kv namespace rename [old-name] Rename a KV namespace GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -247,11 +251,12 @@ describe("wrangler", () => { wrangler kv key delete Remove a single key value pair from the given namespace GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -269,11 +274,12 @@ describe("wrangler", () => { wrangler kv bulk delete Delete multiple key-value pairs from a namespace GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -290,11 +296,12 @@ describe("wrangler", () => { wrangler r2 bucket Manage R2 buckets GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); }); diff --git a/packages/wrangler/src/__tests__/kv.test.ts b/packages/wrangler/src/__tests__/kv.test.ts index 1cf3611d9579..2c2b528c1742 100644 --- a/packages/wrangler/src/__tests__/kv.test.ts +++ b/packages/wrangler/src/__tests__/kv.test.ts @@ -48,11 +48,12 @@ describe("wrangler", () => { wrangler kv bulk Interact with multiple Workers KV key-value pairs at once GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -70,11 +71,12 @@ describe("wrangler", () => { wrangler kv bulk Interact with multiple Workers KV key-value pairs at once GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -99,11 +101,12 @@ describe("wrangler", () => { wrangler kv bulk Interact with multiple Workers KV key-value pairs at once GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -144,11 +147,12 @@ describe("wrangler", () => { namespace The name of the new namespace [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --preview Interact with a preview namespace [boolean]" @@ -176,11 +180,12 @@ describe("wrangler", () => { namespace The name of the new namespace [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --preview Interact with a preview namespace [boolean]" @@ -450,11 +455,12 @@ describe("wrangler", () => { old-name The current name (title) of the namespace to rename [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --namespace-id The id of the namespace to rename [string] @@ -841,11 +847,12 @@ describe("wrangler", () => { value The value to write [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --binding The binding name to the namespace to write to [string] @@ -884,11 +891,12 @@ describe("wrangler", () => { value The value to write [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --binding The binding name to the namespace to write to [string] @@ -929,11 +937,12 @@ describe("wrangler", () => { value The value to write [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --binding The binding name to the namespace to write to [string] @@ -972,11 +981,12 @@ describe("wrangler", () => { value The value to write [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --binding The binding name to the namespace to write to [string] @@ -1015,11 +1025,12 @@ describe("wrangler", () => { value The value to write [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --binding The binding name to the namespace to write to [string] @@ -1060,11 +1071,12 @@ describe("wrangler", () => { value The value to write [string] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --binding The binding name to the namespace to write to [string] @@ -1451,11 +1463,12 @@ describe("wrangler", () => { key The key value to get. [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --binding The binding name to the namespace to get from [string] @@ -1489,11 +1502,12 @@ describe("wrangler", () => { key The key value to get. [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --binding The binding name to the namespace to get from [string] @@ -1528,11 +1542,12 @@ describe("wrangler", () => { key The key value to get. [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --binding The binding name to the namespace to get from [string] diff --git a/packages/wrangler/src/__tests__/mtls-certificates.test.ts b/packages/wrangler/src/__tests__/mtls-certificates.test.ts index f4a27bb5a99e..88ad24ab4f64 100644 --- a/packages/wrangler/src/__tests__/mtls-certificates.test.ts +++ b/packages/wrangler/src/__tests__/mtls-certificates.test.ts @@ -402,11 +402,12 @@ describe("wrangler", () => { wrangler mtls-certificate delete Delete an mTLS certificate GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); }); diff --git a/packages/wrangler/src/__tests__/pages/deploy.test.ts b/packages/wrangler/src/__tests__/pages/deploy.test.ts index dcc705f35f5b..c2ea3fc60a3e 100644 --- a/packages/wrangler/src/__tests__/pages/deploy.test.ts +++ b/packages/wrangler/src/__tests__/pages/deploy.test.ts @@ -64,9 +64,10 @@ describe("pages deploy", () => { directory The directory of static files to upload [string] GLOBAL FLAGS - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --project-name The name of the project you want to deploy to [string] diff --git a/packages/wrangler/src/__tests__/pages/pages.test.ts b/packages/wrangler/src/__tests__/pages/pages.test.ts index 7aed2a4551a1..9737c46e59c1 100644 --- a/packages/wrangler/src/__tests__/pages/pages.test.ts +++ b/packages/wrangler/src/__tests__/pages/pages.test.ts @@ -32,9 +32,10 @@ describe("pages", () => { wrangler pages download Download settings from your project GLOBAL FLAGS - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -52,9 +53,10 @@ describe("pages", () => { command The proxy command to run [deprecated] [string] GLOBAL FLAGS - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --compatibility-date Date to use for compatibility checks [string] @@ -100,9 +102,10 @@ describe("pages", () => { wrangler pages project delete Delete a Cloudflare Pages project GLOBAL FLAGS - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -123,9 +126,10 @@ describe("pages", () => { wrangler pages deployment tail [deployment] Start a tailing session for a project's deployment and livestream logs from your Functions GLOBAL FLAGS - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -142,9 +146,10 @@ describe("pages", () => { directory The directory of static files to upload [string] GLOBAL FLAGS - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --project-name The name of the project you want to deploy to [string] @@ -174,9 +179,10 @@ describe("pages", () => { wrangler pages secret list List all secrets for a Pages project GLOBAL FLAGS - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -193,9 +199,10 @@ describe("pages", () => { wrangler pages download config [projectName] Download your Pages project config as a Wrangler configuration file [experimental] GLOBAL FLAGS - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); diff --git a/packages/wrangler/src/__tests__/pipelines.test.ts b/packages/wrangler/src/__tests__/pipelines.test.ts index 8a6e982aadcd..86c622f6b418 100644 --- a/packages/wrangler/src/__tests__/pipelines.test.ts +++ b/packages/wrangler/src/__tests__/pipelines.test.ts @@ -284,11 +284,12 @@ describe("pipelines", () => { wrangler pipelines delete Delete a pipeline [open-beta] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -327,11 +328,12 @@ describe("pipelines", () => { pipeline The name of the new pipeline [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); diff --git a/packages/wrangler/src/__tests__/pubsub.test.ts b/packages/wrangler/src/__tests__/pubsub.test.ts index 0efc740ddc83..295f2322b1e0 100644 --- a/packages/wrangler/src/__tests__/pubsub.test.ts +++ b/packages/wrangler/src/__tests__/pubsub.test.ts @@ -35,11 +35,12 @@ describe("wrangler", () => { wrangler pubsub broker Interact with your Pub/Sub Brokers GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] 👷🏽 'wrangler pubsub ...' commands are currently in private beta. If your account isn't authorized, commands will fail. Visit the Pub/Sub docs for more info: https://developers.cloudflare.com/pub-sub/", "warn": "", @@ -68,11 +69,12 @@ describe("wrangler", () => { wrangler pubsub namespace describe Describe a Pub/Sub Namespace GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] 👷🏽 'wrangler pubsub ...' commands are currently in private beta. If your account isn't authorized, commands will fail. Visit the Pub/Sub docs for more info: https://developers.cloudflare.com/pub-sub/", "warn": "", @@ -196,11 +198,12 @@ describe("wrangler", () => { wrangler pubsub broker public-keys Show the public keys used for verifying on-publish hooks and credentials for a Broker. GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] 👷🏽 'wrangler pubsub ...' commands are currently in private beta. If your account isn't authorized, commands will fail. Visit the Pub/Sub docs for more info: https://developers.cloudflare.com/pub-sub/", "warn": "", diff --git a/packages/wrangler/src/__tests__/queues.test.ts b/packages/wrangler/src/__tests__/queues.test.ts index a95418156e01..a0a8ef86ad6b 100644 --- a/packages/wrangler/src/__tests__/queues.test.ts +++ b/packages/wrangler/src/__tests__/queues.test.ts @@ -40,11 +40,12 @@ describe("wrangler", () => { wrangler queues purge Purge messages from a Queue GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -114,11 +115,12 @@ describe("wrangler", () => { List Queues GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --page Page number for pagination [number]" @@ -262,11 +264,12 @@ describe("wrangler", () => { name The name of the queue [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --delivery-delay-secs How long a published message should be delayed for, in seconds. Must be between 0 and 42300 [number] [default: 0] @@ -482,11 +485,12 @@ describe("wrangler", () => { name The name of the queue [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --delivery-delay-secs How long a published message should be delayed for, in seconds. Must be between 0 and 42300 [number] @@ -636,11 +640,12 @@ describe("wrangler", () => { name The name of the queue [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -705,11 +710,12 @@ describe("wrangler", () => { wrangler queues consumer worker Configure Queue Worker Consumers GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -753,11 +759,12 @@ describe("wrangler", () => { script-name Name of the consumer script [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --batch-size Maximum number of messages per batch [number] @@ -1075,11 +1082,12 @@ describe("wrangler", () => { script-name Name of the consumer script [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -1463,11 +1471,12 @@ describe("wrangler", () => { wrangler queues consumer http remove Remove a Queue HTTP Pull Consumer GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -1510,11 +1519,12 @@ describe("wrangler", () => { queue-name Name of the queue for the consumer [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --batch-size Maximum number of messages per batch [number] @@ -1641,11 +1651,12 @@ describe("wrangler", () => { queue-name Name of the queue for the consumer [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -1729,11 +1740,12 @@ describe("wrangler", () => { name The name of the queue [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); it("should return queue info with worker producers when the queue has workers configured as producers", async () => { @@ -1892,11 +1904,12 @@ describe("wrangler", () => { name The name of the queue [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -2002,11 +2015,12 @@ describe("wrangler", () => { name The name of the queue [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -2104,11 +2118,12 @@ describe("wrangler", () => { name The name of the queue [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --force Skip the confirmation dialog and forcefully purge the Queue [boolean]" diff --git a/packages/wrangler/src/__tests__/r2.test.ts b/packages/wrangler/src/__tests__/r2.test.ts index f80cde792913..eb72165040b4 100644 --- a/packages/wrangler/src/__tests__/r2.test.ts +++ b/packages/wrangler/src/__tests__/r2.test.ts @@ -101,11 +101,12 @@ describe("r2", () => { wrangler r2 bucket Manage R2 buckets GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -130,11 +131,12 @@ describe("r2", () => { wrangler r2 bucket Manage R2 buckets GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -166,11 +168,12 @@ describe("r2", () => { wrangler r2 bucket lock Manage lock rules for an R2 bucket GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -207,11 +210,12 @@ describe("r2", () => { wrangler r2 bucket lock Manage lock rules for an R2 bucket GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -315,11 +319,12 @@ describe("r2", () => { name The name of the new bucket [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --location The optional location hint that determines geographic placement of the R2 bucket [string] [choices: \\"weur\\", \\"eeur\\", \\"apac\\", \\"wnam\\", \\"enam\\", \\"oc\\"] @@ -349,11 +354,12 @@ describe("r2", () => { name The name of the new bucket [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --location The optional location hint that determines geographic placement of the R2 bucket [string] [choices: \\"weur\\", \\"eeur\\", \\"apac\\", \\"wnam\\", \\"enam\\", \\"oc\\"] @@ -472,11 +478,12 @@ describe("r2", () => { wrangler r2 bucket update storage-class Update the default storage class of an existing R2 bucket GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); expect(std.err).toMatchInlineSnapshot(` "X [ERROR] Unknown argument: foo @@ -502,11 +509,12 @@ describe("r2", () => { name The name of the existing bucket [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS -J, --jurisdiction The jurisdiction of the bucket to be updated [string] @@ -568,11 +576,12 @@ describe("r2", () => { bucket The name of the bucket to delete [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS -J, --jurisdiction The jurisdiction where the bucket exists [string]" @@ -632,11 +641,12 @@ describe("r2", () => { bucket The name of the bucket to delete [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS -J, --jurisdiction The jurisdiction where the bucket exists [string]" @@ -700,11 +710,12 @@ describe("r2", () => { wrangler r2 bucket sippy get Check the status of Sippy on an R2 bucket GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -792,11 +803,12 @@ describe("r2", () => { name The name of the bucket [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS -J, --jurisdiction The jurisdiction where the bucket exists [string] @@ -836,11 +848,12 @@ describe("r2", () => { name The name of the bucket [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS -J, --jurisdiction The jurisdiction where the bucket exists [string]" @@ -888,11 +901,12 @@ describe("r2", () => { name The name of the bucket [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS -J, --jurisdiction The jurisdiction where the bucket exists [string]" @@ -955,11 +969,12 @@ describe("r2", () => { wrangler r2 bucket catalog get Get the status of the data catalog for an R2 bucket [open-beta] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -1011,11 +1026,12 @@ For more details, refer to: https://developers.cloudflare.com/r2/api/s3/tokens/" bucket The name of the bucket to enable [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); expect(std.err).toMatchInlineSnapshot(` "X [ERROR] Not enough non-option arguments: got 0, need at least 1 @@ -1043,11 +1059,12 @@ For more details, refer to: https://developers.cloudflare.com/r2/api/s3/tokens/" bucket The name of the bucket to disable the data catalog for [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); expect(std.err).toMatchInlineSnapshot(` "X [ERROR] Not enough non-option arguments: got 0, need at least 1 @@ -1132,11 +1149,12 @@ For more details, refer to: https://developers.cloudflare.com/r2/api/s3/tokens/" bucket The name of the R2 bucket whose data catalog status to retrieve [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); expect(std.err).toMatchInlineSnapshot(` "X [ERROR] Not enough non-option arguments: got 0, need at least 1 @@ -1352,11 +1370,12 @@ For more details, refer to: https://developers.cloudflare.com/r2/api/s3/tokens/" bucket The name of the R2 bucket to get event notification rules for [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS -J, --jurisdiction The jurisdiction where the bucket exists [string]" @@ -1713,11 +1732,12 @@ For more details, refer to: https://developers.cloudflare.com/r2/api/s3/tokens/" bucket The name of the R2 bucket to create an event notification rule for [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --event-types, --event-type The type of event(s) that will emit event notifications [array] [required] [choices: \\"object-create\\", \\"object-delete\\"] @@ -1872,11 +1892,12 @@ For more details, refer to: https://developers.cloudflare.com/r2/api/s3/tokens/" bucket The name of the R2 bucket to delete an event notification rule for [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --queue The name of the queue that corresponds to the event notification rule. If no rule is provided, all event notification rules associated with the bucket and queue will be deleted [string] [required] @@ -3201,11 +3222,12 @@ For more details, refer to: https://developers.cloudflare.com/r2/api/s3/tokens/" wrangler r2 object delete Delete an object in an R2 bucket GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); describe("remote", () => { diff --git a/packages/wrangler/src/__tests__/secrets-store.test.ts b/packages/wrangler/src/__tests__/secrets-store.test.ts index 4b3804ee9b42..c46169ad3622 100644 --- a/packages/wrangler/src/__tests__/secrets-store.test.ts +++ b/packages/wrangler/src/__tests__/secrets-store.test.ts @@ -25,20 +25,21 @@ describe("secrets-store help", () => { expect(std.err).toMatchInlineSnapshot(`""`); expect(std.out).toMatchInlineSnapshot(` - "wrangler secrets-store + "wrangler secrets-store -🔐 Manage the Secrets Store [alpha] + 🔐 Manage the Secrets Store [alpha] -COMMANDS - wrangler secrets-store store 🔐 Manage Stores within the Secrets Store [alpha] - wrangler secrets-store secret 🔐 Manage Secrets within the Secrets Store [alpha] + COMMANDS + wrangler secrets-store store 🔐 Manage Stores within the Secrets Store [alpha] + wrangler secrets-store secret 🔐 Manage Secrets within the Secrets Store [alpha] -GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + GLOBAL FLAGS + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -53,21 +54,22 @@ GLOBAL FLAGS " `); expect(std.out).toMatchInlineSnapshot(` - " -wrangler secrets-store - -🔐 Manage the Secrets Store [alpha] - -COMMANDS - wrangler secrets-store store 🔐 Manage Stores within the Secrets Store [alpha] - wrangler secrets-store secret 🔐 Manage Secrets within the Secrets Store [alpha] - -GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + " + wrangler secrets-store + + 🔐 Manage the Secrets Store [alpha] + + COMMANDS + wrangler secrets-store store 🔐 Manage Stores within the Secrets Store [alpha] + wrangler secrets-store secret 🔐 Manage Secrets within the Secrets Store [alpha] + + GLOBAL FLAGS + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); }); diff --git a/packages/wrangler/src/__tests__/type-generation.test.ts b/packages/wrangler/src/__tests__/type-generation.test.ts index c8bddbd8ae57..be9d000ce309 100644 --- a/packages/wrangler/src/__tests__/type-generation.test.ts +++ b/packages/wrangler/src/__tests__/type-generation.test.ts @@ -1046,6 +1046,16 @@ describe("generate types", () => { `; fs.writeFileSync(".dev.vars", localVarsEnvContent, "utf8"); + // Add a .env file that will be ignored due to there being a .dev.vars file + const dotEnvContent = dedent` + # Preceding comment + SECRET_A="A from .dev.vars" + MULTI_LINE_SECRET="A: line 1 + line 2" + UNQUOTED_SECRET= unquoted value + `; + fs.writeFileSync(".env", dotEnvContent, "utf8"); + await runWrangler("types --include-runtime=false"); expect(std.out).toMatchInlineSnapshot(` @@ -1070,6 +1080,51 @@ describe("generate types", () => { `); }); + it("should include secret keys from .env, if there is no .dev.vars", async () => { + fs.writeFileSync( + "./wrangler.jsonc", + JSON.stringify({ + vars: { + myTomlVarA: "A from wrangler toml", + myTomlVarB: "B from wrangler toml", + }, + }), + "utf-8" + ); + + const dotEnvContent = dedent` + # Preceding comment + SECRET_A_DOT_ENV="A from .env" + MULTI_LINE_SECRET="A: line 1 + line 2" + UNQUOTED_SECRET= unquoted value + `; + fs.writeFileSync(".env", dotEnvContent, "utf8"); + + await runWrangler("types --include-runtime=false"); + + expect(std.out).toMatchInlineSnapshot(` + "Generating project types... + + declare namespace Cloudflare { + interface Env { + myTomlVarA: \\"A from wrangler toml\\"; + myTomlVarB: \\"B from wrangler toml\\"; + SECRET_A_DOT_ENV: string; + MULTI_LINE_SECRET: string; + UNQUOTED_SECRET: string; + } + } + interface Env extends Cloudflare.Env {} + + ──────────────────────────────────────────────────────────── + ✨ Types written to worker-configuration.d.ts + + 📣 Remember to rerun 'wrangler types' after you change your wrangler.json file. + " + `); + }); + it("should allow opting out of strict-vars", async () => { fs.writeFileSync( "./wrangler.jsonc", diff --git a/packages/wrangler/src/__tests__/vectorize/vectorize.test.ts b/packages/wrangler/src/__tests__/vectorize/vectorize.test.ts index 37e2ec31df62..ec48320f5df8 100644 --- a/packages/wrangler/src/__tests__/vectorize/vectorize.test.ts +++ b/packages/wrangler/src/__tests__/vectorize/vectorize.test.ts @@ -39,11 +39,12 @@ describe("vectorize help", () => { wrangler vectorize delete-metadata-index Delete metadata indexes GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -79,11 +80,12 @@ describe("vectorize help", () => { wrangler vectorize delete-metadata-index Delete metadata indexes GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -107,11 +109,12 @@ describe("vectorize help", () => { name The name of the Vectorize index. [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --json Return output as clean JSON [boolean] [default: false] @@ -139,11 +142,12 @@ describe("vectorize help", () => { name The name of the Vectorize index [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean] + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean] OPTIONS --vector Vector to query the Vectorize Index [number] diff --git a/packages/wrangler/src/__tests__/versions/versions.help.test.ts b/packages/wrangler/src/__tests__/versions/versions.help.test.ts index 45bde39cbddf..b5a3537cdd6d 100644 --- a/packages/wrangler/src/__tests__/versions/versions.help.test.ts +++ b/packages/wrangler/src/__tests__/versions/versions.help.test.ts @@ -23,11 +23,12 @@ describe("versions --help", () => { wrangler versions secret Generate a secret that can be referenced in a Worker GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); }); @@ -54,11 +55,12 @@ describe("versions subhelp", () => { wrangler versions secret Generate a secret that can be referenced in a Worker GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); }); diff --git a/packages/wrangler/src/__tests__/worker-namespace.test.ts b/packages/wrangler/src/__tests__/worker-namespace.test.ts index e0009008f6bb..00e2e4e31583 100644 --- a/packages/wrangler/src/__tests__/worker-namespace.test.ts +++ b/packages/wrangler/src/__tests__/worker-namespace.test.ts @@ -42,11 +42,12 @@ describe("dispatch-namespace", () => { wrangler dispatch-namespace rename Rename a dispatch namespace GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]", + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]", "warn": "", } `); @@ -95,11 +96,12 @@ describe("dispatch-namespace", () => { name Name of the dispatch namespace [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -146,11 +148,12 @@ describe("dispatch-namespace", () => { name Name of the dispatch namespace [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -206,11 +209,12 @@ describe("dispatch-namespace", () => { name Name of the dispatch namespace [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); @@ -317,11 +321,12 @@ describe("dispatch-namespace", () => { newName New name of the dispatch namespace [string] [required] GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" `); }); diff --git a/packages/wrangler/src/__tests__/workflows.test.ts b/packages/wrangler/src/__tests__/workflows.test.ts index 1af1a75b0af0..051b17044cf2 100644 --- a/packages/wrangler/src/__tests__/workflows.test.ts +++ b/packages/wrangler/src/__tests__/workflows.test.ts @@ -125,11 +125,12 @@ describe("wrangler workflows", () => { wrangler workflows instances Manage Workflow instances GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" ` ); }); @@ -156,11 +157,12 @@ describe("wrangler workflows", () => { wrangler workflows instances resume Resume a workflow instance GLOBAL FLAGS - -c, --config Path to Wrangler configuration file [string] - --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] - -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] - -h, --help Show help [boolean] - -v, --version Show version number [boolean]" + -c, --config Path to Wrangler configuration file [string] + --cwd Run as if Wrangler was started in the specified directory instead of the current working directory [string] + -e, --env Environment to use for operations, and for selecting .env and .dev.vars files [string] + --env-file Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files [array] + -h, --help Show help [boolean] + -v, --version Show version number [boolean]" ` ); }); diff --git a/packages/wrangler/src/api/dev.ts b/packages/wrangler/src/api/dev.ts index fbe39b509235..9c500eb51937 100644 --- a/packages/wrangler/src/api/dev.ts +++ b/packages/wrangler/src/api/dev.ts @@ -15,6 +15,7 @@ import type { RequestInfo, RequestInit, Response } from "undici"; export interface Unstable_DevOptions { config?: string; // Path to .toml configuration file, relative to cwd env?: string; // Environment to use for operations, and for selecting .env and .dev.vars files + envFiles?: string[]; // Paths to .env files to load, relative to cwd ip?: string; // IP address to listen on port?: number; // Port to listen on bundle?: boolean; // Set to false to skip internal build steps and directly deploy script @@ -183,6 +184,7 @@ export async function unstable_dev( }, config: options?.config, env: options?.env, + envFile: options?.envFiles, processEntrypoint, additionalModules, bundle: options?.bundle, diff --git a/packages/wrangler/src/api/integrations/platform/index.ts b/packages/wrangler/src/api/integrations/platform/index.ts index 6efa41151ca4..55626c74508d 100644 --- a/packages/wrangler/src/api/integrations/platform/index.ts +++ b/packages/wrangler/src/api/integrations/platform/index.ts @@ -32,6 +32,7 @@ import type { WorkerOptions, } from "miniflare"; +export { getVarsForDev as unstable_getVarsForDev } from "../../../dev/dev-vars"; export { readConfig as unstable_readConfig }; export type { Config as Unstable_Config, @@ -56,6 +57,23 @@ export type GetPlatformProxyOptions = { * point to a valid file on the filesystem */ configPath?: string; + /** + * Paths to `.env` files to load environment variables from, relative to the project directory. + * + * The project directory is computed as the directory containing `configPath` or the current working directory if `configPath` is undefined. + * + * If `envFiles` is defined, only the files in the array will be considered for loading local dev variables. + * If `undefined`, the default behavior is: + * - compute the project directory as that containing the Wrangler configuration file, + * or the current working directory if no Wrangler configuration file is specified. + * - look for `.env` and `.env.local` files in the project directory. + * - if the `environment` option is specified, also look for `.env.` and `.env..local` + * files in the project directory + * - resulting in an `envFiles` array like: `[".env", ".env.local", ".env.", ".env..local"]`. + * + * The values from files earlier in the `envFiles` array (e.g. `envFiles[x]`) will be overridden by values from files later in the array (e.g. `envFiles[x+1)`). + */ + envFiles?: string[]; /** * Indicates if and where to persist the bindings data, if not present or `true` it defaults to the same location * used by wrangler: `.wrangler/state/v3` (so that the same data can be easily used by the caller and wrangler). @@ -183,6 +201,7 @@ async function getMiniflareOptionsFromConfig(args: { const bindings = getBindings( config, options.environment, + options.envFiles, true, {}, remoteBindingsEnabled @@ -350,6 +369,7 @@ export function unstable_getMiniflareWorkerOptions( configOrConfigPath: string | Config, env?: string, options?: { + envFiles?: string[]; imagesLocalMode?: boolean; remoteProxyConnectionString?: RemoteProxyConnectionString; remoteBindingsEnabled?: boolean; @@ -375,7 +395,7 @@ export function unstable_getMiniflareWorkerOptions( const containerDOClassNames = new Set( config.containers?.map((c) => c.class_name) ); - const bindings = getBindings(config, env, true, {}, true); + const bindings = getBindings(config, env, options?.envFiles, true, {}, true); const { bindingOptions, externalWorkers } = buildMiniflareBindingOptions( { name: config.name, diff --git a/packages/wrangler/src/api/startDevWorker/ConfigController.ts b/packages/wrangler/src/api/startDevWorker/ConfigController.ts index 07d27e361ed3..e31125c5af30 100644 --- a/packages/wrangler/src/api/startDevWorker/ConfigController.ts +++ b/packages/wrangler/src/api/startDevWorker/ConfigController.ts @@ -177,6 +177,7 @@ async function resolveBindings( const bindings = getBindings( config, input.env, + input.envFiles, !input.dev?.remote, { kv: extractBindingsOfType("kv_namespace", input.bindings), @@ -323,6 +324,7 @@ async function resolveConfig( sendMetrics: input.sendMetrics ?? config.send_metrics, triggers: await resolveTriggers(config, input), env: input.env, + envFiles: input.envFiles, build: { alias: input.build?.alias ?? config.alias, additionalModules: input.build?.additionalModules ?? [], diff --git a/packages/wrangler/src/api/startDevWorker/types.ts b/packages/wrangler/src/api/startDevWorker/types.ts index f03fe1a0f486..c50374f8bc02 100644 --- a/packages/wrangler/src/api/startDevWorker/types.ts +++ b/packages/wrangler/src/api/startDevWorker/types.ts @@ -84,6 +84,14 @@ export interface StartDevWorkerInput { env?: string; + /** + * An array of paths to the .env files to load for this worker, relative to the project directory. + * + * If not specified, defaults to the standard `.env` files as given by `getDefaultEnvFiles()`. + * The project directory is where the Wrangler configuration file is located or the current working directory otherwise. + */ + envFiles?: string[]; + /** The bindings available to the worker. The specified bindind type will be exposed to the worker on the `env` object under the same key. */ bindings?: Record; // Type level constraint for bindings not sharing names migrations?: DurableObjectMigration[]; diff --git a/packages/wrangler/src/config/dot-env.ts b/packages/wrangler/src/config/dot-env.ts new file mode 100644 index 000000000000..a890878de844 --- /dev/null +++ b/packages/wrangler/src/config/dot-env.ts @@ -0,0 +1,76 @@ +import path from "path"; +import dotenv from "dotenv"; +import dotenvExpand from "dotenv-expand"; +import { logger } from "../logger"; + +/** + * Generates the default array of `envFiles` for .env file loading. + * + * The default order is [`.env`, `.env.local`, `.env.`, `.env..local`]. + * + * @param env - The specific environment name (e.g., "staging") or `undefined` if no specific environment is set. + * @returns An array of strings representing the relative paths to the default .env files. + */ +export function getDefaultEnvFiles(env: string | undefined): string[] { + // Generate the default paths for .env files based on the provided base path and environment. + const envFiles = [".env", ".env.local"]; + if (env !== undefined) { + envFiles.push(`.env.${env}`); + envFiles.push(`.env.${env}.local`); + } + return envFiles; +} + +/** + * Loads environment variables from .env files. + * + * This will merge values from each of of the `envPaths` in order. + * Values in the file at `envPaths[x+1]` will override the values in the files at `envPaths[x]`. + * + * Further, once merged values are expanded, meaning that if a value references another variable + * (e.g., `FOO=${BAR}`), it will be replaced with the value of `BAR` if it exists. + * + * @param envPaths - An array of absolute paths to .env files to load. + * @param options.includeProcessEnv - If true, will include the current process environment variables in the merged result. + * @param options.silent - If true, will not log any messages about the loaded .env files. + * @returns An object containing the merged and expanded environment variables. + */ +export function loadDotEnv( + envPaths: string[], + { includeProcessEnv, silent }: { includeProcessEnv: boolean; silent: boolean } +): dotenv.DotenvParseOutput { + // The `parsedEnv` object will be mutated to contain the merged values. + const parsedEnv = {}; + for (const envPath of envPaths) { + // The `parsed` object only contains the values from the loaded .env file. + const { error, parsed } = dotenv.config({ + path: envPath, + processEnv: parsedEnv, + override: true, + }); + if (error) { + logger.debug(`Failed to load .env file "${envPath}":`, error); + } else if (parsed && !silent) { + const relativePath = path.relative(process.cwd(), envPath); + logger.log(`Using vars defined in ${relativePath}`); + } + } + + // The `expandedEnv` object will be mutated to include the expanded values from `parsedEnv` + // but only if the key is not already defined in `expandedEnv`. + const expandedEnv = {}; + if (includeProcessEnv) { + Object.assign(expandedEnv, process.env); + if (!silent) { + logger.log("Using vars defined in process.env"); + } + } + const { error } = dotenvExpand.expand({ + processEnv: expandedEnv, + parsed: parsedEnv, + }); + if (error) { + logger.debug(`Failed to expand .env values:`, error); + } + return expandedEnv; +} diff --git a/packages/wrangler/src/config/index.ts b/packages/wrangler/src/config/index.ts index b70115592252..fb0361e39a1e 100644 --- a/packages/wrangler/src/config/index.ts +++ b/packages/wrangler/src/config/index.ts @@ -1,7 +1,4 @@ -import path from "node:path"; -import { maybeGetFile } from "@cloudflare/workers-shared"; import TOML from "@iarna/toml"; -import dotenv from "dotenv"; import { FatalError, UserError } from "../errors"; import { logger } from "../logger"; import { EXIT_CODE_INVALID_PAGES_CONFIG } from "../pages/errors"; @@ -211,44 +208,3 @@ export function withConfig( return handler({ ...args, config: readConfig(args, options) }); }; } - -export interface DotEnv { - path: string; - parsed: dotenv.DotenvParseOutput; -} - -function tryLoadDotEnv(basePath: string): DotEnv | undefined { - try { - const contents = maybeGetFile(basePath); - if (contents === undefined) { - logger.debug( - `.env file not found at "${path.relative(".", basePath)}". Continuing... For more details, refer to https://developers.cloudflare.com/workers/wrangler/system-environment-variables/` - ); - return; - } - - const parsed = dotenv.parse(contents); - return { path: basePath, parsed }; - } catch (e) { - logger.debug( - `Failed to load .env file "${path.relative(".", basePath)}":`, - e - ); - } -} - -/** - * Loads a dotenv file from `envPath`, preferring to read `${envPath}.${env}` if - * `env` is defined and that file exists. - * - * Note: The `getDotDevDotVarsContent` function in the `packages/vite-plugin-cloudflare/src/dev-vars.ts` file - * follows the same logic implemented here, the two need to be kept in sync, so if you modify some logic - * here make sure that, if applicable, the same change is reflected there - */ -export function loadDotEnv(envPath: string, env?: string): DotEnv | undefined { - if (env === undefined) { - return tryLoadDotEnv(envPath); - } else { - return tryLoadDotEnv(`${envPath}.${env}`) ?? tryLoadDotEnv(envPath); - } -} diff --git a/packages/wrangler/src/dev.ts b/packages/wrangler/src/dev.ts index dea2c21f88b8..c79cc432e397 100644 --- a/packages/wrangler/src/dev.ts +++ b/packages/wrangler/src/dev.ts @@ -485,6 +485,7 @@ async function setupDevEnv( pattern: r, })), env: args.env, + envFiles: args.envFile, build: { bundle: args.bundle !== undefined ? args.bundle : undefined, define: collectKeyValues(args.define), @@ -864,9 +865,22 @@ function getResolvedSiteAssetPaths( ); } +/** + * Gets the bindings for the Cloudflare Worker. + * + * @param configParam The loaded configuration. + * @param env The environment to use, if any. + * @param envFiles An array of paths, relative to the project directory, of .env files to load. + * If `undefined` it defaults to the standard .env files from `getDefaultEnvFiles()`. + * @param local Whether the dev server should run locally. + * @param args Additional arguments for the dev server. + * @param remoteBindingsEnabled Whether remote bindings are enabled, defaults to the value of the `REMOTE_BINDINGS` flag. + * @returns The bindings for the Cloudflare Worker. + */ export function getBindings( configParam: Config, env: string | undefined, + envFiles: string[] | undefined, local: boolean, args: AdditionalDevProps, remoteBindingsEnabled = getFlag("REMOTE_BINDINGS") @@ -1040,7 +1054,12 @@ export function getBindings( // non-inheritable fields vars: { // Use a copy of combinedVars since we're modifying it later - ...getVarsForDev(configParam, env), + ...getVarsForDev( + configParam.userConfigPath, + envFiles, + configParam.vars, + env + ), ...args.vars, }, durable_objects: { diff --git a/packages/wrangler/src/dev/dev-vars.ts b/packages/wrangler/src/dev/dev-vars.ts index 9d889dfd2598..5ea290ad988a 100644 --- a/packages/wrangler/src/dev/dev-vars.ts +++ b/packages/wrangler/src/dev/dev-vars.ts @@ -1,5 +1,11 @@ import * as path from "node:path"; -import { loadDotEnv } from "../config"; +import { maybeGetFile } from "@cloudflare/workers-shared"; +import dotenv from "dotenv"; +import { getDefaultEnvFiles, loadDotEnv } from "../config/dot-env"; +import { + getCloudflareIncludeProcessEnvFromEnv, + getCloudflareLoadDevVarsFromDotEnv, +} from "../environment-variables/misc-variables"; import { logger } from "../logger"; import type { Config } from "../config"; @@ -16,29 +22,90 @@ import type { Config } from "../config"; * Wrangler configuration). If the `--env ` option is set, we'll first look for * `.dev.vars.`. * - * Any values in this file, formatted like a `dotenv` file, will add to or override `vars` + * If there are no `.dev.vars*` file, (and CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV is not "false") + * we will look for `.env*` files in the same directory. + * If the `envFiles` option is set, we'll look for the `.env` files at those paths instead of the defaults. + * + * Any values in these files (all formatted like `.env` files) will add to or override `vars` * bindings provided in the Wrangler configuration file. + * + * @param configPath - The path to the Wrangler configuration file, if defined. + * @param envFiles - An array of paths to .env files to load; if `undefined` the default .env files will be used (see `getDefaultEnvFiles()`). + * The `envFiles` paths are resolved against the directory of the Wrangler configuration file, if there is one, otherwise against the current working directory. + * @param vars - The existing `vars` bindings from the Wrangler configuration. + * @param env - The specific environment name (e.g., "staging") or `undefined` if no specific environment is set. + * @param silent - If true, will not log any messages about the loaded .dev.vars files or .env files. + * @returns The merged `vars` bindings, including those loaded from `.dev.vars` or `.env` files. */ export function getVarsForDev( - config: Pick, + configPath: string | undefined, + envFiles: string[] | undefined, + vars: Config["vars"], env: string | undefined, silent = false ): Config["vars"] { - const configDir = path.resolve( - config.userConfigPath ? path.dirname(config.userConfigPath) : "." - ); + const configDir = path.resolve(configPath ? path.dirname(configPath) : "."); + + // First, try to load from .dev.vars const devVarsPath = path.resolve(configDir, ".dev.vars"); - const loaded = loadDotEnv(devVarsPath, env); + const loaded = loadDotDevDotVars(devVarsPath, env); if (loaded !== undefined) { const devVarsRelativePath = path.relative(process.cwd(), loaded.path); if (!silent) { logger.log(`Using vars defined in ${devVarsRelativePath}`); } - return { - ...config.vars, - ...loaded.parsed, - }; + return { ...vars, ...loaded.parsed }; + } else if (getCloudflareLoadDevVarsFromDotEnv()) { + // If no .dev.vars files load vars from .env files instead. + const resolvedEnvFilePaths = (envFiles ?? getDefaultEnvFiles(env)).map( + (p) => path.resolve(configDir, p) + ); + const dotEnvVars = loadDotEnv(resolvedEnvFilePaths, { + includeProcessEnv: getCloudflareIncludeProcessEnvFromEnv(), + silent, + }); + return { ...vars, ...dotEnvVars }; } else { - return config.vars; + // Just return the vars from the Wrangler configuration. + return vars; + } +} + +export interface DotDevDotVars { + path: string; + parsed: dotenv.DotenvParseOutput; +} + +function tryLoadDotDevDotVars(basePath: string): DotDevDotVars | undefined { + try { + const contents = maybeGetFile(basePath); + if (contents === undefined) { + logger.debug( + `local dev variables file not found at "${path.relative(".", basePath)}". Continuing... For more details, refer to https://developers.cloudflare.com/workers/wrangler/system-environment-variables/` + ); + return; + } + + const parsed = dotenv.parse(contents); + return { path: basePath, parsed }; + } catch (e) { + throw new Error( + `Failed to load local dev variables file "${path.relative(".", basePath)}":`, + { cause: e } + ); } } + +/** + * Loads a .dev.vars file from `envPath`, preferring to read `${envPath}.${env}` if + * `env` is defined and that file exists. + */ +export function loadDotDevDotVars( + envPath: string, + env?: string +): DotDevDotVars | undefined { + return ( + (env !== undefined && tryLoadDotDevDotVars(`${envPath}.${env}`)) || + tryLoadDotDevDotVars(envPath) + ); +} diff --git a/packages/wrangler/src/environment-variables/factory.ts b/packages/wrangler/src/environment-variables/factory.ts index 2076b7fd1f25..a74834b927c6 100644 --- a/packages/wrangler/src/environment-variables/factory.ts +++ b/packages/wrangler/src/environment-variables/factory.ts @@ -38,7 +38,9 @@ type VariableNames = // We don't get the following using the environment variable factory, // but including here so that all environment variables are documented here: | "WRANGLER_DOCKER_HOST" - | "DOCKER_HOST"; + | "DOCKER_HOST" + | "CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV" + | "CLOUDFLARE_INCLUDE_PROCESS_ENV"; type DeprecatedNames = | "CF_ACCOUNT_ID" diff --git a/packages/wrangler/src/environment-variables/misc-variables.ts b/packages/wrangler/src/environment-variables/misc-variables.ts index c6f80ce6ef5a..acab5c02108b 100644 --- a/packages/wrangler/src/environment-variables/misc-variables.ts +++ b/packages/wrangler/src/environment-variables/misc-variables.ts @@ -145,9 +145,7 @@ function getStagingSubdomain(): string { */ export const getSanitizeLogs = getBooleanEnvironmentVariableFactory({ variableName: "WRANGLER_LOG_SANITIZE", - defaultValue() { - return true; - }, + defaultValue: true, }); /** @@ -255,7 +253,7 @@ export const getRegistryPath = getEnvironmentVariableFactory({ /** * `WRANGLER_D1_EXTRA_LOCATION_CHOICES` is an internal variable to let D1 team target their testing environments. * - * External accounts cannot access testing envionments, so should not set this variable. + * External accounts cannot access testing environments, so should not set this variable. */ export const getD1ExtraLocationChoices: () => string | undefined = getEnvironmentVariableFactory({ @@ -273,3 +271,22 @@ export const getDockerPath = getEnvironmentVariableFactory({ return "docker"; }, }); + +/** +/** + * `CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV` specifies whether to load vars for local dev from `.env` files. + */ +export const getCloudflareLoadDevVarsFromDotEnv = + getBooleanEnvironmentVariableFactory({ + variableName: "CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV", + defaultValue: true, + }); + +/** + * `CLOUDFLARE_INCLUDE_PROCESS_ENV` specifies whether to include the `process.env` in vars loaded from `.env` for local development. + */ +export const getCloudflareIncludeProcessEnvFromEnv = + getBooleanEnvironmentVariableFactory({ + variableName: "CLOUDFLARE_INCLUDE_PROCESS_ENV", + defaultValue: false, + }); diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index a83aab8bb977..a40efc9e13ba 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -1,5 +1,6 @@ import assert from "node:assert"; import os from "node:os"; +import { resolve } from "node:path"; import { setTimeout } from "node:timers/promises"; import { ApiError } from "@cloudflare/containers-shared"; import chalk from "chalk"; @@ -21,7 +22,8 @@ import { } from "./cert/cert"; import { checkNamespace, checkStartupCommand } from "./check/commands"; import { cloudchamber } from "./cloudchamber"; -import { experimental_readRawConfig, loadDotEnv, readConfig } from "./config"; +import { experimental_readRawConfig, readConfig } from "./config"; +import { getDefaultEnvFiles, loadDotEnv } from "./config/dot-env"; import { containers } from "./containers"; import { demandSingleValue } from "./core"; import { CommandRegistry } from "./core/CommandRegistry"; @@ -400,6 +402,13 @@ export function createCLIParser(argv: string[]) { type: "string", requiresArg: true, }) + .option("env-file", { + describe: + "Path to an .env file to load - can be specified multiple times - values from earlier files are overridden by values in later files", + type: "string", + array: true, + requiresArg: true, + }) .check(demandSingleValue("env")) .option("experimental-json-config", { alias: "j", @@ -419,13 +428,14 @@ export function createCLIParser(argv: string[]) { return true; }) .check((args) => { - // Grab locally specified env params from `.env` file - const loaded = loadDotEnv(".env", args.env); - for (const [key, value] of Object.entries(loaded?.parsed ?? {})) { - if (!(key in process.env)) { - process.env[key] = value; - } - } + // Set process environment params from `.env` files if available. + const resolvedEnvFilePaths = ( + args["env-file"] ?? getDefaultEnvFiles(args.env) + ).map((p) => resolve(p)); + process.env = loadDotEnv(resolvedEnvFilePaths, { + includeProcessEnv: true, + silent: true, + }); // Write a session entry to the output file (if there is one). writeOutput({ @@ -463,7 +473,7 @@ export function createCLIParser(argv: string[]) { "Examples:": `${chalk.bold("EXAMPLES")}`, }); wrangler.group( - ["config", "cwd", "env", "help", "version"], + ["config", "cwd", "env", "env-file", "help", "version"], `${chalk.bold("GLOBAL FLAGS")}` ); wrangler.help("help", "Show help").alias("h", "help"); diff --git a/packages/wrangler/src/pages/dev.ts b/packages/wrangler/src/pages/dev.ts index a6aa818f5408..d504eb4256b8 100644 --- a/packages/wrangler/src/pages/dev.ts +++ b/packages/wrangler/src/pages/dev.ts @@ -923,6 +923,7 @@ export const pagesDevCommand = createCommand({ minify: undefined, legacyEnv: undefined, env: undefined, + envFile: undefined, ip, port, inspectorPort, diff --git a/packages/wrangler/src/type-generation/index.ts b/packages/wrangler/src/type-generation/index.ts index 2d815ebd642c..1d7f1d536780 100644 --- a/packages/wrangler/src/type-generation/index.ts +++ b/packages/wrangler/src/type-generation/index.ts @@ -305,10 +305,12 @@ export async function generateEnvTypes( ): Promise<{ envHeader?: string; envTypes?: string }> { const stringKeys: string[] = []; const secrets = getVarsForDev( + config.userConfigPath, + args.envFile, // We do not want `getVarsForDev()` to merge in the standard vars into the dev vars // because we want to be able to work with secrets differently to vars. // So we pass in a fake vars object here. - { ...config, vars: {} }, + {}, args.env, true ) as Record; diff --git a/packages/wrangler/src/yargs-types.ts b/packages/wrangler/src/yargs-types.ts index 47904e8fff86..5a7c9174c720 100644 --- a/packages/wrangler/src/yargs-types.ts +++ b/packages/wrangler/src/yargs-types.ts @@ -9,6 +9,7 @@ export interface CommonYargsOptions { cwd: string | undefined; config: string | undefined; env: string | undefined; + "env-file": string[] | undefined; "experimental-provision": boolean | undefined; "experimental-remote-bindings": boolean | undefined; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1698d64190e7..0e735fc71cb1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2351,6 +2351,27 @@ importers: specifier: workspace:* version: link:../../../wrangler + packages/vite-plugin-cloudflare/playground/dot-env: + devDependencies: + '@cloudflare/vite-plugin': + specifier: workspace:* + version: link:../.. + '@cloudflare/workers-tsconfig': + specifier: workspace:* + version: link:../../../workers-tsconfig + '@cloudflare/workers-types': + specifier: ^4.20250711.0 + version: 4.20250730.0 + typescript: + specifier: catalog:default + version: 5.8.2 + vite: + specifier: catalog:vite-plugin + version: 7.0.0(@types/node@20.19.9)(jiti@2.4.2)(lightningcss@1.29.2) + wrangler: + specifier: workspace:* + version: link:../../../wrangler + packages/vite-plugin-cloudflare/playground/durable-objects: devDependencies: '@cloudflare/vite-plugin': @@ -3508,6 +3529,9 @@ importers: dotenv: specifier: ^16.3.1 version: 16.3.1 + dotenv-expand: + specifier: ^12.0.2 + version: 12.0.2 execa: specifier: ^6.1.0 version: 6.1.0 @@ -8558,6 +8582,10 @@ packages: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} + dotenv-expand@12.0.2: + resolution: {integrity: sha512-lXpXz2ZE1cea1gL4sz2Ipj8y4PiVjytYr3Ij0SWoms1PGxIv7m2CRKuRuCRtHdVuvM/hNJPMxt5PbhboNC4dPQ==} + engines: {node: '>=12'} + dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} @@ -8566,6 +8594,10 @@ packages: resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} engines: {node: '>=12'} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} @@ -18627,16 +18659,22 @@ snapshots: dotenv-cli@7.3.0: dependencies: cross-spawn: 7.0.6 - dotenv: 16.3.1 + dotenv: 16.6.1 dotenv-expand: 10.0.0 minimist: 1.2.6 dotenv-expand@10.0.0: {} + dotenv-expand@12.0.2: + dependencies: + dotenv: 16.6.1 + dotenv@16.0.3: {} dotenv@16.3.1: {} + dotenv@16.6.1: {} + dotenv@8.6.0: {} downshift@6.1.12(react@18.3.1):