Skip to content

Commit 4d8b53e

Browse files
marekpiechuttargos
authored andcommitted
watch: reload changes in contents of --env-file
Make sure we watch and reload on env file changes. Ignore env file in parent process, so child process can reload current vars when we recreate it. Fixes: #54001 PR-URL: #54109 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent e218b7c commit 4d8b53e

File tree

3 files changed

+118
-1
lines changed

3 files changed

+118
-1
lines changed

Diff for: lib/internal/main/watch_mode.js

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ markBootstrapComplete();
3333
// TODO(MoLow): Make kill signal configurable
3434
const kKillSignal = 'SIGTERM';
3535
const kShouldFilterModules = getOptionValue('--watch-path').length === 0;
36+
const kEnvFile = getOptionValue('--env-file');
3637
const kWatchedPaths = ArrayPrototypeMap(getOptionValue('--watch-path'), (path) => resolve(path));
3738
const kPreserveOutput = getOptionValue('--watch-preserve-output');
3839
const kCommand = ArrayPrototypeSlice(process.argv, 1);
@@ -73,6 +74,9 @@ function start() {
7374
},
7475
});
7576
watcher.watchChildProcessModules(child);
77+
if (kEnvFile) {
78+
watcher.filterFile(resolve(kEnvFile));
79+
}
7680
child.once('exit', (code) => {
7781
exited = true;
7882
if (code === 0) {

Diff for: src/node.cc

+4-1
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,10 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
379379
}
380380
#endif
381381

382-
if (env->options()->has_env_file_string) {
382+
// Ignore env file if we're in watch mode.
383+
// Without it env is not updated when restarting child process.
384+
// Child process has --watch flag removed, so it will load the file.
385+
if (env->options()->has_env_file_string && !env->options()->watch_mode) {
383386
per_process::dotenv_file.SetEnvironment(env);
384387
}
385388

Diff for: test/sequential/test-watch-mode.mjs

+110
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,66 @@ function createTmpFile(content = 'console.log("running");', ext = '.js', basenam
3030
return file;
3131
}
3232

33+
function runInBackground({ args = [], options = {}, completed = 'Completed running', shouldFail = false }) {
34+
let future = Promise.withResolvers();
35+
let child;
36+
let stderr = '';
37+
let stdout = [];
38+
39+
const run = () => {
40+
args.unshift('--no-warnings');
41+
child = spawn(execPath, args, { encoding: 'utf8', stdio: 'pipe', ...options });
42+
43+
child.stderr.on('data', (data) => {
44+
stderr += data;
45+
});
46+
47+
const rl = createInterface({ input: child.stdout });
48+
rl.on('line', (data) => {
49+
if (!data.startsWith('Waiting for graceful termination') && !data.startsWith('Gracefully restarted')) {
50+
stdout.push(data);
51+
if (data.startsWith(completed)) {
52+
future.resolve({ stderr, stdout });
53+
future = Promise.withResolvers();
54+
stdout = [];
55+
stderr = '';
56+
} else if (data.startsWith('Failed running')) {
57+
if (shouldFail) {
58+
future.resolve({ stderr, stdout });
59+
} else {
60+
future.reject({ stderr, stdout });
61+
}
62+
future = Promise.withResolvers();
63+
stdout = [];
64+
stderr = '';
65+
}
66+
}
67+
});
68+
};
69+
70+
return {
71+
async done() {
72+
child?.kill();
73+
future.resolve();
74+
return { stdout, stderr };
75+
},
76+
restart(timeout = 1000) {
77+
if (!child) {
78+
run();
79+
}
80+
const timer = setTimeout(() => {
81+
if (!future.resolved) {
82+
child.kill();
83+
future.reject(new Error('Timed out waiting for restart'));
84+
}
85+
}, timeout);
86+
return future.promise.finally(() => {
87+
clearTimeout(timer);
88+
});
89+
}
90+
};
91+
}
92+
3393
async function runWriteSucceed({
3494
file,
3595
watchedFile,
@@ -132,6 +192,56 @@ describe('watch mode', { concurrency: !process.env.TEST_PARALLEL, timeout: 60_00
132192
]);
133193
});
134194

195+
it('should reload env variables when --env-file changes', async () => {
196+
const envKey = `TEST_ENV_${Date.now()}`;
197+
const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey});`);
198+
const envFile = createTmpFile(`${envKey}=value1`, '.env');
199+
const { done, restart } = runInBackground({ args: ['--watch', `--env-file=${envFile}`, jsFile] });
200+
201+
try {
202+
await restart();
203+
writeFileSync(envFile, `${envKey}=value2`);
204+
205+
// Second restart, after env change
206+
const { stdout, stderr } = await restart();
207+
208+
assert.strictEqual(stderr, '');
209+
assert.deepStrictEqual(stdout, [
210+
`Restarting ${inspect(jsFile)}`,
211+
'ENV: value2',
212+
`Completed running ${inspect(jsFile)}`,
213+
]);
214+
} finally {
215+
await done();
216+
}
217+
});
218+
219+
it('should load new env variables when --env-file changes', async () => {
220+
const envKey = `TEST_ENV_${Date.now()}`;
221+
const envKey2 = `TEST_ENV_2_${Date.now()}`;
222+
const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey} + '\\n' + 'ENV2: ' + process.env.${envKey2});`);
223+
const envFile = createTmpFile(`${envKey}=value1`, '.env');
224+
const { done, restart } = runInBackground({ args: ['--watch', `--env-file=${envFile}`, jsFile] });
225+
226+
try {
227+
await restart();
228+
await writeFileSync(envFile, `${envKey}=value1\n${envKey2}=newValue`);
229+
230+
// Second restart, after env change
231+
const { stderr, stdout } = await restart();
232+
233+
assert.strictEqual(stderr, '');
234+
assert.deepStrictEqual(stdout, [
235+
`Restarting ${inspect(jsFile)}`,
236+
'ENV: value1',
237+
'ENV2: newValue',
238+
`Completed running ${inspect(jsFile)}`,
239+
]);
240+
} finally {
241+
await done();
242+
}
243+
});
244+
135245
it('should watch changes to a failing file', async () => {
136246
const file = createTmpFile('throw new Error("fails");');
137247
const { stderr, stdout } = await runWriteSucceed({

0 commit comments

Comments
 (0)