Skip to content

Commit 2d1f1cb

Browse files
committed
watch: add initial implementation
1 parent 7900f65 commit 2d1f1cb

File tree

5 files changed

+123
-1
lines changed

5 files changed

+123
-1
lines changed

lib/internal/main/watch_mode.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
'use strict';
2+
const {
3+
ArrayPrototypeConcat,
4+
ArrayPrototypeMap,
5+
ArrayPrototypeReduce,
6+
ArrayPrototypeSlice,
7+
ArrayPrototypeSome,
8+
StringPrototypeSplit,
9+
StringPrototypeStartsWith,
10+
} = primordials;
11+
const {
12+
prepareMainThreadExecution,
13+
markBootstrapComplete
14+
} = require('internal/process/pre_execution');
15+
const { getOptionValue } = require('internal/options');
16+
17+
const { spawn } = require('child_process');
18+
const { watch } = require('fs/promises');
19+
const { setTimeout, clearTimeout } = require('timers');
20+
const { dirname, sep, resolve } = require('path');
21+
22+
23+
prepareMainThreadExecution(false);
24+
markBootstrapComplete();
25+
26+
const kWatchedFiles = ArrayPrototypeMap(getOptionValue('--watch-file'), (file) => resolve(file));
27+
const args = ArrayPrototypeReduce(ArrayPrototypeConcat(
28+
ArrayPrototypeSlice(process.execArgv),
29+
ArrayPrototypeSlice(process.argv, 1),
30+
), (acc, flag, i, arr) => {
31+
if (arr[i] !== '--watch-file' && arr[i - 1] !== '--watch-file' && arr[i] !== '--watch') {
32+
acc.push(arr[i]);
33+
}
34+
return acc;
35+
}, []);
36+
37+
function isWatchedFile(filename) {
38+
if (kWatchedFiles.length > 0) {
39+
return ArrayPrototypeSome(kWatchedFiles, (file) => StringPrototypeStartsWith(filename, file));
40+
}
41+
42+
const directory = dirname(filename);
43+
if (directory === '.') {
44+
return true;
45+
}
46+
47+
const dirs = StringPrototypeSplit(directory, sep);
48+
return !ArrayPrototypeSome(dirs, (dir) => dir[0] === '.' || dir === 'node_modules');
49+
}
50+
51+
function debounce(fn, duration = 100) {
52+
let timeout;
53+
return () => {
54+
if (timeout) {
55+
clearTimeout(timeout);
56+
}
57+
58+
timeout = setTimeout(fn, duration).unref();
59+
};
60+
}
61+
62+
let childProcess;
63+
function run(restarting) {
64+
if (childProcess && !childProcess.killed) {
65+
childProcess.kill();
66+
}
67+
if (restarting) {
68+
process.stdout.write('\u001Bc');
69+
process.stdout.write('\u001b[32mrestarting process\u001b[39m\n');
70+
}
71+
72+
childProcess = spawn(process.execPath, args, { stdio: ['inherit', 'inherit', 'inherit'] });
73+
}
74+
75+
const restart = debounce(run.bind(null, true));
76+
77+
(async () => {
78+
run();
79+
const watcher = watch(process.cwd(), { recursive: true });
80+
for await (const event of watcher) {
81+
if (isWatchedFile(resolve(event.filename))) {
82+
restart();
83+
}
84+
}
85+
})();

src/inspector_agent.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,10 @@ bool Agent::Start(const std::string& path,
676676
const DebugOptions& options,
677677
std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
678678
bool is_main) {
679+
if (!options.allow_attaching_debugger) {
680+
return false;
681+
}
682+
679683
path_ = path;
680684
debug_options_ = options;
681685
CHECK_NOT_NULL(host_port);

src/node.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,10 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
490490
return StartExecution(env, "internal/main/test_runner");
491491
}
492492

493+
if (env->options()->watch_mode) {
494+
return StartExecution(env, "internal/main/watch_mode");
495+
}
496+
493497
if (!first_argv.empty() && first_argv != "-") {
494498
return StartExecution(env, "internal/main/run_main_module");
495499
}

src/node_options.cc

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,27 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
156156
errors->push_back("either --test or --interactive can be used, not both");
157157
}
158158

159+
debug_options_.allow_attaching_debugger = false;
159160
if (debug_options_.inspector_enabled) {
160161
errors->push_back("the inspector cannot be used with --test");
161162
}
162163
}
163164

165+
if (watch_mode) {
166+
if (syntax_check_only) {
167+
errors->push_back("either --watch or --check can be used, not both");
168+
}
169+
170+
if (has_eval_string) {
171+
errors->push_back("either --watch or --eval can be used, not both");
172+
}
173+
174+
if (force_repl) {
175+
errors->push_back("either --watch or --interactive can be used, not both");
176+
}
177+
debug_options_.allow_attaching_debugger = false;
178+
}
179+
164180
#if HAVE_INSPECTOR
165181
if (!cpu_prof) {
166182
if (!cpu_prof_name.empty()) {
@@ -586,7 +602,15 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
586602
"", /* undocumented, only for debugging */
587603
&EnvironmentOptions::verify_base_objects,
588604
kAllowedInEnvironment);
589-
605+
AddOption("--watch",
606+
"run in watch mode",
607+
&EnvironmentOptions::watch_mode,
608+
kAllowedInEnvironment);
609+
AddOption("--watch-file",
610+
"files to watch",
611+
&EnvironmentOptions::watch_mode_files,
612+
kAllowedInEnvironment);
613+
Implies("--watch-file", "--watch");
590614
AddOption("--check",
591615
"syntax check script without executing",
592616
&EnvironmentOptions::syntax_check_only);

src/node_options.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class DebugOptions : public Options {
7171
DebugOptions(DebugOptions&&) = default;
7272
DebugOptions& operator=(DebugOptions&&) = default;
7373

74+
75+
bool allow_attaching_debugger = true;
7476
// --inspect
7577
bool inspector_enabled = false;
7678
// --debug
@@ -172,6 +174,9 @@ class EnvironmentOptions : public Options {
172174
false;
173175
#endif // DEBUG
174176

177+
bool watch_mode = false;
178+
std::vector<std::string> watch_mode_files;
179+
175180
bool syntax_check_only = false;
176181
bool has_eval_string = false;
177182
bool experimental_wasi = false;

0 commit comments

Comments
 (0)