Skip to content

Commit 1ad7570

Browse files
feat: add support for reading build time env variables from a .env file (#882)
This change will automatically load up a `.env` file, if found, and apply its values to the current environment. An example would be to provide a specific CLOUDFLARE_ACCOUNT_ID value. Related to #190
1 parent bae5ba4 commit 1ad7570

File tree

10 files changed

+155
-130
lines changed

10 files changed

+155
-130
lines changed

.changeset/spotty-waves-think.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
feat: add support for reading build time env variables from a `.env` file
6+
7+
This change will automatically load up a `.env` file, if found, and apply its
8+
values to the current environment. An example would be to provide a specific
9+
CLOUDFLARE_ACCOUNT_ID value.
10+
11+
Related to cloudflare#190

examples/local-mode-tests/.env

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FOO="the value of foo"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name = "local-mode-tests"
2+
compatibility_date = "2022-03-27"
3+
4+
# This custom build command will show whether the FOO
5+
# environment variable was read from the `.env` file.
6+
build.command = "node -e \"console.log(process.env.FOO)\""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { spawnWranglerDev } from "./helpers";
2+
3+
it("should use the environment variable from the .env file", async () => {
4+
const { wranglerProcess, fetchWhenReady, terminateProcess } =
5+
spawnWranglerDev("src/module.ts", "src/wrangler.dotenv.toml", 9002);
6+
7+
try {
8+
await fetchWhenReady("http://localhost");
9+
expect(wranglerProcess.stdout?.read().toString()).toContain(
10+
"the value of foo"
11+
);
12+
} finally {
13+
await terminateProcess();
14+
}
15+
});
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { spawn } from "node:child_process";
2+
import { fetch } from "undici";
3+
import type { Response } from "undici";
4+
5+
const isWindows = process.platform === "win32";
6+
7+
export async function sleep(ms: number): Promise<void> {
8+
await new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
9+
}
10+
11+
/**
12+
* Spawn a child process that is running `wrangler dev`.
13+
*
14+
* @returns two helper functions:
15+
* - `fetchWhenReady()` will run a fetch against the preview Worker, once it is up and running,
16+
* and return its response.
17+
* - `terminateProcess()` send a signal to the `wrangler dev` child process to kill itself.
18+
*/
19+
export function spawnWranglerDev(
20+
srcPath: string,
21+
wranglerTomlPath: string,
22+
port: number
23+
) {
24+
const wranglerProcess = spawn(
25+
"npx",
26+
[
27+
"wrangler",
28+
"dev",
29+
srcPath,
30+
"--local",
31+
"--config",
32+
wranglerTomlPath,
33+
"--port",
34+
port.toString(),
35+
],
36+
{
37+
shell: isWindows,
38+
stdio: "pipe",
39+
}
40+
);
41+
42+
const fetchWhenReady = async (url: string): Promise<Response> => {
43+
const MAX_ATTEMPTS = 50;
44+
const SLEEP_MS = 100;
45+
let attempts = MAX_ATTEMPTS;
46+
while (attempts-- > 0) {
47+
await sleep(SLEEP_MS);
48+
try {
49+
return await fetch(`${url}:${port}`);
50+
} catch {}
51+
}
52+
throw new Error(
53+
`Failed to connect to "${url}:${port}" within ${
54+
(MAX_ATTEMPTS * SLEEP_MS) / 1000
55+
} seconds.`
56+
);
57+
};
58+
59+
const terminateProcess = () => {
60+
return new Promise((resolve, reject) => {
61+
wranglerProcess.once("exit", (code) => {
62+
if (!code) {
63+
resolve(code);
64+
} else {
65+
reject(code);
66+
}
67+
});
68+
wranglerProcess.kill();
69+
});
70+
};
71+
72+
return {
73+
wranglerProcess,
74+
fetchWhenReady,
75+
terminateProcess,
76+
};
77+
}
+13-65
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,16 @@
1-
import { spawn } from "child_process";
2-
import { fetch } from "undici";
3-
import type { ChildProcess } from "child_process";
4-
import type { Response } from "undici";
5-
6-
const waitUntilReady = async (url: string): Promise<Response> => {
7-
let response: Response | undefined = undefined;
8-
9-
while (response === undefined) {
10-
await new Promise((resolvePromise) => setTimeout(resolvePromise, 100));
11-
12-
try {
13-
response = await fetch(url);
14-
} catch {}
15-
}
16-
17-
return response as Response;
18-
};
19-
const isWindows = process.platform === "win32";
20-
21-
let wranglerProcess: ChildProcess;
22-
23-
beforeAll(async () => {
24-
// These tests break in CI for windows, so we're disabling them for now
25-
if (isWindows) return;
26-
27-
wranglerProcess = spawn(
28-
"npx",
29-
[
30-
"wrangler",
31-
"dev",
32-
"src/module.ts",
33-
"--local",
34-
"--config",
35-
"src/wrangler.module.toml",
36-
"--port",
37-
"9001",
38-
],
39-
{
40-
shell: isWindows,
41-
stdio: "inherit",
42-
}
43-
);
44-
});
45-
46-
afterAll(async () => {
47-
// These tests break in CI for windows, so we're disabling them for now
48-
if (isWindows) return;
49-
50-
await new Promise((resolve, reject) => {
51-
wranglerProcess.once("exit", (code) => {
52-
if (!code) {
53-
resolve(code);
54-
} else {
55-
reject(code);
56-
}
57-
});
58-
wranglerProcess.kill();
59-
});
60-
});
1+
import { spawnWranglerDev } from "./helpers";
612

623
it("renders", async () => {
63-
// These tests break in CI for windows, so we're disabling them for now
64-
if (isWindows) return;
4+
const { fetchWhenReady, terminateProcess } = spawnWranglerDev(
5+
"src/module.ts",
6+
"src/wrangler.module.toml",
7+
9001
8+
);
659

66-
const response = await waitUntilReady("http://localhost:9001/");
67-
const text = await response.text();
68-
expect(text).toMatchInlineSnapshot(`
10+
try {
11+
const response = await fetchWhenReady("http://localhost");
12+
const text = await response.text();
13+
expect(text).toMatchInlineSnapshot(`
6914
"{
7015
\\"VAR1\\": \\"value1\\",
7116
\\"VAR2\\": 123,
@@ -76,4 +21,7 @@ it("renders", async () => {
7621
\\"data\\": \\"Here be some data\\"
7722
}"
7823
`);
24+
} finally {
25+
await terminateProcess();
26+
}
7927
});
+13-65
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,16 @@
1-
import { spawn } from "child_process";
2-
import { fetch } from "undici";
3-
import type { ChildProcess } from "child_process";
4-
import type { Response } from "undici";
5-
6-
const waitUntilReady = async (url: string): Promise<Response> => {
7-
let response: Response | undefined = undefined;
8-
9-
while (response === undefined) {
10-
await new Promise((resolvePromise) => setTimeout(resolvePromise, 100));
11-
12-
try {
13-
response = await fetch(url);
14-
} catch {}
15-
}
16-
17-
return response as Response;
18-
};
19-
const isWindows = process.platform === "win32";
20-
21-
let wranglerProcess: ChildProcess;
22-
23-
beforeAll(async () => {
24-
// These tests break in CI for windows, so we're disabling them for now
25-
if (isWindows) return;
26-
27-
wranglerProcess = spawn(
28-
"npx",
29-
[
30-
"wrangler",
31-
"dev",
32-
"src/sw.ts",
33-
"--local",
34-
"--config",
35-
"src/wrangler.sw.toml",
36-
"--port",
37-
"9002",
38-
],
39-
{
40-
shell: isWindows,
41-
stdio: "inherit",
42-
}
43-
);
44-
});
45-
46-
afterAll(async () => {
47-
// These tests break in CI for windows, so we're disabling them for now
48-
if (isWindows) return;
49-
50-
await new Promise((resolve, reject) => {
51-
wranglerProcess.once("exit", (code) => {
52-
if (!code) {
53-
resolve(code);
54-
} else {
55-
reject(code);
56-
}
57-
});
58-
wranglerProcess.kill();
59-
});
60-
});
1+
import { spawnWranglerDev } from "./helpers";
612

623
it("renders", async () => {
63-
// These tests break in CI for windows, so we're disabling them for now
64-
if (isWindows) return;
4+
const { fetchWhenReady, terminateProcess } = spawnWranglerDev(
5+
"src/sw.ts",
6+
"src/wrangler.sw.toml",
7+
9000
8+
);
659

66-
const response = await waitUntilReady("http://localhost:9002/");
67-
const text = await response.text();
68-
expect(text).toMatchInlineSnapshot(`
10+
try {
11+
const response = await fetchWhenReady("http://localhost");
12+
const text = await response.text();
13+
expect(text).toMatchInlineSnapshot(`
6914
"{
7015
\\"VAR1\\": \\"value1\\",
7116
\\"VAR2\\": 123,
@@ -78,4 +23,7 @@ it("renders", async () => {
7823
\\"DATA\\": \\"Here be some data\\"
7924
}"
8025
`);
26+
} finally {
27+
await terminateProcess();
28+
}
8129
});

package-lock.json

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/wrangler/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"cmd-shim": "^4.1.0",
7070
"command-exists": "^1.2.9",
7171
"devtools-protocol": "^0.0.955664",
72+
"dotenv": "^16.0.0",
7273
"execa": "^6.1.0",
7374
"faye-websocket": "^0.11.4",
7475
"finalhandler": "^1.2.0",

packages/wrangler/src/cli.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import "dotenv/config"; // Grab locally specified env params from a `.env` file.
12
import process from "process";
23
import { hideBin } from "yargs/helpers";
34
import { FatalError } from "./errors";

0 commit comments

Comments
 (0)