Skip to content

Commit

Permalink
watch: add initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
MoLow committed Aug 23, 2022
1 parent 7900f65 commit 2d1f1cb
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 1 deletion.
85 changes: 85 additions & 0 deletions lib/internal/main/watch_mode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict';
const {
ArrayPrototypeConcat,
ArrayPrototypeMap,
ArrayPrototypeReduce,
ArrayPrototypeSlice,
ArrayPrototypeSome,
StringPrototypeSplit,
StringPrototypeStartsWith,
} = primordials;
const {
prepareMainThreadExecution,
markBootstrapComplete
} = require('internal/process/pre_execution');
const { getOptionValue } = require('internal/options');

const { spawn } = require('child_process');
const { watch } = require('fs/promises');
const { setTimeout, clearTimeout } = require('timers');
const { dirname, sep, resolve } = require('path');


prepareMainThreadExecution(false);
markBootstrapComplete();

const kWatchedFiles = ArrayPrototypeMap(getOptionValue('--watch-file'), (file) => resolve(file));
const args = ArrayPrototypeReduce(ArrayPrototypeConcat(
ArrayPrototypeSlice(process.execArgv),
ArrayPrototypeSlice(process.argv, 1),
), (acc, flag, i, arr) => {
if (arr[i] !== '--watch-file' && arr[i - 1] !== '--watch-file' && arr[i] !== '--watch') {
acc.push(arr[i]);
}
return acc;
}, []);

function isWatchedFile(filename) {
if (kWatchedFiles.length > 0) {
return ArrayPrototypeSome(kWatchedFiles, (file) => StringPrototypeStartsWith(filename, file));
}

const directory = dirname(filename);
if (directory === '.') {
return true;
}

const dirs = StringPrototypeSplit(directory, sep);
return !ArrayPrototypeSome(dirs, (dir) => dir[0] === '.' || dir === 'node_modules');
}

function debounce(fn, duration = 100) {
let timeout;
return () => {
if (timeout) {
clearTimeout(timeout);
}

timeout = setTimeout(fn, duration).unref();
};
}

let childProcess;
function run(restarting) {
if (childProcess && !childProcess.killed) {
childProcess.kill();
}
if (restarting) {
process.stdout.write('\u001Bc');
process.stdout.write('\u001b[32mrestarting process\u001b[39m\n');
}

childProcess = spawn(process.execPath, args, { stdio: ['inherit', 'inherit', 'inherit'] });
}

const restart = debounce(run.bind(null, true));

(async () => {
run();
const watcher = watch(process.cwd(), { recursive: true });
for await (const event of watcher) {
if (isWatchedFile(resolve(event.filename))) {
restart();
}
}
})();
4 changes: 4 additions & 0 deletions src/inspector_agent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,10 @@ bool Agent::Start(const std::string& path,
const DebugOptions& options,
std::shared_ptr<ExclusiveAccess<HostPort>> host_port,
bool is_main) {
if (!options.allow_attaching_debugger) {
return false;
}

path_ = path;
debug_options_ = options;
CHECK_NOT_NULL(host_port);
Expand Down
4 changes: 4 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,10 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
return StartExecution(env, "internal/main/test_runner");
}

if (env->options()->watch_mode) {
return StartExecution(env, "internal/main/watch_mode");
}

if (!first_argv.empty() && first_argv != "-") {
return StartExecution(env, "internal/main/run_main_module");
}
Expand Down
26 changes: 25 additions & 1 deletion src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,27 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors) {
errors->push_back("either --test or --interactive can be used, not both");
}

debug_options_.allow_attaching_debugger = false;
if (debug_options_.inspector_enabled) {
errors->push_back("the inspector cannot be used with --test");
}
}

if (watch_mode) {
if (syntax_check_only) {
errors->push_back("either --watch or --check can be used, not both");
}

if (has_eval_string) {
errors->push_back("either --watch or --eval can be used, not both");
}

if (force_repl) {
errors->push_back("either --watch or --interactive can be used, not both");
}
debug_options_.allow_attaching_debugger = false;
}

#if HAVE_INSPECTOR
if (!cpu_prof) {
if (!cpu_prof_name.empty()) {
Expand Down Expand Up @@ -586,7 +602,15 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"", /* undocumented, only for debugging */
&EnvironmentOptions::verify_base_objects,
kAllowedInEnvironment);

AddOption("--watch",
"run in watch mode",
&EnvironmentOptions::watch_mode,
kAllowedInEnvironment);
AddOption("--watch-file",
"files to watch",
&EnvironmentOptions::watch_mode_files,
kAllowedInEnvironment);
Implies("--watch-file", "--watch");
AddOption("--check",
"syntax check script without executing",
&EnvironmentOptions::syntax_check_only);
Expand Down
5 changes: 5 additions & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ class DebugOptions : public Options {
DebugOptions(DebugOptions&&) = default;
DebugOptions& operator=(DebugOptions&&) = default;


bool allow_attaching_debugger = true;
// --inspect
bool inspector_enabled = false;
// --debug
Expand Down Expand Up @@ -172,6 +174,9 @@ class EnvironmentOptions : public Options {
false;
#endif // DEBUG

bool watch_mode = false;
std::vector<std::string> watch_mode_files;

bool syntax_check_only = false;
bool has_eval_string = false;
bool experimental_wasi = false;
Expand Down

0 comments on commit 2d1f1cb

Please sign in to comment.