Skip to content

Commit 32ba91a

Browse files
vicbjames-elicx
andauthored
feat: retrieve CLI environment variables from process.env and .env* files (#937)
Co-authored-by: James Anderson <[email protected]>
1 parent 54c47e5 commit 32ba91a

File tree

10 files changed

+231
-127
lines changed

10 files changed

+231
-127
lines changed

.changeset/quiet-snails-hope.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@opennextjs/cloudflare": minor
3+
---
4+
5+
feat: retrieve CLI environment variables from `process.env` and `.env*` files
6+
7+
Recommended usage on CI:
8+
9+
- Add your secrets to `process.env` (i.e. `CF_ACCOUNT_ID`)
10+
- Add public values to the wrangler config `wrangler.jsonc` (i.e. `R2_CACHE_PREFIX_ENV_NAME`)
11+
12+
Recommended usage for local dev:
13+
14+
- Add your secrets to either a `.dev.vars*` or `.env*` file (i.e. `CF_ACCOUNT_ID`)
15+
- Add public values to the wrangler config `wrangler.jsonc` (i.e. `R2_CACHE_PREFIX_ENV_NAME`)

packages/cloudflare/src/api/cloudflare-context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@ async function getCloudflareContextFromWrangler<
340340

341341
const { env, cf, ctx } = await getPlatformProxy({
342342
...options,
343+
// The `env` passed to the fetch handler does not contain variables from `.env*` files.
344+
// because we invoke wrangler with `CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV`=`"false"`.
345+
// Initializing `envFiles` with an empty list is the equivalent for this API call.
346+
envFiles: [],
343347
environment,
344348
});
345349
return {

packages/cloudflare/src/cli/build/utils/extract-project-env-vars.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { BuildOptions } from "@opennextjs/aws/build/helper.js";
66

77
function readEnvFile(filePath: string) {
88
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
9-
return parse(fs.readFileSync(filePath).toString());
9+
return parse(fs.readFileSync(filePath, "utf-8"));
1010
}
1111
}
1212

packages/cloudflare/src/cli/commands/deploy.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,30 @@ export async function deployCommand(args: WithWranglerArgs<{ cacheChunkSize?: nu
2323
printHeaders("deploy");
2424

2525
const { config } = await retrieveCompiledConfig();
26-
const options = getNormalizedOptions(config);
26+
const buildOpts = getNormalizedOptions(config);
2727

2828
const wranglerConfig = readWranglerConfig(args);
2929

30-
const envVars = await getEnvFromPlatformProxy({
31-
configPath: args.wranglerConfigPath,
32-
environment: args.env,
33-
});
30+
const envVars = await getEnvFromPlatformProxy(config, buildOpts);
3431

35-
const deploymentMapping = await getDeploymentMapping(options, config, envVars);
32+
await populateCache(
33+
buildOpts,
34+
config,
35+
wranglerConfig,
36+
{
37+
target: "remote",
38+
environment: args.env,
39+
wranglerConfigPath: args.wranglerConfigPath,
40+
cacheChunkSize: args.cacheChunkSize,
41+
shouldUsePreviewId: false,
42+
},
43+
envVars
44+
);
3645

37-
await populateCache(options, config, wranglerConfig, {
38-
target: "remote",
39-
environment: args.env,
40-
wranglerConfigPath: args.wranglerConfigPath,
41-
cacheChunkSize: args.cacheChunkSize,
42-
shouldUsePreviewId: false,
43-
});
46+
const deploymentMapping = await getDeploymentMapping(buildOpts, config, envVars);
4447

4548
runWrangler(
46-
options,
49+
buildOpts,
4750
[
4851
"deploy",
4952
...args.wranglerArgs,

packages/cloudflare/src/cli/commands/helpers.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,75 @@
1+
import { type BuildOptions } from "@opennextjs/aws/build/helper.js";
12
import { getPlatformProxy, type GetPlatformProxyOptions } from "wrangler";
23

4+
import { extractProjectEnvVars } from "../build/utils/extract-project-env-vars.js";
5+
36
export type WorkerEnvVar = Record<keyof CloudflareEnv, string | undefined>;
47

58
/**
6-
* Return the string env vars from the worker.
9+
* Returns the env vars to use by the CLI.
10+
*
11+
* The environments variables are returned from a combination of `process.env`, wrangler config, and `.env*` files.
12+
*
13+
* Recommended usage on CI:
14+
*
15+
* - Add you secrets to `process.env` (i.e. `CF_ACCOUNT_ID`)
16+
* - Add public values to the wrangler config `wrangler.jsonc` (i.e. `R2_CACHE_PREFIX_ENV_NAME`)
17+
*
18+
* Note: `.dev.vars*` and `.env*` should not be checked in.
19+
*
20+
* Recommended usage for local dev:
21+
*
22+
* - Add you secrets to either a `.dev.vars*` or `.env*` file (i.e. `CF_ACCOUNT_ID`)
23+
* - Add public values to the wrangler config `wrangler.jsonc` (i.e. `R2_CACHE_PREFIX_ENV_NAME`)
24+
*
25+
* Note: `.env*` files are also used by `next dev` while `.dev.var*` files are only loaded by `wrangler`.
26+
*
27+
* Loading details:
28+
*
29+
* 1. The variables are first initialized from `process.env`
30+
* 2. They are then augmented/replaced with variables from the wrangler config (`wrangler.jsonc` and `.dev.vars*`)
31+
* 3. They are then augmented with variables from `.env*` files (existing values are not replaced).
732
*
833
* @param options Options to pass to `getPlatformProxy`, i.e. to set the environment
34+
* @param buildOpts Open Next build options
935
* @returns the env vars
1036
*/
11-
export async function getEnvFromPlatformProxy(options: GetPlatformProxyOptions) {
12-
const envVars = {} as WorkerEnvVar;
13-
const proxy = await getPlatformProxy<CloudflareEnv>(options);
37+
export async function getEnvFromPlatformProxy(options: GetPlatformProxyOptions, buildOpts: BuildOptions) {
38+
// 1. Start from `process.env`
39+
const envVars = process.env;
40+
41+
// 2. Apply vars from workers `env`
42+
const proxy = await getPlatformProxy<CloudflareEnv>({
43+
...options,
44+
// Next.js uses a different mechanism to load `.env*` files from wrangler.
45+
// We prevent wrangler for loading the files and handle that in `getEnvFromPlatformProxy`.
46+
envFiles: [],
47+
});
48+
1449
Object.entries(proxy.env).forEach(([key, value]) => {
1550
if (typeof value === "string") {
51+
// filter out bindings by only considering string values
1652
envVars[key as keyof CloudflareEnv] = value;
1753
}
1854
});
55+
1956
await proxy.dispose();
20-
return envVars;
57+
58+
// 3. Apply new vars from `.env*` files
59+
let mode: "production" | "development" | "test" = "production";
60+
if (envVars.NEXTJS_ENV === "development") {
61+
mode = "development";
62+
} else if (envVars.NEXTJS_ENV === "test") {
63+
mode = "test";
64+
}
65+
66+
const dotEnvVars = extractProjectEnvVars(mode, buildOpts);
67+
68+
for (const varName in dotEnvVars) {
69+
envVars[varName] ??= dotEnvVars[varName];
70+
}
71+
72+
return envVars as unknown as WorkerEnvVar;
2173
}
2274

2375
/**

0 commit comments

Comments
 (0)