-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PR-URL: nodejs/node#44366 Backport-PR-URL: nodejs/node#44976 Fixes: nodejs/node#40429 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
- Loading branch information
Showing
29 changed files
with
953 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
'use strict'; | ||
const { | ||
ArrayPrototypeFilter, | ||
ArrayPrototypeForEach, | ||
ArrayPrototypeJoin, | ||
ArrayPrototypeMap, | ||
ArrayPrototypePushApply, | ||
ArrayPrototypeSlice, | ||
} = primordials; | ||
|
||
const { | ||
prepareMainThreadExecution, | ||
} = require('internal/bootstrap/pre_execution'); | ||
const { triggerUncaughtException } = internalBinding('errors'); | ||
const { getOptionValue } = require('internal/options'); | ||
const { emitExperimentalWarning } = require('internal/util'); | ||
const { FilesWatcher } = require('internal/watch_mode/files_watcher'); | ||
const { green, blue, red, white, clear } = require('internal/util/colors'); | ||
|
||
const { spawn } = require('child_process'); | ||
const { inspect } = require('util'); | ||
const { setTimeout, clearTimeout } = require('timers'); | ||
const { resolve } = require('path'); | ||
const { once, on } = require('events'); | ||
|
||
|
||
prepareMainThreadExecution(false, false); | ||
markBootstrapComplete(); | ||
|
||
// TODO(MoLow): Make kill signal configurable | ||
const kKillSignal = 'SIGTERM'; | ||
const kShouldFilterModules = getOptionValue('--watch-path').length === 0; | ||
const kWatchedPaths = ArrayPrototypeMap(getOptionValue('--watch-path'), (path) => resolve(path)); | ||
const kCommand = ArrayPrototypeSlice(process.argv, 1); | ||
const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' ')); | ||
const args = ArrayPrototypeFilter(process.execArgv, (arg, i, arr) => | ||
arg !== '--watch-path' && arr[i - 1] !== '--watch-path' && arg !== '--watch'); | ||
ArrayPrototypePushApply(args, kCommand); | ||
|
||
const watcher = new FilesWatcher({ throttle: 500, mode: kShouldFilterModules ? 'filter' : 'all' }); | ||
ArrayPrototypeForEach(kWatchedPaths, (p) => watcher.watchPath(p)); | ||
|
||
let graceTimer; | ||
let child; | ||
let exited; | ||
|
||
function start() { | ||
exited = false; | ||
const stdio = kShouldFilterModules ? ['inherit', 'inherit', 'inherit', 'ipc'] : undefined; | ||
child = spawn(process.execPath, args, { stdio, env: { ...process.env, WATCH_REPORT_DEPENDENCIES: '1' } }); | ||
watcher.watchChildProcessModules(child); | ||
child.once('exit', (code) => { | ||
exited = true; | ||
if (code === 0) { | ||
process.stdout.write(`${blue}Completed running ${kCommandStr}${white}\n`); | ||
} else { | ||
process.stdout.write(`${red}Failed running ${kCommandStr}${white}\n`); | ||
} | ||
}); | ||
} | ||
|
||
async function killAndWait(signal = kKillSignal, force = false) { | ||
child?.removeAllListeners(); | ||
if (!child) { | ||
return; | ||
} | ||
if ((child.killed || exited) && !force) { | ||
return; | ||
} | ||
const onExit = once(child, 'exit'); | ||
child.kill(signal); | ||
const { 0: exitCode } = await onExit; | ||
return exitCode; | ||
} | ||
|
||
function reportGracefulTermination() { | ||
// Log if process takes more than 500ms to stop. | ||
let reported = false; | ||
clearTimeout(graceTimer); | ||
graceTimer = setTimeout(() => { | ||
reported = true; | ||
process.stdout.write(`${blue}Waiting for graceful termination...${white}\n`); | ||
}, 500).unref(); | ||
return () => { | ||
clearTimeout(graceTimer); | ||
if (reported) { | ||
process.stdout.write(`${clear}${green}Gracefully restarted ${kCommandStr}${white}\n`); | ||
} | ||
}; | ||
} | ||
|
||
async function stop() { | ||
watcher.clearFileFilters(); | ||
const clearGraceReport = reportGracefulTermination(); | ||
await killAndWait(); | ||
clearGraceReport(); | ||
} | ||
|
||
async function restart() { | ||
process.stdout.write(`${clear}${green}Restarting ${kCommandStr}${white}\n`); | ||
await stop(); | ||
start(); | ||
} | ||
|
||
(async () => { | ||
emitExperimentalWarning('Watch mode'); | ||
|
||
try { | ||
start(); | ||
|
||
// eslint-disable-next-line no-unused-vars | ||
for await (const _ of on(watcher, 'changed')) { | ||
await restart(); | ||
} | ||
} catch (error) { | ||
triggerUncaughtException(error, true /* fromPromise */); | ||
} | ||
})(); | ||
|
||
// Exiting gracefully to avoid stdout/stderr getting written after | ||
// parent process is killed. | ||
// this is fairly safe since user code cannot run in this process | ||
function signalHandler(signal) { | ||
return async () => { | ||
watcher.clear(); | ||
const exitCode = await killAndWait(signal, true); | ||
process.exit(exitCode ?? 0); | ||
}; | ||
} | ||
process.on('SIGTERM', signalHandler('SIGTERM')); | ||
process.on('SIGINT', signalHandler('SIGINT')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
'use strict'; | ||
|
||
module.exports = { | ||
blue: '', | ||
green: '', | ||
white: '', | ||
red: '', | ||
clear: '', | ||
hasColors: false, | ||
refresh() { | ||
if (process.stderr.isTTY) { | ||
const hasColors = process.stderr.hasColors(); | ||
module.exports.blue = hasColors ? '\u001b[34m' : ''; | ||
module.exports.green = hasColors ? '\u001b[32m' : ''; | ||
module.exports.white = hasColors ? '\u001b[39m' : ''; | ||
module.exports.red = hasColors ? '\u001b[31m' : ''; | ||
module.exports.clear = hasColors ? '\u001bc' : ''; | ||
module.exports.hasColors = hasColors; | ||
} | ||
} | ||
}; | ||
|
||
module.exports.refresh(); |
Oops, something went wrong.