1'use strict'; 2const { 3 ArrayPrototypeFilter, 4 ArrayPrototypeForEach, 5 ArrayPrototypeJoin, 6 ArrayPrototypeMap, 7 ArrayPrototypePushApply, 8 ArrayPrototypeSlice, 9 StringPrototypeStartsWith, 10} = primordials; 11 12const { 13 prepareMainThreadExecution, 14 markBootstrapComplete, 15} = require('internal/process/pre_execution'); 16const { triggerUncaughtException } = internalBinding('errors'); 17const { getOptionValue } = require('internal/options'); 18const { emitExperimentalWarning } = require('internal/util'); 19const { FilesWatcher } = require('internal/watch_mode/files_watcher'); 20const { green, blue, red, white, clear } = require('internal/util/colors'); 21 22const { spawn } = require('child_process'); 23const { inspect } = require('util'); 24const { setTimeout, clearTimeout } = require('timers'); 25const { resolve } = require('path'); 26const { once, on } = require('events'); 27 28 29prepareMainThreadExecution(false, false); 30markBootstrapComplete(); 31 32// TODO(MoLow): Make kill signal configurable 33const kKillSignal = 'SIGTERM'; 34const kShouldFilterModules = getOptionValue('--watch-path').length === 0; 35const kWatchedPaths = ArrayPrototypeMap(getOptionValue('--watch-path'), (path) => resolve(path)); 36const kPreserveOutput = getOptionValue('--watch-preserve-output'); 37const kCommand = ArrayPrototypeSlice(process.argv, 1); 38const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' ')); 39const args = ArrayPrototypeFilter(process.execArgv, (arg, i, arr) => 40 !StringPrototypeStartsWith(arg, '--watch-path') && 41 (!arr[i - 1] || !StringPrototypeStartsWith(arr[i - 1], '--watch-path')) && 42 arg !== '--watch' && arg !== '--watch-preserve-output'); 43ArrayPrototypePushApply(args, kCommand); 44 45const watcher = new FilesWatcher({ throttle: 500, mode: kShouldFilterModules ? 'filter' : 'all' }); 46ArrayPrototypeForEach(kWatchedPaths, (p) => watcher.watchPath(p)); 47 48let graceTimer; 49let child; 50let exited; 51 52function start() { 53 exited = false; 54 const stdio = kShouldFilterModules ? ['inherit', 'inherit', 'inherit', 'ipc'] : 'inherit'; 55 child = spawn(process.execPath, args, { stdio, env: { ...process.env, WATCH_REPORT_DEPENDENCIES: '1' } }); 56 watcher.watchChildProcessModules(child); 57 child.once('exit', (code) => { 58 exited = true; 59 if (code === 0) { 60 process.stdout.write(`${blue}Completed running ${kCommandStr}${white}\n`); 61 } else { 62 process.stdout.write(`${red}Failed running ${kCommandStr}${white}\n`); 63 } 64 }); 65} 66 67async function killAndWait(signal = kKillSignal, force = false) { 68 child?.removeAllListeners(); 69 if (!child) { 70 return; 71 } 72 if ((child.killed || exited) && !force) { 73 return; 74 } 75 const onExit = once(child, 'exit'); 76 child.kill(signal); 77 const { 0: exitCode } = await onExit; 78 return exitCode; 79} 80 81function reportGracefulTermination() { 82 // Log if process takes more than 500ms to stop. 83 let reported = false; 84 clearTimeout(graceTimer); 85 graceTimer = setTimeout(() => { 86 reported = true; 87 process.stdout.write(`${blue}Waiting for graceful termination...${white}\n`); 88 }, 500).unref(); 89 return () => { 90 clearTimeout(graceTimer); 91 if (reported) { 92 process.stdout.write(`${clear}${green}Gracefully restarted ${kCommandStr}${white}\n`); 93 } 94 }; 95} 96 97async function stop() { 98 watcher.clearFileFilters(); 99 const clearGraceReport = reportGracefulTermination(); 100 await killAndWait(); 101 clearGraceReport(); 102} 103 104async function restart() { 105 if (!kPreserveOutput) process.stdout.write(clear); 106 process.stdout.write(`${green}Restarting ${kCommandStr}${white}\n`); 107 await stop(); 108 start(); 109} 110 111(async () => { 112 emitExperimentalWarning('Watch mode'); 113 114 try { 115 start(); 116 117 // eslint-disable-next-line no-unused-vars 118 for await (const _ of on(watcher, 'changed')) { 119 await restart(); 120 } 121 } catch (error) { 122 triggerUncaughtException(error, true /* fromPromise */); 123 } 124})(); 125 126// Exiting gracefully to avoid stdout/stderr getting written after 127// parent process is killed. 128// this is fairly safe since user code cannot run in this process 129function signalHandler(signal) { 130 return async () => { 131 watcher.clear(); 132 const exitCode = await killAndWait(signal, true); 133 process.exit(exitCode ?? 0); 134 }; 135} 136process.on('SIGTERM', signalHandler('SIGTERM')); 137process.on('SIGINT', signalHandler('SIGINT')); 138