Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4700ee5

Browse filesBrowse files
MoLowRafaelGSS
authored andcommitted
cli: add --watch
PR-URL: #44366 Backport-PR-URL: #44571 Fixes: #40429 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
1 parent 1315a83 commit 4700ee5
Copy full SHA for 4700ee5
Expand file treeCollapse file tree

29 files changed

+956
-42
lines changed
Open diff view settings
Collapse file

‎doc/api/cli.md‎

Copy file name to clipboardExpand all lines: doc/api/cli.md
+49Lines changed: 49 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,53 @@ on the number of online processors.
15291529
If the value provided is larger than V8's maximum, then the largest value
15301530
will be chosen.
15311531

1532+
### `--watch`
1533+
1534+
<!-- YAML
1535+
added: REPLACEME
1536+
-->
1537+
1538+
> Stability: 1 - Experimental
1539+
1540+
Starts Node.js in watch mode.
1541+
When in watch mode, changes in the watched files cause the Node.js process to
1542+
restart.
1543+
By default, watch mode will watch the entry point
1544+
and any required or imported module.
1545+
Use `--watch-path` to specify what paths to watch.
1546+
1547+
This flag cannot be combined with
1548+
`--check`, `--eval`, `--interactive`, or the REPL.
1549+
1550+
```console
1551+
$ node --watch index.js
1552+
```
1553+
1554+
### `--watch-path`
1555+
1556+
<!-- YAML
1557+
added: REPLACEME
1558+
-->
1559+
1560+
> Stability: 1 - Experimental
1561+
1562+
Starts Node.js in watch mode and specifies what paths to watch.
1563+
When in watch mode, changes in the watched paths cause the Node.js process to
1564+
restart.
1565+
This will turn off watching of required or imported modules, even when used in
1566+
combination with `--watch`.
1567+
1568+
This flag cannot be combined with
1569+
`--check`, `--eval`, `--interactive`, or the REPL.
1570+
1571+
```console
1572+
$ node --watch-path=./src --watch-path=./tests index.js
1573+
```
1574+
1575+
This option is only supported on macOS and Windows.
1576+
An `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM` exception will be thrown
1577+
when the option is used on a platform that does not support it.
1578+
15321579
### `--zero-fill-buffers`
15331580

15341581
<!-- YAML
@@ -1829,6 +1876,8 @@ Node.js options that are allowed are:
18291876
* `--use-largepages`
18301877
* `--use-openssl-ca`
18311878
* `--v8-pool-size`
1879+
* `--watch-path`
1880+
* `--watch`
18321881
* `--zero-fill-buffers`
18331882

18341883
<!-- node-options-node end -->
Collapse file

‎lib/internal/assert/assertion_error.js‎

Copy file name to clipboardExpand all lines: lib/internal/assert/assertion_error.js
+17-32Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,12 @@ const { inspect } = require('internal/util/inspect');
2121
const {
2222
removeColors,
2323
} = require('internal/util');
24+
const colors = require('internal/util/colors');
2425
const {
2526
validateObject,
2627
} = require('internal/validators');
2728
const { isErrorStackTraceLimitWritable } = require('internal/errors');
2829

29-
let blue = '';
30-
let green = '';
31-
let red = '';
32-
let white = '';
3330

3431
const kReadableOperator = {
3532
deepStrictEqual: 'Expected values to be strictly deep-equal:',
@@ -169,7 +166,7 @@ function createErrDiff(actual, expected, operator) {
169166
// Only remove lines in case it makes sense to collapse those.
170167
// TODO: Accept env to always show the full error.
171168
if (actualLines.length > 50) {
172-
actualLines[46] = `${blue}...${white}`;
169+
actualLines[46] = `${colors.blue}...${colors.white}`;
173170
while (actualLines.length > 47) {
174171
ArrayPrototypePop(actualLines);
175172
}
@@ -182,7 +179,7 @@ function createErrDiff(actual, expected, operator) {
182179
// There were at least five identical lines at the end. Mark a couple of
183180
// skipped.
184181
if (i >= 5) {
185-
end = `\n${blue}...${white}${end}`;
182+
end = `\n${colors.blue}...${colors.white}${end}`;
186183
skipped = true;
187184
}
188185
if (other !== '') {
@@ -193,15 +190,15 @@ function createErrDiff(actual, expected, operator) {
193190
let printedLines = 0;
194191
let identical = 0;
195192
const msg = kReadableOperator[operator] +
196-
`\n${green}+ actual${white} ${red}- expected${white}`;
197-
const skippedMsg = ` ${blue}...${white} Lines skipped`;
193+
`\n${colors.green}+ actual${colors.white} ${colors.red}- expected${colors.white}`;
194+
const skippedMsg = ` ${colors.blue}...${colors.white} Lines skipped`;
198195

199196
let lines = actualLines;
200-
let plusMinus = `${green}+${white}`;
197+
let plusMinus = `${colors.green}+${colors.white}`;
201198
let maxLength = expectedLines.length;
202199
if (actualLines.length < maxLines) {
203200
lines = expectedLines;
204-
plusMinus = `${red}-${white}`;
201+
plusMinus = `${colors.red}-${colors.white}`;
205202
maxLength = actualLines.length;
206203
}
207204

@@ -216,7 +213,7 @@ function createErrDiff(actual, expected, operator) {
216213
res += `\n ${lines[i - 3]}`;
217214
printedLines++;
218215
} else {
219-
res += `\n${blue}...${white}`;
216+
res += `\n${colors.blue}...${colors.white}`;
220217
skipped = true;
221218
}
222219
}
@@ -272,7 +269,7 @@ function createErrDiff(actual, expected, operator) {
272269
res += `\n ${actualLines[i - 3]}`;
273270
printedLines++;
274271
} else {
275-
res += `\n${blue}...${white}`;
272+
res += `\n${colors.blue}...${colors.white}`;
276273
skipped = true;
277274
}
278275
}
@@ -286,8 +283,8 @@ function createErrDiff(actual, expected, operator) {
286283
identical = 0;
287284
// Add the actual line to the result and cache the expected diverging
288285
// line so consecutive diverging lines show up as +++--- and not +-+-+-.
289-
res += `\n${green}+${white} ${actualLine}`;
290-
other += `\n${red}-${white} ${expectedLine}`;
286+
res += `\n${colors.green}+${colors.white} ${actualLine}`;
287+
other += `\n${colors.red}-${colors.white} ${expectedLine}`;
291288
printedLines += 2;
292289
// Lines are identical
293290
} else {
@@ -306,8 +303,8 @@ function createErrDiff(actual, expected, operator) {
306303
}
307304
// Inspected object to big (Show ~50 rows max)
308305
if (printedLines > 50 && i < maxLines - 2) {
309-
return `${msg}${skippedMsg}\n${res}\n${blue}...${white}${other}\n` +
310-
`${blue}...${white}`;
306+
return `${msg}${skippedMsg}\n${res}\n${colors.blue}...${colors.white}${other}\n` +
307+
`${colors.blue}...${colors.white}`;
311308
}
312309
}
313310

@@ -347,21 +344,9 @@ class AssertionError extends Error {
347344
if (message != null) {
348345
super(String(message));
349346
} else {
350-
if (process.stderr.isTTY) {
351-
// Reset on each call to make sure we handle dynamically set environment
352-
// variables correct.
353-
if (process.stderr.hasColors()) {
354-
blue = '\u001b[34m';
355-
green = '\u001b[32m';
356-
white = '\u001b[39m';
357-
red = '\u001b[31m';
358-
} else {
359-
blue = '';
360-
green = '';
361-
white = '';
362-
red = '';
363-
}
364-
}
347+
// Reset colors on each call to make sure we handle dynamically set environment
348+
// variables correct.
349+
colors.refresh();
365350
// Prevent the error stack from being visible by duplicating the error
366351
// in a very close way to the original in case both sides are actually
367352
// instances of Error.
@@ -393,7 +378,7 @@ class AssertionError extends Error {
393378
// Only remove lines in case it makes sense to collapse those.
394379
// TODO: Accept env to always show the full error.
395380
if (res.length > 50) {
396-
res[46] = `${blue}...${white}`;
381+
res[46] = `${colors.blue}...${colors.white}`;
397382
while (res.length > 47) {
398383
ArrayPrototypePop(res);
399384
}
Collapse file

‎lib/internal/main/watch_mode.js‎

Copy file name to clipboard
+132Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
'use strict';
2+
const {
3+
ArrayPrototypeFilter,
4+
ArrayPrototypeForEach,
5+
ArrayPrototypeJoin,
6+
ArrayPrototypeMap,
7+
ArrayPrototypePushApply,
8+
ArrayPrototypeSlice,
9+
} = primordials;
10+
11+
const {
12+
prepareMainThreadExecution,
13+
markBootstrapComplete
14+
} = require('internal/process/pre_execution');
15+
const { triggerUncaughtException } = internalBinding('errors');
16+
const { getOptionValue } = require('internal/options');
17+
const { emitExperimentalWarning } = require('internal/util');
18+
const { FilesWatcher } = require('internal/watch_mode/files_watcher');
19+
const { green, blue, red, white, clear } = require('internal/util/colors');
20+
21+
const { spawn } = require('child_process');
22+
const { inspect } = require('util');
23+
const { setTimeout, clearTimeout } = require('timers');
24+
const { resolve } = require('path');
25+
const { once, on } = require('events');
26+
27+
28+
prepareMainThreadExecution(false, false);
29+
markBootstrapComplete();
30+
31+
// TODO(MoLow): Make kill signal configurable
32+
const kKillSignal = 'SIGTERM';
33+
const kShouldFilterModules = getOptionValue('--watch-path').length === 0;
34+
const kWatchedPaths = ArrayPrototypeMap(getOptionValue('--watch-path'), (path) => resolve(path));
35+
const kCommand = ArrayPrototypeSlice(process.argv, 1);
36+
const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' '));
37+
const args = ArrayPrototypeFilter(process.execArgv, (arg, i, arr) =>
38+
arg !== '--watch-path' && arr[i - 1] !== '--watch-path' && arg !== '--watch');
39+
ArrayPrototypePushApply(args, kCommand);
40+
41+
const watcher = new FilesWatcher({ throttle: 500, mode: kShouldFilterModules ? 'filter' : 'all' });
42+
ArrayPrototypeForEach(kWatchedPaths, (p) => watcher.watchPath(p));
43+
44+
let graceTimer;
45+
let child;
46+
let exited;
47+
48+
function start() {
49+
exited = false;
50+
const stdio = kShouldFilterModules ? ['inherit', 'inherit', 'inherit', 'ipc'] : undefined;
51+
child = spawn(process.execPath, args, { stdio, env: { ...process.env, WATCH_REPORT_DEPENDENCIES: '1' } });
52+
watcher.watchChildProcessModules(child);
53+
child.once('exit', (code) => {
54+
exited = true;
55+
if (code === 0) {
56+
process.stdout.write(`${blue}Completed running ${kCommandStr}${white}\n`);
57+
} else {
58+
process.stdout.write(`${red}Failed running ${kCommandStr}${white}\n`);
59+
}
60+
});
61+
}
62+
63+
async function killAndWait(signal = kKillSignal, force = false) {
64+
child?.removeAllListeners();
65+
if (!child) {
66+
return;
67+
}
68+
if ((child.killed || exited) && !force) {
69+
return;
70+
}
71+
const onExit = once(child, 'exit');
72+
child.kill(signal);
73+
const { 0: exitCode } = await onExit;
74+
return exitCode;
75+
}
76+
77+
function reportGracefulTermination() {
78+
// Log if process takes more than 500ms to stop.
79+
let reported = false;
80+
clearTimeout(graceTimer);
81+
graceTimer = setTimeout(() => {
82+
reported = true;
83+
process.stdout.write(`${blue}Waiting for graceful termination...${white}\n`);
84+
}, 500).unref();
85+
return () => {
86+
clearTimeout(graceTimer);
87+
if (reported) {
88+
process.stdout.write(`${clear}${green}Gracefully restarted ${kCommandStr}${white}\n`);
89+
}
90+
};
91+
}
92+
93+
async function stop() {
94+
watcher.clearFileFilters();
95+
const clearGraceReport = reportGracefulTermination();
96+
await killAndWait();
97+
clearGraceReport();
98+
}
99+
100+
async function restart() {
101+
process.stdout.write(`${clear}${green}Restarting ${kCommandStr}${white}\n`);
102+
await stop();
103+
start();
104+
}
105+
106+
(async () => {
107+
emitExperimentalWarning('Watch mode');
108+
109+
try {
110+
start();
111+
112+
// eslint-disable-next-line no-unused-vars
113+
for await (const _ of on(watcher, 'changed')) {
114+
await restart();
115+
}
116+
} catch (error) {
117+
triggerUncaughtException(error, true /* fromPromise */);
118+
}
119+
})();
120+
121+
// Exiting gracefully to avoid stdout/stderr getting written after
122+
// parent process is killed.
123+
// this is fairly safe since user code cannot run in this process
124+
function signalHandler(signal) {
125+
return async () => {
126+
watcher.clear();
127+
const exitCode = await killAndWait(signal, true);
128+
process.exit(exitCode ?? 0);
129+
};
130+
}
131+
process.on('SIGTERM', signalHandler('SIGTERM'));
132+
process.on('SIGINT', signalHandler('SIGINT'));
Collapse file

‎lib/internal/modules/cjs/loader.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/cjs/loader.js
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ const {
100100
const { getOptionValue } = require('internal/options');
101101
const preserveSymlinks = getOptionValue('--preserve-symlinks');
102102
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
103+
const shouldReportRequiredModules = process.env.WATCH_REPORT_DEPENDENCIES;
103104
// Do not eagerly grab .manifest, it may be in TDZ
104105
const policy = getOptionValue('--experimental-policy') ?
105106
require('internal/process/policy') :
@@ -168,6 +169,12 @@ function updateChildren(parent, child, scan) {
168169
ArrayPrototypePush(children, child);
169170
}
170171

172+
function reportModuleToWatchMode(filename) {
173+
if (shouldReportRequiredModules && process.send) {
174+
process.send({ 'watch:require': filename });
175+
}
176+
}
177+
171178
const moduleParentCache = new SafeWeakMap();
172179
function Module(id = '', parent) {
173180
this.id = id;
@@ -776,6 +783,7 @@ Module._load = function(request, parent, isMain) {
776783
// cache key names.
777784
relResolveCacheIdentifier = `${parent.path}\x00${request}`;
778785
const filename = relativeResolveCache[relResolveCacheIdentifier];
786+
reportModuleToWatchMode(filename);
779787
if (filename !== undefined) {
780788
const cachedModule = Module._cache[filename];
781789
if (cachedModule !== undefined) {
@@ -828,6 +836,8 @@ Module._load = function(request, parent, isMain) {
828836
module.id = '.';
829837
}
830838

839+
reportModuleToWatchMode(filename);
840+
831841
Module._cache[filename] = module;
832842
if (parent !== undefined) {
833843
relativeResolveCache[relResolveCacheIdentifier] = filename;
Collapse file

‎lib/internal/modules/esm/loader.js‎

Copy file name to clipboardExpand all lines: lib/internal/modules/esm/loader.js
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,10 @@ class ESMLoader {
474474
getOptionValue('--inspect-brk')
475475
);
476476

477+
if (process.env.WATCH_REPORT_DEPENDENCIES && process.send) {
478+
process.send({ 'watch:import': url });
479+
}
480+
477481
const job = new ModuleJob(
478482
this,
479483
url,
Collapse file

‎lib/internal/util/colors.js‎

Copy file name to clipboard
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
module.exports = {
4+
blue: '',
5+
green: '',
6+
white: '',
7+
red: '',
8+
clear: '',
9+
hasColors: false,
10+
refresh() {
11+
if (process.stderr.isTTY) {
12+
const hasColors = process.stderr.hasColors();
13+
module.exports.blue = hasColors ? '\u001b[34m' : '';
14+
module.exports.green = hasColors ? '\u001b[32m' : '';
15+
module.exports.white = hasColors ? '\u001b[39m' : '';
16+
module.exports.red = hasColors ? '\u001b[31m' : '';
17+
module.exports.clear = hasColors ? '\u001bc' : '';
18+
module.exports.hasColors = hasColors;
19+
}
20+
}
21+
};
22+
23+
module.exports.refresh();

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.