Skip to content

Commit cabdfd0

Browse files
fix: avoid overwriting existing env vars (#583)
Co-authored-by: WillBooster (Codex CLI) <agent@willbooster.com>
1 parent 97075dc commit cabdfd0

File tree

2 files changed

+61
-14
lines changed

2 files changed

+61
-14
lines changed

packages/shared-lib-node/src/env.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,35 +82,35 @@ export function readEnvironmentVariables(
8282
console.info('Reading env files:', envPaths.join(', '));
8383
}
8484

85-
const envPathAndEnvVarCountPairs: [string, string[]][] = [];
85+
const envPathAndLoadedEnvVarNames: [string, string[]][] = [];
8686
const envVars: Record<string, string> = {};
8787
for (const envPath of envPaths) {
8888
const keys: string[] = [];
8989
for (const [key, value] of Object.entries(readEnvFile(path.join(cwd, envPath)))) {
90-
if (!(key in envVars)) {
90+
if (!(key in envVars) && !(key in process.env)) {
9191
envVars[key] = value;
9292
keys.push(key);
9393
}
9494
}
95-
envPathAndEnvVarCountPairs.push([envPath, keys]);
95+
envPathAndLoadedEnvVarNames.push([envPath, keys]);
9696
if (argv.verbose && keys.length > 0) {
9797
console.info(`Read ${keys.length} environment variables from ${envPath}`);
9898
}
9999
}
100100
if (!argv.verbose) {
101101
console.info(
102-
`Read: ${envPathAndEnvVarCountPairs.map(([envPath, keys]) => `${envPath} (${keys.join(', ')})`).join(', ')}`
102+
`Read: ${envPathAndLoadedEnvVarNames.map(([envPath, keys]) => `${envPath} (${keys.join(', ')})`).join(', ')}`
103103
);
104104
}
105105

106106
if (argv.checkEnv) {
107107
const exampleKeys = Object.keys(readEnvFile(path.join(cwd, argv.checkEnv)));
108-
const missingKeys = exampleKeys.filter((key) => !(key in envVars));
108+
const missingKeys = exampleKeys.filter((key) => !(key in envVars) && !(key in process.env));
109109
if (missingKeys.length > 0) {
110110
throw new Error(`Missing environment variables in [${envPaths.join(', ')}]: [${missingKeys.join(', ')}]`);
111111
}
112112
}
113-
return [expand({ parsed: envVars, processEnv: {} }).parsed ?? envVars, envPathAndEnvVarCountPairs];
113+
return [expand({ parsed: envVars, processEnv: {} }).parsed ?? envVars, envPathAndLoadedEnvVarNames];
114114
}
115115

116116
/**
@@ -121,7 +121,11 @@ export function readAndApplyEnvironmentVariables(
121121
cwd: string
122122
): Record<string, string | undefined> {
123123
const [envVars] = readEnvironmentVariables(argv, cwd);
124-
Object.assign(process.env, envVars);
124+
for (const [key, value] of Object.entries(envVars)) {
125+
if (!(key in process.env)) {
126+
process.env[key] = value;
127+
}
128+
}
125129
return envVars;
126130
}
127131

packages/shared-lib-node/test/env.test.ts

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
1-
import { beforeEach, describe, expect, it } from 'vitest';
1+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
22

3-
import { readAndApplyEnvironmentVariables } from '../src/env.js';
3+
import { readAndApplyEnvironmentVariables, readEnvironmentVariables } from '../src/env.js';
44

5-
describe('readAndApplyEnvironmentVariables()', () => {
6-
beforeEach(() => {
7-
process.env.WB_ENV = '';
8-
process.env.NODE_ENV = '';
9-
});
5+
const originalEnv = { ...process.env };
6+
7+
beforeEach(() => {
8+
process.env.WB_ENV = '';
9+
process.env.NODE_ENV = '';
10+
// Clear env vars that could affect env loading behavior in tests.
11+
12+
delete process.env.PORT;
13+
14+
delete process.env.NAME;
15+
});
16+
17+
afterEach(() => {
18+
for (const key of Object.keys(process.env)) {
19+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
20+
delete process.env[key];
21+
}
22+
Object.assign(process.env, originalEnv);
23+
});
1024

25+
describe('readAndApplyEnvironmentVariables()', () => {
1126
it('should load no env vars with empty options', () => {
1227
const envVars = readAndApplyEnvironmentVariables({}, 'test/fixtures/app1');
1328
expect(envVars).toEqual({});
@@ -44,4 +59,32 @@ describe('readAndApplyEnvironmentVariables()', () => {
4459
);
4560
expect(envVars).toEqual({ ENV: 'test2', PORT: '4002', NAME: 'app2' });
4661
});
62+
63+
it('should not overwrite existing process.env values', () => {
64+
process.env.PORT = '9999';
65+
process.env.NAME = 'override';
66+
const envVars = readAndApplyEnvironmentVariables({ autoCascadeEnv: true }, 'test/fixtures/app1');
67+
expect(envVars).toEqual({ ENV: 'development1' });
68+
expect(process.env.ENV).toBe('development1');
69+
expect(process.env.PORT).toBe('9999');
70+
expect(process.env.NAME).toBe('override');
71+
});
72+
});
73+
74+
describe('readEnvironmentVariables()', () => {
75+
it('should skip existing process.env values in env vars and env files list', () => {
76+
process.env.PORT = '9999';
77+
process.env.NAME = 'override';
78+
const [envVars, envPathAndLoadedEnvVarNames] = readEnvironmentVariables(
79+
{ autoCascadeEnv: true },
80+
'test/fixtures/app1'
81+
);
82+
expect(envVars).toEqual({ ENV: 'development1' });
83+
expect(envPathAndLoadedEnvVarNames).toEqual([
84+
['.env.development', ['ENV']],
85+
['.env', []],
86+
]);
87+
expect(process.env.PORT).toBe('9999');
88+
expect(process.env.NAME).toBe('override');
89+
});
4790
});

0 commit comments

Comments
 (0)