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
26 changes: 21 additions & 5 deletions lib/internal/main/watch_mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
ArrayPrototypePush,
ArrayPrototypePushApply,
ArrayPrototypeSlice,
JSONStringify,
StringPrototypeStartsWith,
} = primordials;

Expand Down Expand Up @@ -44,18 +45,22 @@ const kCommand = ArrayPrototypeSlice(process.argv, 1);
const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' '));

const argsWithoutWatchOptions = [];
const removedWatchFlags = [];
const argsFromBinding = getOptionsAsFlagsFromBinding();
for (let i = 0; i < argsFromBinding.length; i++) {
const arg = argsFromBinding[i];
if (StringPrototypeStartsWith(arg, '--watch=')) {
ArrayPrototypePush(removedWatchFlags, arg);
continue;
}
if (arg === '--watch') {
ArrayPrototypePush(removedWatchFlags, arg);
const nextArg = argsFromBinding[i + 1];
if (nextArg && nextArg[0] !== '-') {
// If `--watch` doesn't include `=` and the next
// argument is not a flag then it is interpreted as
// the watch argument, so we need to skip that as well
ArrayPrototypePush(removedWatchFlags, nextArg);
i++;
}
continue;
Expand All @@ -66,7 +71,14 @@ for (let i = 0; i < argsFromBinding.length; i++) {
// if --watch-path doesn't include `=` it means
// that the next arg is the target path, so we
// need to skip that as well
i++;
ArrayPrototypePush(removedWatchFlags, arg);
const nextArg = argsFromBinding[i + 1];
if (nextArg) {
ArrayPrototypePush(removedWatchFlags, nextArg);
i++;
}
} else {
ArrayPrototypePush(removedWatchFlags, arg);
}
continue;
}
Expand Down Expand Up @@ -95,12 +107,16 @@ let exited;
function start() {
exited = false;
const stdio = kShouldFilterModules ? ['inherit', 'inherit', 'inherit', 'ipc'] : 'inherit';
const env = {
...process.env,
WATCH_REPORT_DEPENDENCIES: '1',
};
if (removedWatchFlags.length > 0) {
env.NODE_WATCH_ARGS = JSONStringify(removedWatchFlags);
}
child = spawn(process.execPath, argsWithoutWatchOptions, {
stdio,
env: {
...process.env,
WATCH_REPORT_DEPENDENCIES: '1',
},
env,
});
watcher.watchChildProcessModules(child);
if (kEnvFiles.length > 0) {
Expand Down
16 changes: 16 additions & 0 deletions lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
'use strict';

const {
ArrayIsArray,
ArrayPrototypeForEach,
ArrayPrototypeUnshiftApply,
Date,
DatePrototypeGetDate,
DatePrototypeGetFullYear,
DatePrototypeGetHours,
DatePrototypeGetMinutes,
DatePrototypeGetMonth,
DatePrototypeGetSeconds,
JSONParse,
NumberParseInt,
ObjectDefineProperty,
ObjectFreeze,
Expand Down Expand Up @@ -270,6 +273,19 @@ function patchProcessObject(expandArgv1) {
process._exiting = false;
process.argv[0] = process.execPath;

const watchArgsFromLauncher = process.env.NODE_WATCH_ARGS;
if (watchArgsFromLauncher !== undefined) {
delete process.env.NODE_WATCH_ARGS;
try {
const parsed = JSONParse(watchArgsFromLauncher);
if (ArrayIsArray(parsed) && parsed.length > 0) {
ArrayPrototypeUnshiftApply(process.execArgv, parsed);
}
} catch {
// Ignore malformed payloads.
}
}

/** @type {string} */
let mainEntry;
// If requested, update process.argv[1] to replace whatever the user provided with the resolved absolute file path of
Expand Down
48 changes: 48 additions & 0 deletions test/sequential/test-watch-mode-watch-flags.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,52 @@ describe('watch mode - watch flags', { concurrency: !process.env.TEST_PARALLEL,
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});

it('exposes watch flags through process.execArgv inside the watched script', async () => {
const projectDir = tmpdir.resolve('project-watch-exec-argv');
mkdirSync(projectDir);

const file = createTmpFile(`
console.log(JSON.stringify(process.execArgv));
`, '.js', projectDir);
const watchPath = path.join(projectDir, 'template.html');
writeFileSync(watchPath, '');

async function assertExecArgv(args, expectedSubsequences) {
const { stdout, stderr } = await runNode({
args, options: { cwd: projectDir }
});

assert.strictEqual(stderr, '');

const execArgvLine = stdout[0];
const execArgv = JSON.parse(execArgvLine);
assert.ok(Array.isArray(execArgv));
const matched = expectedSubsequences.some((expectedSeq) => {
for (let i = 0; i <= execArgv.length - expectedSeq.length; i++) {
let ok = true;
for (let j = 0; j < expectedSeq.length; j++) {
if (execArgv[i + j] !== expectedSeq[j]) {
ok = false;
break;
}
}
if (ok) return true;
}
return false;
});
assert.ok(matched,
`execArgv (${execArgv}) does not contain any expected sequence (${expectedSubsequences.map((seq) => `[${seq}]`).join(', ')})`);
assert.match(stdout.at(-1), /^Completed running/);
}

await assertExecArgv(['--watch', file], [['--watch']]);
await assertExecArgv(['--watch-path=template.html', file], [['--watch-path=template.html']]);
await assertExecArgv(
['--watch-path', 'template.html', file],
[
['--watch-path', 'template.html'],
['--watch-path=template.html'],
]);
});
});
Loading