Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,9 @@ in the `$schema` must be replaced with the version of Node.js you are using.
},
"testRunner": {
"test-isolation": "process"
},
"watch": {
"watch-preserve-output": true
}
}
```
Expand Down
29 changes: 29 additions & 0 deletions doc/node-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,35 @@
"type": "boolean"
}
}
},
"watch": {
"type": "object",
"additionalProperties": false,
"properties": {
"watch": {
"type": "boolean"
},
"watch-kill-signal": {
"type": "string"
},
"watch-path": {
"oneOf": [
{
"type": "string"
},
{
"items": {
"type": "string",
"minItems": 1
},
"type": "array"
}
]
},
"watch-preserve-output": {
"type": "boolean"
}
}
}
},
"type": "object"
Expand Down
21 changes: 16 additions & 5 deletions lib/internal/main/watch_mode.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';
const {
ArrayPrototypeForEach,
ArrayPrototypeIncludes,
ArrayPrototypeJoin,
ArrayPrototypeMap,
ArrayPrototypePush,
Expand All @@ -17,7 +18,7 @@ const {
triggerUncaughtException,
exitCodes: { kNoFailure },
} = internalBinding('errors');
const { getOptionValue } = require('internal/options');
const { getOptionValue, getOptionsAsFlagsFromBinding } = require('internal/options');
const { FilesWatcher } = require('internal/watch_mode/files_watcher');
const { green, blue, red, white, clear } = require('internal/util/colors');
const { convertToValidSignal } = require('internal/util');
Expand All @@ -40,14 +41,14 @@ const kCommand = ArrayPrototypeSlice(process.argv, 1);
const kCommandStr = inspect(ArrayPrototypeJoin(kCommand, ' '));

const argsWithoutWatchOptions = [];

for (let i = 0; i < process.execArgv.length; i++) {
const arg = process.execArgv[i];
const argsFromBinding = getOptionsAsFlagsFromBinding();
for (let i = 0; i < argsFromBinding.length; i++) {
const arg = argsFromBinding[i];
if (StringPrototypeStartsWith(arg, '--watch=')) {
continue;
}
if (arg === '--watch') {
const nextArg = process.execArgv[i + 1];
const nextArg = argsFromBinding[i + 1];
if (nextArg && nextArg[0] !== '-') {
// If `--watch` doesn't include `=` and the next
// argument is not a flag then it is interpreted as
Expand All @@ -66,6 +67,16 @@ for (let i = 0; i < process.execArgv.length; i++) {
}
continue;
}
if (StringPrototypeStartsWith(arg, '--experimental-config-file')) {
if (!ArrayPrototypeIncludes(arg, '=')) {
// Skip the flag and the next argument (the config file path)
i++;
}
continue;
}
if (arg === '--experimental-default-config-file') {
continue;
}
ArrayPrototypePush(argsWithoutWatchOptions, arg);
}

Expand Down
16 changes: 11 additions & 5 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors,
} else if (test_runner_force_exit) {
errors->push_back("either --watch or --test-force-exit "
"can be used, not both");
} else if (!test_runner && (argv->size() < 1 || (*argv)[1].empty())) {
} else if (!test_runner && watch_mode_paths.empty() && argv->size() < 1) {
errors->push_back("--watch requires specifying a file");
}

Expand Down Expand Up @@ -1013,20 +1013,26 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
AddOption("--watch",
"run in watch mode",
&EnvironmentOptions::watch_mode,
kAllowedInEnvvar);
kAllowedInEnvvar,
false,
OptionNamespaces::kWatchNamespace);
AddOption("--watch-path",
"path to watch",
&EnvironmentOptions::watch_mode_paths,
kAllowedInEnvvar);
kAllowedInEnvvar,
OptionNamespaces::kWatchNamespace);
AddOption("--watch-kill-signal",
"kill signal to send to the process on watch mode restarts"
"(default: SIGTERM)",
&EnvironmentOptions::watch_mode_kill_signal,
kAllowedInEnvvar);
kAllowedInEnvvar,
OptionNamespaces::kWatchNamespace);
AddOption("--watch-preserve-output",
"preserve outputs on watch mode restart",
&EnvironmentOptions::watch_mode_preserve_output,
kAllowedInEnvvar);
kAllowedInEnvvar,
false,
OptionNamespaces::kWatchNamespace);
Implies("--watch-path", "--watch");
AddOption("--check",
"syntax check script without executing",
Expand Down
3 changes: 2 additions & 1 deletion src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,8 @@ std::vector<std::string> MapAvailableNamespaces();
// Define all namespace entries
#define OPTION_NAMESPACE_LIST(V) \
V(kNoNamespace, "") \
V(kTestRunnerNamespace, "testRunner")
V(kTestRunnerNamespace, "testRunner") \
V(kWatchNamespace, "watch")

enum class OptionNamespaces {
#define V(name, _) name,
Expand Down
69 changes: 69 additions & 0 deletions test/sequential/test-watch-mode.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -791,4 +791,73 @@ process.on('message', (message) => {
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});

it('should watch changes to a file from config file', async () => {
const file = createTmpFile();
const configFile = createTmpFile(JSON.stringify({ watch: { 'watch': true } }), '.json');
const { stderr, stdout } = await runWriteSucceed({
file, watchedFile: file, args: ['--experimental-config-file', configFile, file], options: {
timeout: 10000
}
});

assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});

it('should watch changes to a file with watch-path from config file', {
skip: !supportsRecursive,
}, async () => {
const dir = tmpdir.resolve('subdir4');
mkdirSync(dir);
const file = createTmpFile();
const watchedFile = createTmpFile('', '.js', dir);
const configFile = createTmpFile(JSON.stringify({ watch: { 'watch-path': [dir] } }), '.json', dir);

const args = ['--experimental-config-file', configFile, file];
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile, args });

assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
assert.strictEqual(stderr, '');
});

it('should watch changes to a file from default config file', async () => {
const dir = tmpdir.resolve('subdir5');
mkdirSync(dir);

const file = createTmpFile('console.log("running");', '.js', dir);
writeFileSync(path.join(dir, 'node.config.json'), JSON.stringify({ watch: { 'watch': true } }));

const { stderr, stdout } = await runWriteSucceed({
file,
watchedFile: file,
args: ['--experimental-default-config-file', file],
options: {
timeout: 10000,
cwd: dir
}
});

assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
});
Loading