mirror of
https://github.com/nodejs/node.git
synced 2025-05-09 19:38:23 +00:00

PR-URL: https://github.com/nodejs/node/pull/48926 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
140 lines
4.2 KiB
JavaScript
140 lines
4.2 KiB
JavaScript
'use strict';
|
|
const {
|
|
ArrayPrototypeFilter,
|
|
ArrayPrototypeForEach,
|
|
ArrayPrototypeJoin,
|
|
ArrayPrototypeMap,
|
|
ArrayPrototypePushApply,
|
|
ArrayPrototypeSlice,
|
|
StringPrototypeStartsWith,
|
|
} = primordials;
|
|
|
|
const {
|
|
prepareMainThreadExecution,
|
|
markBootstrapComplete,
|
|
} = require('internal/process/pre_execution');
|
|
const {
|
|
triggerUncaughtException,
|
|
exitCodes: { kNoFailure },
|
|
} = 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 kPreserveOutput = getOptionValue('--watch-preserve-output');
|
|
const kCommand = ArrayPrototypeSlice(process.argv, 1);
|
|
const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' '));
|
|
const args = ArrayPrototypeFilter(process.execArgv, (arg, i, arr) =>
|
|
!StringPrototypeStartsWith(arg, '--watch-path') &&
|
|
(!arr[i - 1] || !StringPrototypeStartsWith(arr[i - 1], '--watch-path')) &&
|
|
arg !== '--watch' && arg !== '--watch-preserve-output');
|
|
ArrayPrototypePushApply(args, kCommand);
|
|
|
|
const watcher = new FilesWatcher({ debounce: 200, 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'] : 'inherit';
|
|
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() {
|
|
if (!kPreserveOutput) process.stdout.write(clear);
|
|
process.stdout.write(`${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 ?? kNoFailure);
|
|
};
|
|
}
|
|
process.on('SIGTERM', signalHandler('SIGTERM'));
|
|
process.on('SIGINT', signalHandler('SIGINT'));
|