diff --git a/lib/internal/main/watch_mode.js b/lib/internal/main/watch_mode.js index 06c2c8602da444..48f78af26e6ff7 100644 --- a/lib/internal/main/watch_mode.js +++ b/lib/internal/main/watch_mode.js @@ -7,6 +7,7 @@ const { ArrayPrototypePush, ArrayPrototypePushApply, ArrayPrototypeSlice, + JSONStringify, StringPrototypeStartsWith, } = primordials; @@ -44,17 +45,22 @@ const kCommand = ArrayPrototypeSlice(process.argv, 1); const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' ')); const argsWithoutWatchOptions = []; -for (let i = 0; i < process.execArgv.length; i++) { - const arg = process.execArgv[i]; +const removedWatchFlags = []; +const args = process.execArgv; +for (let i = 0; i < args.length; i++) { + const arg = args[i]; if (StringPrototypeStartsWith(arg, '--watch=')) { + ArrayPrototypePush(removedWatchFlags, arg); continue; } if (arg === '--watch') { - const nextArg = process.execArgv[i + 1]; + ArrayPrototypePush(removedWatchFlags, arg); + const nextArg = args[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; @@ -65,7 +71,14 @@ for (let i = 0; i < process.execArgv.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 = args[i + 1]; + if (nextArg) { + ArrayPrototypePush(removedWatchFlags, nextArg); + i++; + } + } else { + ArrayPrototypePush(removedWatchFlags, arg); } continue; } @@ -94,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) { diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index 0902536708bf1d..ba8bdbcc0416b0 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -1,7 +1,9 @@ 'use strict'; const { + ArrayIsArray, ArrayPrototypeForEach, + ArrayPrototypeUnshiftApply, Date, DatePrototypeGetDate, DatePrototypeGetFullYear, @@ -9,6 +11,7 @@ const { DatePrototypeGetMinutes, DatePrototypeGetMonth, DatePrototypeGetSeconds, + JSONParse, NumberParseInt, ObjectDefineProperty, ObjectFreeze, @@ -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 diff --git a/test/sequential/test-watch-mode-watch-flags.mjs b/test/sequential/test-watch-mode-watch-flags.mjs index 385f381ad6a0ed..2e8b7a7880ac2d 100644 --- a/test/sequential/test-watch-mode-watch-flags.mjs +++ b/test/sequential/test-watch-mode-watch-flags.mjs @@ -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'], + ]); + }); });