Skip to content

Commit 1ca7ac7

Browse files
src: allow empty --experimental-config-file
PR-URL: #61610 Reviewed-By: Pietro Marchini <pietro.marchini94@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
1 parent c4e035e commit 1ca7ac7

18 files changed

Lines changed: 343 additions & 188 deletions

doc/api/cli.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ added: v23.6.0
963963
964964
Enable experimental import support for `.node` addons.
965965

966-
### `--experimental-config-file=config`
966+
### `--experimental-config-file=path`, `--experimental-config-file`
967967

968968
<!-- YAML
969969
added: v23.10.0
@@ -972,6 +972,12 @@ added: v23.10.0
972972
> Stability: 1.0 - Early development
973973
974974
If present, Node.js will look for a configuration file at the specified path.
975+
If the path is not specified, Node.js will look for a `node.config.json` file
976+
in the current working directory.
977+
To specify a custom path, use the `--experimental-config-file=path` form.
978+
The space-separated `--experimental-config-file path` form is not supported.
979+
The alias `--experimental-default-config-file` is equivalent to
980+
`--experimental-config-file` without an argument.
975981
Node.js will read the configuration file and apply the settings. The
976982
configuration file should be a JSON file with the following structure. `vX.Y.Z`
977983
in the `$schema` must be replaced with the version of Node.js you are using.
@@ -1043,9 +1049,10 @@ added: v23.10.0
10431049

10441050
> Stability: 1.0 - Early development
10451051
1046-
If the `--experimental-default-config-file` flag is present, Node.js will look for a
1052+
This flag is an alias for `--experimental-config-file` without an argument.
1053+
If present, Node.js will look for a
10471054
`node.config.json` file in the current working directory and load it as a
1048-
as configuration file.
1055+
configuration file.
10491056

10501057
### `--experimental-eventsource`
10511058

doc/api/test.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4503,7 +4503,7 @@ test.describe('my suite', (suite) => {
45034503
[`suite()`]: #suitename-options-fn
45044504
[`test()`]: #testname-options-fn
45054505
[code coverage]: #collecting-code-coverage
4506-
[configuration files]: cli.md#--experimental-config-fileconfig
4506+
[configuration files]: cli.md#--experimental-config-filepath---experimental-config-file
45074507
[describe options]: #describename-options-fn
45084508
[it options]: #testname-options-fn
45094509
[module customization hooks]: module.md#customization-hooks

doc/node.1

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,75 @@ Interpret the entry point as a URL.
169169
.It Fl -experimental-addon-modules
170170
Enable experimental addon module support.
171171
.
172-
.It Fl -experimental-config-file
173-
Specifies the configuration file to load.
172+
.It Fl -experimental-config-file , Fl -experimental-config-file Ns = Ns Ar config , Fl -experimental-default-config-file
173+
If present, Node.js will look for a configuration file at the specified path.
174+
If the path is not specified, Node.js will look for a \fBnode.config.json\fR file
175+
in the current working directory.
176+
To specify a custom path, use the \fB--experimental-config-file=\fR\fBpath\fR form.
177+
The space-separated \fB--experimental-config-file path\fR form is not supported.
178+
Node.js will read the configuration file and apply the settings. The
179+
configuration file should be a JSON file with the following structure. \fBvX.Y.Z\fR
180+
in the \fB$schema\fR must be replaced with the version of Node.js you are using.
181+
.Bd -literal
182+
{
183+
"$schema": "https://nodejs.org/dist/vX.Y.Z/docs/node-config-schema.json",
184+
"nodeOptions": {
185+
"import": [
186+
"amaro/strip"
187+
],
188+
"watch-path": "src",
189+
"watch-preserve-output": true
190+
},
191+
"testRunner": {
192+
"test-isolation": "process"
193+
},
194+
"watch": {
195+
"watch-preserve-output": true
196+
}
197+
}
198+
.Ed
199+
The configuration file supports namespace-specific options:
200+
.Bl -bullet
201+
.It
202+
The \fBnodeOptions\fR field contains CLI flags that are allowed in \fBNODE_OPTIONS\fR.
203+
.It
204+
Namespace fields like \fBtestRunner\fR contain configuration specific to that subsystem.
205+
.El
206+
No-op flags are not supported.
207+
Not all V8 flags are currently supported.
208+
It is possible to use the official JSON schema
209+
to validate the configuration file, which may vary depending on the Node.js version.
210+
Each key in the configuration file corresponds to a flag that can be passed
211+
as a command-line argument. The value of the key is the value that would be
212+
passed to the flag.
213+
For example, the configuration file above is equivalent to
214+
the following command-line arguments:
215+
.Bd -literal
216+
node --import amaro/strip --watch-path=src --watch-preserve-output --test-isolation=process
217+
.Ed
218+
The priority in configuration is as follows:
219+
.Bl -bullet
220+
.It
221+
NODE_OPTIONS and command-line options
222+
.It
223+
Dotenv NODE_OPTIONS
224+
.It
225+
Configuration file
226+
.El
227+
Values in the configuration file will not override the values in the environment
228+
variables, command-line options, or the \fBNODE_OPTIONS\fR env file parsed by the
229+
\fB--env-file\fR flag.
230+
Keys cannot be duplicated within the same or different namespaces.
231+
The configuration parser will throw an error if the configuration file contains
232+
unknown keys or keys that cannot be used in a namespace.
233+
Node.js will not sanitize or perform validation on the user-provided configuration,
234+
so \fBNEVER\fR use untrusted configuration files.
174235
.
175236
.It Fl -experimental-default-config-file
176-
Enable support for automatically loading node.config.json.
237+
This flag is an alias for \fB--experimental-config-file\fR without an argument.
238+
If present, Node.js will look for a
239+
\fBnode.config.json\fR file in the current working directory and load it as a
240+
configuration file.
177241
.
178242
.It Fl -experimental-import-meta-resolve
179243
Enable experimental ES modules support for import.meta.resolve().

lib/internal/main/watch_mode.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
22
const {
33
ArrayPrototypeForEach,
4-
ArrayPrototypeIncludes,
54
ArrayPrototypeJoin,
65
ArrayPrototypeMap,
76
ArrayPrototypePush,
@@ -67,11 +66,8 @@ for (let i = 0; i < process.execArgv.length; i++) {
6766
}
6867
continue;
6968
}
70-
if (StringPrototypeStartsWith(arg, '--experimental-config-file')) {
71-
if (!ArrayPrototypeIncludes(arg, '=')) {
72-
// Skip the flag and the next argument (the config file path)
73-
i++;
74-
}
69+
if (arg === '--experimental-config-file' ||
70+
StringPrototypeStartsWith(arg, '--experimental-config-file=')) {
7571
continue;
7672
}
7773
if (arg === '--experimental-default-config-file') {

lib/internal/process/pre_execution.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,7 @@ function setupSQLite() {
386386
}
387387

388388
function initializeConfigFileSupport() {
389-
if (getOptionValue('--experimental-default-config-file') ||
390-
getOptionValue('--experimental-config-file')) {
389+
if (getOptionValue('--experimental-config-file')) {
391390
emitExperimentalWarning('--experimental-config-file');
392391
}
393392
}

lib/internal/test_runner/runner.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,11 @@ function createTestFileList(patterns, cwd) {
161161

162162
function filterExecArgv(arg, i, arr) {
163163
return !ArrayPrototypeIncludes(kFilterArgs, arg) &&
164-
!ArrayPrototypeSome(kFilterArgValues, (p) => arg === p || (i > 0 && arr[i - 1] === p) || StringPrototypeStartsWith(arg, `${p}=`));
164+
!ArrayPrototypeSome(kFilterArgValues, (p) => {
165+
return arg === p ||
166+
StringPrototypeStartsWith(arg, `${p}=`) ||
167+
(p !== '--experimental-config-file' && i > 0 && arr[i - 1] === p);
168+
});
165169
}
166170

167171
function getRunArgs(path, { forceExit,

src/node.cc

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -911,15 +911,21 @@ static ExitCode InitializeNodeWithArgsInternal(
911911
}
912912

913913
std::string node_options_from_config;
914-
if (auto path = per_process::config_reader.GetDataFromArgs(*argv)) {
915-
switch (per_process::config_reader.ParseConfig(*path)) {
914+
auto config_path = per_process::config_reader.GetDataFromArgs(argv);
915+
if (per_process::config_reader.HasInvalidDefaultConfigFileArgument()) {
916+
errors->push_back("--experimental-default-config-file does not take an "
917+
"argument");
918+
return ExitCode::kInvalidCommandLineArgument;
919+
}
920+
if (config_path) {
921+
switch (per_process::config_reader.ParseConfig(*config_path)) {
916922
case ParseResult::Valid:
917923
break;
918924
case ParseResult::InvalidContent:
919-
errors->push_back(std::string(*path) + ": invalid content");
925+
errors->push_back(std::string(*config_path) + ": invalid content");
920926
break;
921927
case ParseResult::FileError:
922-
errors->push_back(std::string(*path) + ": not found");
928+
errors->push_back(std::string(*config_path) + ": not found");
923929
break;
924930
default:
925931
UNREACHABLE();

src/node_config_file.cc

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,53 @@
22
#include "debug_utils-inl.h"
33
#include "simdjson.h"
44

5-
#include <string>
6-
75
namespace node {
86

7+
constexpr std::string_view kConfigFileFlag = "--experimental-config-file";
8+
constexpr std::string_view kDefaultConfigFileFlag =
9+
"--experimental-default-config-file";
10+
constexpr std::string_view kDefaultConfigFileName = "node.config.json";
11+
12+
inline bool HasEqualsPrefix(std::string_view arg, std::string_view flag) {
13+
return arg.size() > flag.size() && arg.starts_with(flag) &&
14+
arg[flag.size()] == '=';
15+
}
16+
917
std::optional<std::string_view> ConfigReader::GetDataFromArgs(
10-
const std::vector<std::string>& args) {
11-
constexpr std::string_view flag_path = "--experimental-config-file";
12-
constexpr std::string_view default_file =
13-
"--experimental-default-config-file";
14-
15-
bool has_default_config_file = false;
16-
17-
for (auto it = args.begin(); it != args.end(); ++it) {
18-
if (*it == flag_path) {
19-
// Case: "--experimental-config-file foo"
20-
if (auto next = std::next(it); next != args.end()) {
21-
return *next;
18+
std::vector<std::string>* args) {
19+
std::optional<std::string_view> result;
20+
invalid_default_config_file_argument_ = false;
21+
22+
for (size_t i = 0; i < args->size(); ++i) {
23+
std::string& arg = (*args)[i];
24+
25+
if (arg == kConfigFileFlag) {
26+
// --experimental-config-file
27+
arg = std::string(kConfigFileFlag) + "=" +
28+
std::string(kDefaultConfigFileName);
29+
result = kDefaultConfigFileName;
30+
} else if (HasEqualsPrefix(arg, kConfigFileFlag)) {
31+
// --experimental-config-file=path
32+
std::string_view path =
33+
std::string_view(arg).substr(kConfigFileFlag.size() + 1);
34+
if (!path.empty()) {
35+
result = path;
2236
}
23-
} else if (it->starts_with(flag_path)) {
24-
// Case: "--experimental-config-file=foo"
25-
if (it->size() > flag_path.size() && (*it)[flag_path.size()] == '=') {
26-
return std::string_view(*it).substr(flag_path.size() + 1);
27-
}
28-
} else if (*it == default_file || it->starts_with(default_file)) {
29-
has_default_config_file = true;
37+
} else if (arg == kDefaultConfigFileFlag) {
38+
// --experimental-default-config-file
39+
arg = std::string(kConfigFileFlag) + "=" +
40+
std::string(kDefaultConfigFileName);
41+
result = kDefaultConfigFileName;
42+
} else if (HasEqualsPrefix(arg, kDefaultConfigFileFlag)) {
43+
invalid_default_config_file_argument_ = true;
3044
}
3145
}
3246

33-
if (has_default_config_file) {
34-
return "node.config.json";
35-
}
47+
return result;
48+
}
3649

37-
return std::nullopt;
50+
bool ConfigReader::HasInvalidDefaultConfigFileArgument() const {
51+
return invalid_default_config_file_argument_;
3852
}
3953

4054
ParseResult ConfigReader::ProcessOptionValue(

src/node_config_file.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ class ConfigReader {
3030
ParseResult ParseConfig(const std::string_view& config_path);
3131

3232
std::optional<std::string_view> GetDataFromArgs(
33-
const std::vector<std::string>& args);
33+
std::vector<std::string>* args);
34+
bool HasInvalidDefaultConfigFileArgument() const;
3435

3536
std::string GetNodeOptions();
3637
const std::vector<std::string>& GetNamespaceFlags() const;
@@ -53,6 +54,7 @@ class ConfigReader {
5354

5455
std::vector<std::string> node_options_;
5556
std::vector<std::string> namespace_options_;
57+
bool invalid_default_config_file_argument_ = false;
5658

5759
// Cache for fast lookup of environment options
5860
std::unordered_map<std::string, options_parser::OptionMappingDetails>

src/node_options.cc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -851,11 +851,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
851851
&EnvironmentOptions::optional_env_file);
852852
Implies("--env-file-if-exists", "[has_env_file_string]");
853853
AddOption("--experimental-config-file",
854-
"set config file from supplied file",
855-
&EnvironmentOptions::experimental_config_file_path);
856-
AddOption("--experimental-default-config-file",
857-
"set config file from default config file",
858-
&EnvironmentOptions::experimental_default_config_file);
854+
"set config file path",
855+
&EnvironmentOptions::experimental_config_file_path,
856+
kDisallowedInEnvvar);
857+
AddAlias("--experimental-default-config-file", "--experimental-config-file");
859858
AddOption("--test",
860859
"launch test runner on startup",
861860
&EnvironmentOptions::test_runner,

0 commit comments

Comments
 (0)