• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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