nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
179 lines (161 loc) • 5.02 kB
JavaScript
// 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"));