Skip to content

Commit 9c3ff21

Browse files
Implement .env support in local development
1 parent cb2fd2e commit 9c3ff21

File tree

13 files changed

+643
-122
lines changed

13 files changed

+643
-122
lines changed

packages/wrangler/e2e/__snapshots__/dev.test.ts.snap

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ exports[`basic js dev: 'wrangler dev --remote' > can modify Worker during wrangl
1616

1717
exports[`basic js dev: 'wrangler dev --remote' > can modify Worker during wrangler dev --remote 2`] = `"Updated Worker! value"`;
1818

19-
exports[`basic js dev: 'wrangler dev --remote' > can modify worker during wrangler dev --remote 1`] = `"Hello World!"`;
20-
21-
exports[`basic js dev: 'wrangler dev --remote' > can modify worker during wrangler dev --remote 2`] = `"Updated Worker! value"`;
22-
2319
exports[`basic js dev: 'wrangler dev --remote' > hotkeys can be disabled with wrangler dev --remote 1`] = `"Hello World!"`;
2420

2521
exports[`basic js dev: 'wrangler dev' > --test-scheduled works with wrangler dev > custom build 1`] = `"Ran scheduled event"`;
@@ -38,8 +34,4 @@ exports[`basic js dev: 'wrangler dev' > can modify Worker during wrangler dev 1`
3834

3935
exports[`basic js dev: 'wrangler dev' > can modify Worker during wrangler dev 2`] = `"Updated Worker! value"`;
4036

41-
exports[`basic js dev: 'wrangler dev' > can modify worker during wrangler dev 1`] = `"Hello World!"`;
42-
43-
exports[`basic js dev: 'wrangler dev' > can modify worker during wrangler dev 2`] = `"Updated Worker! value"`;
44-
4537
exports[`basic js dev: 'wrangler dev' > hotkeys can be disabled with wrangler dev 1`] = `"Hello World!"`;

packages/wrangler/e2e/dev.test.ts

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,3 +2294,251 @@ This is a random email body.
22942294
`);
22952295
});
22962296
});
2297+
2298+
describe(".env support in local dev", () => {
2299+
const seedFiles = {
2300+
"wrangler.jsonc": JSON.stringify({
2301+
name: workerName,
2302+
main: "src/index.ts",
2303+
compatibility_date: "2025-07-01",
2304+
vars: {
2305+
WRANGLER_ENV_VAR_0: "default-0",
2306+
WRANGLER_ENV_VAR_1: "default-1",
2307+
WRANGLER_ENV_VAR_2: "default-2",
2308+
WRANGLER_ENV_VAR_3: "default-3",
2309+
},
2310+
}),
2311+
"src/index.ts": dedent`
2312+
export default {
2313+
fetch(request, env) {
2314+
return new Response(JSON.stringify(env, null, 2));
2315+
}
2316+
}
2317+
`,
2318+
};
2319+
2320+
it("should load environment variables from .env file", async () => {
2321+
const helper = new WranglerE2ETestHelper();
2322+
await helper.seed(seedFiles);
2323+
await helper.seed({
2324+
".env": dedent`
2325+
WRANGLER_ENV_VAR_1=env-1
2326+
WRANGLER_ENV_VAR_2=env-2
2327+
`,
2328+
});
2329+
2330+
const worker = helper.runLongLived("wrangler dev");
2331+
const { url } = await worker.waitForReady();
2332+
expect(await (await fetch(url)).text()).toMatchInlineSnapshot(`
2333+
"{
2334+
"WRANGLER_ENV_VAR_0": "default-0",
2335+
"WRANGLER_ENV_VAR_1": "env-1",
2336+
"WRANGLER_ENV_VAR_2": "env-2",
2337+
"WRANGLER_ENV_VAR_3": "default-3"
2338+
}"
2339+
`);
2340+
});
2341+
2342+
it("should not load load environment variables from .env files if there is a .dev.vars file", async () => {
2343+
const helper = new WranglerE2ETestHelper();
2344+
await helper.seed(seedFiles);
2345+
await helper.seed({
2346+
".env": dedent`
2347+
WRANGLER_ENV_VAR_1=env-1
2348+
WRANGLER_ENV_VAR_2=env-2
2349+
`,
2350+
});
2351+
await helper.seed({
2352+
".dev.vars": dedent`
2353+
WRANGLER_ENV_VAR_1=dev-vars-1
2354+
WRANGLER_ENV_VAR_2=dev-vars-2
2355+
`,
2356+
});
2357+
2358+
const worker = helper.runLongLived("wrangler dev");
2359+
const { url } = await worker.waitForReady();
2360+
expect(await (await fetch(url)).text()).toMatchInlineSnapshot(`
2361+
"{
2362+
"WRANGLER_ENV_VAR_0": "default-0",
2363+
"WRANGLER_ENV_VAR_1": "dev-vars-1",
2364+
"WRANGLER_ENV_VAR_2": "dev-vars-2",
2365+
"WRANGLER_ENV_VAR_3": "default-3"
2366+
}"
2367+
`);
2368+
});
2369+
2370+
it("should load environment variables from .env.staging if it exists and --env=staging", async () => {
2371+
const helper = new WranglerE2ETestHelper();
2372+
await helper.seed(seedFiles);
2373+
await helper.seed({
2374+
".env.staging": dedent`
2375+
WRANGLER_ENV_VAR_2=staging-2
2376+
WRANGLER_ENV_VAR_3=staging-3
2377+
`,
2378+
});
2379+
2380+
const worker = helper.runLongLived("wrangler dev --env=staging");
2381+
const { url } = await worker.waitForReady();
2382+
expect(await (await fetch(url)).text()).toMatchInlineSnapshot(`
2383+
"{
2384+
"WRANGLER_ENV_VAR_0": "default-0",
2385+
"WRANGLER_ENV_VAR_1": "default-1",
2386+
"WRANGLER_ENV_VAR_2": "staging-2",
2387+
"WRANGLER_ENV_VAR_3": "staging-3"
2388+
}"
2389+
`);
2390+
});
2391+
2392+
it("should prefer to load environment variables from .env.staging over .env, if --env=staging", async () => {
2393+
const helper = new WranglerE2ETestHelper();
2394+
await helper.seed(seedFiles);
2395+
await helper.seed({
2396+
".env": dedent`
2397+
WRANGLER_ENV_VAR_1=env-1
2398+
WRANGLER_ENV_VAR_2=env-2
2399+
`,
2400+
});
2401+
await helper.seed({
2402+
".env.staging": dedent`
2403+
WRANGLER_ENV_VAR_2=staging-2
2404+
WRANGLER_ENV_VAR_3=staging-3
2405+
`,
2406+
});
2407+
2408+
const worker = helper.runLongLived("wrangler dev --env=staging");
2409+
const { url } = await worker.waitForReady();
2410+
expect(await (await fetch(url)).text()).toMatchInlineSnapshot(`
2411+
"{
2412+
"WRANGLER_ENV_VAR_0": "default-0",
2413+
"WRANGLER_ENV_VAR_1": "env-1",
2414+
"WRANGLER_ENV_VAR_2": "staging-2",
2415+
"WRANGLER_ENV_VAR_3": "staging-3"
2416+
}"
2417+
`);
2418+
});
2419+
2420+
it("should load environment variables from .env file if --env=xxx and .env.xxx does not exist", async () => {
2421+
const helper = new WranglerE2ETestHelper();
2422+
await helper.seed(seedFiles);
2423+
await helper.seed({
2424+
".env": dedent`
2425+
WRANGLER_ENV_VAR_1=env-1
2426+
WRANGLER_ENV_VAR_2=env-2
2427+
`,
2428+
});
2429+
await helper.seed({
2430+
".env.staging": dedent`
2431+
WRANGLER_ENV_VAR_2=staging-2
2432+
WRANGLER_ENV_VAR_3=staging-3
2433+
`,
2434+
});
2435+
2436+
const worker = helper.runLongLived("wrangler dev --env=xxx");
2437+
const { url } = await worker.waitForReady();
2438+
expect(await (await fetch(url)).text()).toMatchInlineSnapshot(`
2439+
"{
2440+
"WRANGLER_ENV_VAR_0": "default-0",
2441+
"WRANGLER_ENV_VAR_1": "env-1",
2442+
"WRANGLER_ENV_VAR_2": "env-2",
2443+
"WRANGLER_ENV_VAR_3": "default-3"
2444+
}"
2445+
`);
2446+
});
2447+
2448+
it("should prefer to load environment variables from .env.local over .env", async () => {
2449+
const helper = new WranglerE2ETestHelper();
2450+
await helper.seed(seedFiles);
2451+
await helper.seed({
2452+
".env": dedent`
2453+
WRANGLER_ENV_VAR_1=env-1
2454+
WRANGLER_ENV_VAR_2=env-2
2455+
`,
2456+
});
2457+
await helper.seed({
2458+
".env.local": dedent`
2459+
WRANGLER_ENV_VAR_2=local-2
2460+
WRANGLER_ENV_VAR_3=local-3
2461+
`,
2462+
});
2463+
2464+
const worker = helper.runLongLived("wrangler dev");
2465+
const { url } = await worker.waitForReady();
2466+
expect(await (await fetch(url)).text()).toMatchInlineSnapshot(`
2467+
"{
2468+
"WRANGLER_ENV_VAR_0": "default-0",
2469+
"WRANGLER_ENV_VAR_1": "env-1",
2470+
"WRANGLER_ENV_VAR_2": "local-2",
2471+
"WRANGLER_ENV_VAR_3": "local-3"
2472+
}"
2473+
`);
2474+
});
2475+
2476+
it("should prefer to load environment variables from .env.staging.local over .env.staging, etc", async () => {
2477+
const helper = new WranglerE2ETestHelper();
2478+
await helper.seed(seedFiles);
2479+
await helper.seed({
2480+
".env": dedent`
2481+
WRANGLER_ENV_VAR_1=env-1
2482+
WRANGLER_ENV_VAR_2=env-2
2483+
`,
2484+
});
2485+
await helper.seed({
2486+
".env.local": dedent`
2487+
WRANGLER_ENV_VAR_2=local-2
2488+
WRANGLER_ENV_VAR_3=local-3
2489+
`,
2490+
});
2491+
await helper.seed({
2492+
".env.staging": dedent`
2493+
WRANGLER_ENV_VAR_3=staging-3
2494+
WRANGLER_ENV_VAR_4=staging-4
2495+
`,
2496+
});
2497+
await helper.seed({
2498+
".env.staging.local": dedent`
2499+
WRANGLER_ENV_VAR_4=staging-local-4
2500+
WRANGLER_ENV_VAR_5=staging-local-5
2501+
`,
2502+
});
2503+
2504+
const worker = helper.runLongLived("wrangler dev --env=staging");
2505+
const { url } = await worker.waitForReady();
2506+
expect(await (await fetch(url)).text()).toMatchInlineSnapshot(`
2507+
"{
2508+
"WRANGLER_ENV_VAR_0": "default-0",
2509+
"WRANGLER_ENV_VAR_1": "env-1",
2510+
"WRANGLER_ENV_VAR_2": "local-2",
2511+
"WRANGLER_ENV_VAR_3": "staging-3",
2512+
"WRANGLER_ENV_VAR_4": "staging-local-4",
2513+
"WRANGLER_ENV_VAR_5": "staging-local-5"
2514+
}"
2515+
`);
2516+
});
2517+
2518+
it("should load environment variables from process.env if CLOUDFLARE_INCLUDE_PROCESS_ENV is true", async () => {
2519+
const helper = new WranglerE2ETestHelper();
2520+
await helper.seed(seedFiles);
2521+
await helper.seed({
2522+
".env": dedent`
2523+
WRANGLER_ENV_VAR_1=env-1
2524+
WRANGLER_ENV_VAR_2=env-2
2525+
`,
2526+
});
2527+
2528+
const worker = helper.runLongLived("wrangler dev", {
2529+
env: { CLOUDFLARE_INCLUDE_PROCESS_ENV: "true", ...process.env },
2530+
});
2531+
const { url } = await worker.waitForReady();
2532+
// We could dump out all the bindings but that would be a lot of noise, and also may change between OSes and runs.
2533+
// Instead, we know that the `CLOUDFLARE_INCLUDE_PROCESS_ENV` variable should be present, so we just check for that.
2534+
expect(await (await fetch(url)).text()).contains(
2535+
'"CLOUDFLARE_INCLUDE_PROCESS_ENV": "true"'
2536+
);
2537+
expect(await (await fetch(url)).text()).contains(
2538+
'"WRANGLER_ENV_VAR_0": "default-0"'
2539+
);
2540+
expect(await (await fetch(url)).text()).contains(
2541+
'"WRANGLER_ENV_VAR_1": "env-1"'
2542+
);
2543+
});
2544+
});

packages/wrangler/e2e/helpers/wrangler.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,6 @@ export async function runWrangler(
2525
return runCommand(getWranglerCommand(wranglerCommand), { cwd, env, timeout });
2626
}
2727

28-
export function runWranglerLongLived(
29-
wranglerCommand: string,
30-
options: WranglerCommandOptions = {}
31-
) {
32-
return new WranglerLongLivedCommand(wranglerCommand, options);
33-
}
34-
3528
export class WranglerLongLivedCommand extends LongLivedCommand {
3629
constructor(wranglerCommand: string, options: WranglerCommandOptions = {}) {
3730
super(getWranglerCommand(wranglerCommand), getOptions(options));

packages/wrangler/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
"date-fns": "^4.1.0",
117117
"devtools-protocol": "^0.0.1182435",
118118
"dotenv": "^16.3.1",
119+
"dotenv-expand": "^12.0.2",
119120
"execa": "^6.1.0",
120121
"find-up": "^6.3.0",
121122
"get-port": "^7.0.0",

0 commit comments

Comments
 (0)