Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
36 changes: 27 additions & 9 deletions lib/internal/main/watch_mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const {
ArrayPrototypePushApply,
ArrayPrototypeSlice,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeReplaceAll,
StringPrototypeSlice,
StringPrototypeStartsWith,
} = primordials;

Expand Down Expand Up @@ -41,9 +44,23 @@ const kPreserveOutput = getOptionValue('--watch-preserve-output');
const kCommand = ArrayPrototypeSlice(process.argv, 1);
const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' '));

// The C++ option parser treats `_` and `-` as equivalent in option names
// (e.g. --watch_path is --watch-path), so normalize before matching.
function normalizeOptionName(arg) {
if (!StringPrototypeStartsWith(arg, '--')) {
return arg;
}
const equalsIndex = StringPrototypeIndexOf(arg, '=');
if (equalsIndex === -1) {
return StringPrototypeReplaceAll(arg, '_', '-');
}
return StringPrototypeReplaceAll(StringPrototypeSlice(arg, 0, equalsIndex), '_', '-') +
StringPrototypeSlice(arg, equalsIndex);
}

const argsWithoutWatchOptions = [];
for (let i = 0; i < process.execArgv.length; i++) {
const arg = process.execArgv[i];
const arg = normalizeOptionName(process.execArgv[i]);
if (StringPrototypeStartsWith(arg, '--watch=')) {
continue;
}
Expand Down Expand Up @@ -74,7 +91,7 @@ for (let i = 0; i < process.execArgv.length; i++) {
if (arg === '--experimental-default-config-file') {
continue;
}
ArrayPrototypePush(argsWithoutWatchOptions, arg);
ArrayPrototypePush(argsWithoutWatchOptions, process.execArgv[i]);
}

ArrayPrototypePushApply(argsWithoutWatchOptions, kCommand);
Expand All @@ -88,15 +105,16 @@ if (kNodeOptions != null) {
const parts = parseNodeOptionsEnvVar(kNodeOptions);
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (part === '--watch' ||
part === '--watch-preserve-output' ||
StringPrototypeStartsWith(part, '--watch=') ||
StringPrototypeStartsWith(part, '--watch-preserve-output=') ||
StringPrototypeStartsWith(part, '--watch-path=') ||
StringPrototypeStartsWith(part, '--watch-kill-signal=')) {
const name = normalizeOptionName(part);
if (name === '--watch' ||
name === '--watch-preserve-output' ||
StringPrototypeStartsWith(name, '--watch=') ||
StringPrototypeStartsWith(name, '--watch-preserve-output=') ||
StringPrototypeStartsWith(name, '--watch-path=') ||
StringPrototypeStartsWith(name, '--watch-kill-signal=')) {
continue;
}
if (part === '--watch-path' || part === '--watch-kill-signal') {
if (name === '--watch-path' || name === '--watch-kill-signal') {
// Skip the flag and its separate value argument
i++;
continue;
Expand Down
54 changes: 54 additions & 0 deletions test/sequential/test-watch-mode.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,60 @@ process.on('message', (message) => {
}
});

it('should strip underscore aliases of watch flags from NODE_OPTIONS', async () => {
// Avoid recursively watching the repository's source and test trees.
const cwd = tmpdir.resolve('node-options-underscore');
mkdirSync(path.join(cwd, 'src'), { recursive: true });
mkdirSync(path.join(cwd, 'test'));
const file = createTmpFile('console.log(process.env.NODE_OPTIONS);');
const nodeOptions = [
'--watch_path=./src',
'--watch_path', './test',
'--watch_preserve_output',
'--watch_preserve_output=true',
'--watch_kill_signal=SIGKILL',
'--watch_kill_signal', 'SIGINT',
'--max_old_space_size=4096',
'--no-warnings',
].join(' ');
const { done, restart } = runInBackground({
args: ['--watch', file],
options: {
cwd,
env: { ...process.env, NODE_OPTIONS: nodeOptions },
},
});

try {
const { stdout, stderr } = await restart();

assert.strictEqual(stderr, '');
const nodeOptionsLine = stdout.find((line) => line.includes('--max_old_space_size'));
assert.ok(nodeOptionsLine);
assert.strictEqual(nodeOptionsLine, '--max_old_space_size=4096 --no-warnings');
} finally {
await done();
}
});

it('should strip --watch_path from execArgv in child process', async () => {
const dir = tmpdir.resolve('exec-argv-underscore');
mkdirSync(dir, { recursive: true });
const file = createTmpFile('console.log(JSON.stringify(process.execArgv));');
const { done, restart } = runInBackground({
args: [`--watch_path=${dir}`, file],
});

try {
const { stdout, stderr } = await restart();

assert.strictEqual(stderr, '');
assert.ok(stdout.includes('["--no-warnings"]'));
} finally {
await done();
}
});

it('should not strip --watch when it appears inside a quoted NODE_OPTIONS value', {
// Honoring --require from NODE_OPTIONS is required for this test.
skip: !!process.config.variables.node_without_node_options,
Expand Down
Loading