77 ArrayPrototypePush,
88 ArrayPrototypePushApply,
99 ArrayPrototypeSlice,
10- RegExpPrototypeSymbolSplit ,
10+ StringPrototypeIncludes ,
1111 StringPrototypeStartsWith,
1212} = primordials ;
1313
@@ -19,7 +19,7 @@ const {
1919 triggerUncaughtException,
2020 exitCodes : { kNoFailure } ,
2121} = internalBinding ( 'errors' ) ;
22- const { getOptionValue } = require ( 'internal/options' ) ;
22+ const { getOptionValue, parseNodeOptionsEnvVar } = require ( 'internal/options' ) ;
2323const { FilesWatcher } = require ( 'internal/watch_mode/files_watcher' ) ;
2424const { green, blue, red, white, clear } = require ( 'internal/util/colors' ) ;
2525const { convertToValidSignal } = require ( 'internal/util' ) ;
@@ -85,14 +85,15 @@ for (let i = 0; i < process.execArgv.length; i++) {
8585
8686ArrayPrototypePushApply ( argsWithoutWatchOptions , kCommand ) ;
8787
88+ // Strip watch-related flags from NODE_OPTIONS to prevent infinite loop
89+ // when NODE_OPTIONS contains --watch (see issue #61740).
8890const kNodeOptions = process . env . NODE_OPTIONS ;
8991let cleanNodeOptions = kNodeOptions ;
9092if ( kNodeOptions != null ) {
91- const nodeOptionsArgs = [ ] ;
92- const parts = RegExpPrototypeSymbolSplit ( / \s + / , kNodeOptions ) ;
93+ const keep = [ ] ;
94+ const parts = parseNodeOptionsEnvVar ( kNodeOptions ) ;
9395 for ( let i = 0 ; i < parts . length ; i ++ ) {
9496 const part = parts [ i ] ;
95- if ( part === '' ) continue ;
9697 if ( part === '--watch' ||
9798 part === '--watch-preserve-output' ||
9899 StringPrototypeStartsWith ( part , '--watch=' ) ||
@@ -102,13 +103,17 @@ if (kNodeOptions != null) {
102103 continue ;
103104 }
104105 if ( part === '--watch-path' || part === '--watch-kill-signal' ) {
105- // These flags take a separate value argument
106+ // Skip the flag and its separate value argument
106107 i ++ ;
107108 continue ;
108109 }
109- ArrayPrototypePush ( nodeOptionsArgs , part ) ;
110+ // The C++ tokenizer strips quotes during parsing, so values that
111+ // originally contained spaces (e.g. --require "./path with spaces/f.js")
112+ // need to be re-quoted before rejoining into a single string, otherwise
113+ // the child's C++ parser would split them into separate tokens.
114+ ArrayPrototypePush ( keep , StringPrototypeIncludes ( part , ' ' ) ? `"${ part } "` : part ) ;
110115 }
111- cleanNodeOptions = ArrayPrototypeJoin ( nodeOptionsArgs , ' ' ) ;
116+ cleanNodeOptions = ArrayPrototypeJoin ( keep , ' ' ) ;
112117}
113118
114119const watcher = new FilesWatcher ( { debounce : 200 , mode : kShouldFilterModules ? 'filter' : 'all' } ) ;
0 commit comments