/
watch_mode.js
125 lines (111 loc) Β· 3.78 KB
/
watch_mode.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
'use strict';
const {
ArrayPrototypeFilter,
ArrayPrototypeJoin,
ArrayPrototypeMap,
ArrayPrototypePush,
ArrayPrototypePushApply,
ArrayPrototypeSlice,
} = primordials;
const {
prepareMainThreadExecution,
markBootstrapComplete
} = require('internal/process/pre_execution');
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 = getOptionValue('--watch-path').length ?
ArrayPrototypeMap(getOptionValue('--watch-path'), (path) => resolve(path)) :
[];
const kCommand = ArrayPrototypeSlice(process.argv, 1);
const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' '));
const args = ArrayPrototypeFilter(process.execArgv, (x, i, arr) =>
x !== '--watch-path' && arr[i - 1] !== '--watch-path' && x !== '--watch');
ArrayPrototypePush(args, '--watch-report-ipc');
ArrayPrototypePushApply(args, kCommand);
const watcher = new FilesWatcher({ throttle: 500, mode: kShouldFilterModules ? 'filter' : 'all' });
kWatchedPaths.forEach((p) => watcher.watchPath(p));
let graceTimer;
let child;
let exited;
function start() {
// Spawning in detached mode so node can control when signals are forwarded
exited = false;
const stdio = kShouldFilterModules ? ['inherit', 'inherit', 'inherit', 'ipc'] : undefined;
child = spawn(process.execPath, args, { stdio, detached: true });
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) {
child?.removeAllListeners();
if (!child || child.killed || exited) {
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');
start();
// eslint-disable-next-line no-unused-vars
for await (const _ of on(watcher, 'changed')) {
await restart();
}
})();
// 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();
process.exit(await killAndWait(signal));
};
}
process.on('SIGTERM', signalHandler('SIGTERM'));
process.on('SIGINT', signalHandler('SIGINT'));