Skip to content

watch: reload changes in contents of --env-file #54109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/internal/main/watch_mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
// TODO(MoLow): Make kill signal configurable
const kKillSignal = 'SIGTERM';
const kShouldFilterModules = getOptionValue('--watch-path').length === 0;
const kEnvFile = getOptionValue('--env-file')

Check failure on line 36 in lib/internal/main/watch_mode.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
const kWatchedPaths = ArrayPrototypeMap(getOptionValue('--watch-path'), (path) => resolve(path));
const kPreserveOutput = getOptionValue('--watch-preserve-output');
const kCommand = ArrayPrototypeSlice(process.argv, 1);
Expand Down Expand Up @@ -73,6 +74,9 @@
},
});
watcher.watchChildProcessModules(child);
if (kEnvFile) {
watcher.filterFile(resolve(kEnvFile))

Check failure on line 78 in lib/internal/main/watch_mode.js

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
}
child.once('exit', (code) => {
exited = true;
if (code === 0) {
Expand Down
5 changes: 4 additions & 1 deletion src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,10 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
}
#endif

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

Expand Down
110 changes: 110 additions & 0 deletions test/sequential/test-watch-mode.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,66 @@
return file;
}

function runInBackground({ args = [], options = {}, completed = 'Completed running', shouldFail = false }) {
let future = Promise.withResolvers()

Check failure on line 34 in test/sequential/test-watch-mode.mjs

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
let child

Check failure on line 35 in test/sequential/test-watch-mode.mjs

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
let stderr = '';
let stdout = [];

const run = () => {
args.unshift('--no-warnings');
child = spawn(execPath, args, { encoding: 'utf8', stdio: 'pipe', ...options });

child.stderr.on('data', (data) => {
stderr += data;
});

const rl = createInterface({ input: child.stdout })

Check failure on line 47 in test/sequential/test-watch-mode.mjs

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
rl.on('line', data => {

Check failure on line 48 in test/sequential/test-watch-mode.mjs

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Expected parentheses around arrow function argument
if (!data.startsWith('Waiting for graceful termination') && !data.startsWith('Gracefully restarted')) {
stdout.push(data);
if (data.startsWith(completed)) {
future.resolve({ stderr, stdout })

Check failure on line 52 in test/sequential/test-watch-mode.mjs

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
future = Promise.withResolvers()

Check failure on line 53 in test/sequential/test-watch-mode.mjs

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
stdout = []

Check failure on line 54 in test/sequential/test-watch-mode.mjs

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Missing semicolon
stderr = ""

Check failure on line 55 in test/sequential/test-watch-mode.mjs

View workflow job for this annotation

GitHub Actions / lint-js-and-md

Strings must use singlequote
} else if (data.startsWith('Failed running')) {
if (shouldFail) {
future.resolve({ stderr, stdout })
} else {
future.reject({ stderr, stdout })
}
future = Promise.withResolvers()
stdout = []
stderr = ""
}
}
})
}

return {
async done() {
child?.kill()
future.resolve()
return { stdout, stderr }
},
restart(timeout = 1000) {
if (!child) {
run()
}
const timer = setTimeout(() => {
if (!future.resolved) {
child.kill()
future.reject(new Error('Timed out waiting for restart'))
}
}, timeout)
return future.promise.finally(() => {
clearTimeout(timer)
})
}
}
}

async function runWriteSucceed({
file,
watchedFile,
Expand Down Expand Up @@ -132,6 +192,56 @@
]);
});

it('should reload env variables when --env-file changes', async () => {
const envKey = `TEST_ENV_${Date.now()}`
const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey});`)
const envFile = createTmpFile(`${envKey}=value1`, '.env')
const { done, restart } = runInBackground({ args: ['--watch', `--env-file=${envFile}`, jsFile] });

try {
await restart()
writeFileSync(envFile, `${envKey}=value2`);

//Second restart, after env change
const { stdout, stderr } = await restart()

assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
`Restarting ${inspect(jsFile)}`,
'ENV: value2',
`Completed running ${inspect(jsFile)}`,
]);
} finally {
await done()
}
})

it('should load new env variables when --env-file changes', async () => {
const envKey = `TEST_ENV_${Date.now()}`
const envKey2 = `TEST_ENV_2_${Date.now()}`
const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey} + '\\n' + 'ENV2: ' + process.env.${envKey2});`)
const envFile = createTmpFile(`${envKey}=value1`, '.env')
const { done, restart } = runInBackground({ args: ['--watch', `--env-file=${envFile}`, jsFile] });

try {
await restart()
await writeFileSync(envFile, `${envKey}=value1\n${envKey2}=newValue`);

//Second restart, after env change
const { stderr, stdout } = await restart()

assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
`Restarting ${inspect(jsFile)}`,
'ENV: value1',
'ENV2: newValue',
`Completed running ${inspect(jsFile)}`,
]);
} finally {
await done()
}
})

it('should watch changes to a failing file', async () => {
const file = createTmpFile('throw new Error("fails");');
const { stderr, stdout } = await runWriteSucceed({
Expand Down
Loading