UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

179 lines (161 loc) 5.02 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/main/watch_mode.js import { prepareMainThreadExecution, markBootstrapComplete, } from "nstdlib/lib/internal/process/pre_execution"; import { triggerUncaughtException, exitCodes as __exitCodes__, } from "nstdlib/stub/binding/errors"; import { getOptionValue } from "nstdlib/lib/internal/options"; import { FilesWatcher } from "nstdlib/lib/internal/watch_mode/files_watcher"; import { green, blue, red, white, clear, } from "nstdlib/lib/internal/util/colors"; import { spawn } from "nstdlib/lib/child_process"; import { inspect } from "nstdlib/lib/util"; import { setTimeout, clearTimeout } from "nstdlib/lib/timers"; import { resolve } from "nstdlib/lib/path"; import { once } from "nstdlib/lib/events"; const { kNoFailure } = __exitCodes__; prepareMainThreadExecution(false, false); markBootstrapComplete(); // TODO(MoLow): Make kill signal configurable const kKillSignal = "SIGTERM"; const kShouldFilterModules = getOptionValue("--watch-path").length === 0; const kEnvFile = getOptionValue("--env-file"); const kWatchedPaths = Array.prototype.map.call( getOptionValue("--watch-path"), (path) => resolve(path), ); const kPreserveOutput = getOptionValue("--watch-preserve-output"); const kCommand = Array.prototype.slice.call(process.argv, 1); const kCommandStr = inspect(Array.prototype.join.call(kCommand, " ")); const argsWithoutWatchOptions = []; for (let i = 0; i < process.execArgv.length; i++) { const arg = process.execArgv[i]; if (String.prototype.startsWith.call(arg, "--watch")) { i++; const nextArg = process.execArgv[i]; if (nextArg && String.prototype.startsWith.call(nextArg, "-")) { Array.prototype.push.call(argsWithoutWatchOptions, nextArg); } continue; } Array.prototype.push.call(argsWithoutWatchOptions, arg); } Array.prototype.push.apply(argsWithoutWatchOptions, kCommand); const watcher = new FilesWatcher({ debounce: 200, mode: kShouldFilterModules ? "filter" : "all", }); Array.prototype.forEach.call(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, argsWithoutWatchOptions, { stdio, env: { ...process.env, WATCH_REPORT_DEPENDENCIES: "1", }, }); watcher.watchChildProcessModules(child); if (kEnvFile) { watcher.filterFile(resolve(kEnvFile)); } 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`); } }); return child; } 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(child) { // Without this line, the child process is still able to receive IPC, but is unable to send additional messages watcher.destroyIPC(child); watcher.clearFileFilters(); const clearGraceReport = reportGracefulTermination(); await killAndWait(); clearGraceReport(); } let restarting = false; async function restart(child) { if (restarting) return; restarting = true; try { if (!kPreserveOutput) process.stdout.write(clear); process.stdout.write(`${green}Restarting ${kCommandStr}${white}\n`); await stop(child); return start(); } finally { restarting = false; } } async function init() { let child = start(); const restartChild = async () => { child = await restart(child); }; watcher.on("changed", restartChild).on("error", (error) => { watcher.off("changed", restartChild); triggerUncaughtException(error, true /* fromPromise */); }); } init(); // 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"));