From 6e0a5872093b08d7a8372a30e0b81eb46bf01c97 Mon Sep 17 00:00:00 2001 From: Efe Karasakal Date: Tue, 17 Feb 2026 20:19:51 +0100 Subject: [PATCH 1/3] watch: fix --env-file-if-exists crashing on linux if the file is missing --- lib/internal/main/watch_mode.js | 15 +++++++++++---- test/sequential/test-watch-mode.mjs | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/internal/main/watch_mode.js b/lib/internal/main/watch_mode.js index 06c2c8602da444..6cbe684fe0c252 100644 --- a/lib/internal/main/watch_mode.js +++ b/lib/internal/main/watch_mode.js @@ -24,6 +24,7 @@ const { green, blue, red, white, clear } = require('internal/util/colors'); const { convertToValidSignal } = require('internal/util'); const { spawn } = require('child_process'); +const { existsSync } = require('fs'); const { inspect } = require('util'); const { setTimeout, clearTimeout } = require('timers'); const { resolve } = require('path'); @@ -34,10 +35,8 @@ markBootstrapComplete(); const kKillSignal = convertToValidSignal(getOptionValue('--watch-kill-signal')); const kShouldFilterModules = getOptionValue('--watch-path').length === 0; -const kEnvFiles = [ - ...getOptionValue('--env-file'), - ...getOptionValue('--env-file-if-exists'), -]; +const kEnvFiles = getOptionValue('--env-file'); +const kOptionalEnvFiles = getOptionValue('--env-file-if-exists'); const kWatchedPaths = ArrayPrototypeMap(getOptionValue('--watch-path'), (path) => resolve(path)); const kPreserveOutput = getOptionValue('--watch-preserve-output'); const kCommand = ArrayPrototypeSlice(process.argv, 1); @@ -105,6 +104,14 @@ function start() { if (kEnvFiles.length > 0) { ArrayPrototypeForEach(kEnvFiles, (file) => watcher.filterFile(resolve(file))); } + if (kOptionalEnvFiles.length > 0) { + ArrayPrototypeForEach(kOptionalEnvFiles, (file) => { + const resolvedPath = resolve(file); + if (existsSync(resolvedPath)) { + watcher.filterFile(resolvedPath); + } + }); + } child.once('exit', (code) => { exited = true; const waitingForChanges = 'Waiting for file changes before restarting...'; diff --git a/test/sequential/test-watch-mode.mjs b/test/sequential/test-watch-mode.mjs index a5cac129ad1c21..39ad7ef72ada00 100644 --- a/test/sequential/test-watch-mode.mjs +++ b/test/sequential/test-watch-mode.mjs @@ -277,6 +277,27 @@ describe('watch mode', { concurrency: !process.env.TEST_PARALLEL, timeout: 60_00 } }); + it('should not crash when --env-file-if-exists points to a missing file', async () => { + const envKey = `TEST_ENV_${Date.now()}`; + const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey});`); + const missingEnvFile = path.join(tmpdir.path, `missing-${Date.now()}.env`); + const { done, restart } = runInBackground({ + args: ['--watch-path', tmpdir.path, `--env-file-if-exists=${missingEnvFile}`, jsFile], + }); + + try { + const { stderr, stdout } = await restart(); + + assert.doesNotMatch(stderr, /ENOENT: no such file or directory, watch/); + assert.deepStrictEqual(stdout, [ + 'ENV: undefined', + `Completed running ${inspect(jsFile)}. Waiting for file changes before restarting...`, + ]); + } finally { + await done(); + } + }); + it('should watch changes to a failing file', async () => { const file = createTmpFile('throw new Error("fails");'); const { stderr, stdout } = await runWriteSucceed({ From 635ff26bd913be34517d0576b97c0298a5a6f229 Mon Sep 17 00:00:00 2001 From: Efe Karasakal Date: Wed, 18 Feb 2026 17:03:30 +0100 Subject: [PATCH 2/3] watch: try/catch instead of existsSync --- lib/internal/main/watch_mode.js | 39 +++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/internal/main/watch_mode.js b/lib/internal/main/watch_mode.js index 6cbe684fe0c252..319f153c9f3aa0 100644 --- a/lib/internal/main/watch_mode.js +++ b/lib/internal/main/watch_mode.js @@ -16,19 +16,18 @@ const { } = require('internal/process/pre_execution'); const { triggerUncaughtException, - exitCodes: { kNoFailure }, + exitCodes: {kNoFailure}, } = internalBinding('errors'); -const { getOptionValue } = require('internal/options'); -const { FilesWatcher } = require('internal/watch_mode/files_watcher'); -const { green, blue, red, white, clear } = require('internal/util/colors'); -const { convertToValidSignal } = require('internal/util'); - -const { spawn } = require('child_process'); -const { existsSync } = require('fs'); -const { inspect } = require('util'); -const { setTimeout, clearTimeout } = require('timers'); -const { resolve } = require('path'); -const { once } = require('events'); +const {getOptionValue} = require('internal/options'); +const {FilesWatcher} = require('internal/watch_mode/files_watcher'); +const {green, blue, red, white, clear} = require('internal/util/colors'); +const {convertToValidSignal} = require('internal/util'); + +const {spawn} = require('child_process'); +const {inspect} = require('util'); +const {setTimeout, clearTimeout} = require('timers'); +const {resolve} = require('path'); +const {once} = require('events'); prepareMainThreadExecution(false, false); markBootstrapComplete(); @@ -83,7 +82,7 @@ for (let i = 0; i < process.execArgv.length; i++) { ArrayPrototypePushApply(argsWithoutWatchOptions, kCommand); -const watcher = new FilesWatcher({ debounce: 200, mode: kShouldFilterModules ? 'filter' : 'all' }); +const watcher = new FilesWatcher({debounce: 200, mode: kShouldFilterModules ? 'filter' : 'all'}); ArrayPrototypeForEach(kWatchedPaths, (p) => watcher.watchPath(p)); let graceTimer; @@ -106,9 +105,13 @@ function start() { } if (kOptionalEnvFiles.length > 0) { ArrayPrototypeForEach(kOptionalEnvFiles, (file) => { - const resolvedPath = resolve(file); - if (existsSync(resolvedPath)) { - watcher.filterFile(resolvedPath); + try { + watcher.filterFile(resolve(file)); + } catch (error) { + if (error?.code !== 'ENOENT' && error?.code !== 'ENOTDIR') { + throw error; + } + // Failed watching the file, ignore } }); } @@ -134,7 +137,7 @@ async function killAndWait(signal = kKillSignal, force = false) { } const onExit = once(child, 'exit'); child.kill(signal); - const { 0: exitCode } = await onExit; + const {0: exitCode} = await onExit; return exitCode; } @@ -167,6 +170,7 @@ async function stop(child) { } let restarting = false; + async function restart(child) { if (restarting) return; restarting = true; @@ -205,5 +209,6 @@ function signalHandler(signal) { process.exit(exitCode ?? kNoFailure); }; } + process.on('SIGTERM', signalHandler('SIGTERM')); process.on('SIGINT', signalHandler('SIGINT')); From abb5f5a5f5bfa061f369bd963fce609943945f39 Mon Sep 17 00:00:00 2001 From: Efe Karasakal Date: Wed, 18 Feb 2026 18:44:20 +0100 Subject: [PATCH 3/3] watch: js-lint-fix --- lib/internal/main/watch_mode.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/internal/main/watch_mode.js b/lib/internal/main/watch_mode.js index 319f153c9f3aa0..5f9608dcb14971 100644 --- a/lib/internal/main/watch_mode.js +++ b/lib/internal/main/watch_mode.js @@ -16,18 +16,18 @@ const { } = require('internal/process/pre_execution'); const { triggerUncaughtException, - exitCodes: {kNoFailure}, + exitCodes: { kNoFailure }, } = internalBinding('errors'); -const {getOptionValue} = require('internal/options'); -const {FilesWatcher} = require('internal/watch_mode/files_watcher'); -const {green, blue, red, white, clear} = require('internal/util/colors'); -const {convertToValidSignal} = require('internal/util'); +const { getOptionValue } = require('internal/options'); +const { FilesWatcher } = require('internal/watch_mode/files_watcher'); +const { green, blue, red, white, clear } = require('internal/util/colors'); +const { convertToValidSignal } = require('internal/util'); -const {spawn} = require('child_process'); -const {inspect} = require('util'); -const {setTimeout, clearTimeout} = require('timers'); -const {resolve} = require('path'); -const {once} = require('events'); +const { spawn } = require('child_process'); +const { inspect } = require('util'); +const { setTimeout, clearTimeout } = require('timers'); +const { resolve } = require('path'); +const { once } = require('events'); prepareMainThreadExecution(false, false); markBootstrapComplete(); @@ -82,7 +82,7 @@ for (let i = 0; i < process.execArgv.length; i++) { ArrayPrototypePushApply(argsWithoutWatchOptions, kCommand); -const watcher = new FilesWatcher({debounce: 200, mode: kShouldFilterModules ? 'filter' : 'all'}); +const watcher = new FilesWatcher({ debounce: 200, mode: kShouldFilterModules ? 'filter' : 'all' }); ArrayPrototypeForEach(kWatchedPaths, (p) => watcher.watchPath(p)); let graceTimer; @@ -137,7 +137,7 @@ async function killAndWait(signal = kKillSignal, force = false) { } const onExit = once(child, 'exit'); child.kill(signal); - const {0: exitCode} = await onExit; + const { 0: exitCode } = await onExit; return exitCode; }