Skip to content

Commit 433657d

Browse files
marco-ippolitotargos
authored andcommittedMar 11, 2025
src: namespace config file flags
PR-URL: #57170 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Paolo Insogna <paolo@cowtech.it>
1 parent d8937f1 commit 433657d

20 files changed

+688
-621
lines changed
 

‎doc/api/cli.md

+10-8
Original file line numberDiff line numberDiff line change
@@ -936,17 +936,19 @@ with the following structure:
936936
```json
937937
{
938938
"$schema": "https://nodejs.org/dist/REPLACEME/docs/node_config_json_schema.json",
939-
"experimental-transform-types": true,
940-
"import": [
941-
"amaro/transform"
942-
],
943-
"disable-warning": "ExperimentalWarning",
944-
"watch-path": "src",
945-
"watch-preserve-output": true
939+
"nodeOptions": {
940+
"experimental-transform-types": true,
941+
"import": [
942+
"amaro/transform"
943+
],
944+
"disable-warning": "ExperimentalWarning",
945+
"watch-path": "src",
946+
"watch-preserve-output": true
947+
}
946948
}
947949
```
948950

949-
Only flags that are allowed in [`NODE_OPTIONS`][] are supported.
951+
In the `nodeOptions` field, only flags that are allowed in [`NODE_OPTIONS`][] are supported.
950952
No-op flags are not supported.
951953
Not all V8 flags are currently supported.
952954

‎doc/node_config_json_schema.json

+547-541
Large diffs are not rendered by default.

‎lib/internal/options.js

+14-6
Original file line numberDiff line numberDiff line change
@@ -45,33 +45,41 @@ function generateConfigJsonSchema() {
4545
$schema: 'https://json-schema.org/draft/2020-12/schema',
4646
additionalProperties: false,
4747
properties: {
48+
nodeOptions: {
49+
__proto__: null,
50+
additionalProperties: false,
51+
properties: { __proto__: null },
52+
type: 'object',
53+
},
4854
__proto__: null,
4955
},
5056
type: 'object',
5157
};
5258

59+
const nodeOptions = schema.properties.nodeOptions.properties;
60+
5361
for (const { 0: key, 1: type } of map) {
5462
const keyWithoutPrefix = StringPrototypeReplace(key, '--', '');
5563
if (type === 'array') {
56-
schema.properties[keyWithoutPrefix] = {
64+
nodeOptions[keyWithoutPrefix] = {
5765
__proto__: null,
5866
oneOf: [
5967
{ __proto__: null, type: 'string' },
60-
{ __proto__: null, type: 'array', items: { __proto__: null, type: 'string', minItems: 1 } },
68+
{ __proto__: null, items: { __proto__: null, type: 'string', minItems: 1 }, type: 'array' },
6169
],
6270
};
6371
} else {
64-
schema.properties[keyWithoutPrefix] = { __proto__: null, type };
72+
nodeOptions[keyWithoutPrefix] = { __proto__: null, type };
6573
}
6674
}
6775

6876
// Sort the proerties by key alphabetically.
69-
const sortedKeys = ArrayPrototypeSort(ObjectKeys(schema.properties));
77+
const sortedKeys = ArrayPrototypeSort(ObjectKeys(nodeOptions));
7078
const sortedProperties = ObjectFromEntries(
71-
ArrayPrototypeMap(sortedKeys, (key) => [key, schema.properties[key]]),
79+
ArrayPrototypeMap(sortedKeys, (key) => [key, nodeOptions[key]]),
7280
);
7381

74-
schema.properties = sortedProperties;
82+
schema.properties.nodeOptions.properties = sortedProperties;
7583

7684
return schema;
7785
}

‎src/node_config_file.cc

+64-45
Original file line numberDiff line numberDiff line change
@@ -27,43 +27,13 @@ std::optional<std::string_view> ConfigReader::GetDataFromArgs(
2727
return std::nullopt;
2828
}
2929

30-
ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
31-
std::string file_content;
32-
// Read the configuration file
33-
int r = ReadFileSync(&file_content, config_path.data());
34-
if (r != 0) {
35-
const char* err = uv_strerror(r);
36-
FPrintF(
37-
stderr, "Cannot read configuration from %s: %s\n", config_path, err);
38-
return ParseResult::FileError;
39-
}
40-
41-
// Parse the configuration file
42-
simdjson::ondemand::parser json_parser;
43-
simdjson::ondemand::document document;
44-
if (json_parser.iterate(file_content).get(document)) {
45-
FPrintF(stderr, "Can't parse %s\n", config_path.data());
46-
return ParseResult::InvalidContent;
47-
}
48-
49-
simdjson::ondemand::object main_object;
50-
// If document is not an object, throw an error.
51-
if (auto root_error = document.get_object().get(main_object)) {
52-
if (root_error == simdjson::error_code::INCORRECT_TYPE) {
53-
FPrintF(stderr,
54-
"Root value unexpected not an object for %s\n\n",
55-
config_path.data());
56-
} else {
57-
FPrintF(stderr, "Can't parse %s\n", config_path.data());
58-
}
59-
return ParseResult::InvalidContent;
60-
}
61-
30+
ParseResult ConfigReader::ParseNodeOptions(
31+
simdjson::ondemand::object* node_options_object) {
6232
auto env_options_map = options_parser::MapEnvOptionsFlagInputType();
6333
simdjson::ondemand::value ondemand_value;
6434
std::string_view key;
6535

66-
for (auto field : main_object) {
36+
for (auto field : *node_options_object) {
6737
if (field.unescaped_key().get(key) || field.value().get(ondemand_value)) {
6838
return ParseResult::InvalidContent;
6939
}
@@ -79,7 +49,8 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
7949
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
8050
return ParseResult::InvalidContent;
8151
}
82-
flags_.push_back(it->first + "=" + (result ? "true" : "false"));
52+
node_options_.push_back(it->first + "=" +
53+
(result ? "true" : "false"));
8354
break;
8455
}
8556
// String array can allow both string and array types
@@ -102,7 +73,7 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
10273
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
10374
return ParseResult::InvalidContent;
10475
}
105-
flags_.push_back(it->first + "=" + std::string(import));
76+
node_options_.push_back(it->first + "=" + std::string(import));
10677
}
10778
break;
10879
}
@@ -112,7 +83,7 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
11283
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
11384
return ParseResult::InvalidContent;
11485
}
115-
flags_.push_back(it->first + "=" + result);
86+
node_options_.push_back(it->first + "=" + result);
11687
break;
11788
}
11889
default:
@@ -127,7 +98,7 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
12798
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
12899
return ParseResult::InvalidContent;
129100
}
130-
flags_.push_back(it->first + "=" + result);
101+
node_options_.push_back(it->first + "=" + result);
131102
break;
132103
}
133104
case options_parser::OptionType::kInteger: {
@@ -136,7 +107,7 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
136107
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
137108
return ParseResult::InvalidContent;
138109
}
139-
flags_.push_back(it->first + "=" + std::to_string(result));
110+
node_options_.push_back(it->first + "=" + std::to_string(result));
140111
break;
141112
}
142113
case options_parser::OptionType::kHostPort:
@@ -146,7 +117,7 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
146117
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
147118
return ParseResult::InvalidContent;
148119
}
149-
flags_.push_back(it->first + "=" + std::to_string(result));
120+
node_options_.push_back(it->first + "=" + std::to_string(result));
150121
break;
151122
}
152123
case options_parser::OptionType::kNoOp: {
@@ -170,26 +141,74 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
170141
return ParseResult::InvalidContent;
171142
}
172143
}
144+
return ParseResult::Valid;
145+
}
146+
147+
ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
148+
std::string file_content;
149+
// Read the configuration file
150+
int r = ReadFileSync(&file_content, config_path.data());
151+
if (r != 0) {
152+
const char* err = uv_strerror(r);
153+
FPrintF(
154+
stderr, "Cannot read configuration from %s: %s\n", config_path, err);
155+
return ParseResult::FileError;
156+
}
157+
158+
// Parse the configuration file
159+
simdjson::ondemand::parser json_parser;
160+
simdjson::ondemand::document document;
161+
if (json_parser.iterate(file_content).get(document)) {
162+
FPrintF(stderr, "Can't parse %s\n", config_path.data());
163+
return ParseResult::InvalidContent;
164+
}
165+
166+
simdjson::ondemand::object main_object;
167+
// If document is not an object, throw an error.
168+
if (auto root_error = document.get_object().get(main_object)) {
169+
if (root_error == simdjson::error_code::INCORRECT_TYPE) {
170+
FPrintF(stderr,
171+
"Root value unexpected not an object for %s\n\n",
172+
config_path.data());
173+
} else {
174+
FPrintF(stderr, "Can't parse %s\n", config_path.data());
175+
}
176+
return ParseResult::InvalidContent;
177+
}
178+
179+
simdjson::ondemand::object node_options_object;
180+
// If "nodeOptions" is an object, parse it
181+
if (auto node_options_error =
182+
main_object["nodeOptions"].get_object().get(node_options_object)) {
183+
if (node_options_error != simdjson::error_code::NO_SUCH_FIELD) {
184+
FPrintF(stderr,
185+
"\"nodeOptions\" value unexpected for %s\n\n",
186+
config_path.data());
187+
return ParseResult::InvalidContent;
188+
}
189+
} else {
190+
return ParseNodeOptions(&node_options_object);
191+
}
173192

174193
return ParseResult::Valid;
175194
}
176195

177196
std::string ConfigReader::AssignNodeOptions() {
178-
if (flags_.empty()) {
197+
if (node_options_.empty()) {
179198
return "";
180199
} else {
181-
DCHECK_GT(flags_.size(), 0);
200+
DCHECK_GT(node_options_.size(), 0);
182201
std::string acc;
183-
acc.reserve(flags_.size() * 2);
184-
for (size_t i = 0; i < flags_.size(); ++i) {
202+
acc.reserve(node_options_.size() * 2);
203+
for (size_t i = 0; i < node_options_.size(); ++i) {
185204
// The space is necessary at the beginning of the string
186-
acc += " " + flags_[i];
205+
acc += " " + node_options_[i];
187206
}
188207
return acc;
189208
}
190209
}
191210

192211
size_t ConfigReader::GetFlagsSize() {
193-
return flags_.size();
212+
return node_options_.size();
194213
}
195214
} // namespace node

‎src/node_config_file.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ class ConfigReader {
3333
size_t GetFlagsSize();
3434

3535
private:
36-
std::vector<std::string> flags_;
36+
ParseResult ParseNodeOptions(simdjson::ondemand::object* node_options_object);
37+
38+
std::vector<std::string> node_options_;
3739
};
3840

3941
} // namespace node

‎test/fixtures/rc/host-port.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"inspect-port": 65535
2+
"nodeOptions": {
3+
"inspect-port": 65535
4+
}
35
}
+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"import": "./test/fixtures/printA.js"
2+
"nodeOptions":{
3+
"import": "./test/fixtures/printA.js"
4+
}
35
}

‎test/fixtures/rc/import.json

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{
2-
"import": [
3-
"./test/fixtures/printA.js",
4-
"./test/fixtures/printB.js",
5-
"./test/fixtures/printC.js"
6-
]
2+
"nodeOptions": {
3+
"import": [
4+
"./test/fixtures/printA.js",
5+
"./test/fixtures/printB.js",
6+
"./test/fixtures/printC.js"
7+
]
8+
}
79
}

‎test/fixtures/rc/invalid-import.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
{
2-
"import": [1]
2+
"nodeOptions": {
3+
"import": [
4+
1
5+
]
6+
}
37
}
+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"max-http-header-size": -1
2+
"nodeOptions": {
3+
"max-http-header-size": -1
4+
}
35
}

‎test/fixtures/rc/no-op.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"http-parser": true
2+
"nodeOptions": {
3+
"http-parser": true
4+
}
35
}
+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"--test": true
2+
"nodeOptions": {
3+
"test": true
4+
}
35
}

‎test/fixtures/rc/numeric.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"max-http-header-size": 4294967295
2+
"nodeOptions": {
3+
"max-http-header-size": 4294967295
4+
}
35
}
+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
2-
"experimental-transform-types": true,
3-
"experimental-transform-types": false
2+
"nodeOptions": {
3+
"experimental-transform-types": true,
4+
"experimental-transform-types": false
5+
}
46
}

‎test/fixtures/rc/sneaky-flag.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"import": "./test/fixtures/printA.js --experimental-transform-types"
2+
"nodeOptions": {
3+
"import": "./test/fixtures/printA.js --experimental-transform-types"
4+
}
35
}

‎test/fixtures/rc/string.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"test-reporter": "dot"
2+
"nodeOptions": {
3+
"test-reporter": "dot"
4+
}
35
}

‎test/fixtures/rc/transform-types.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"experimental-transform-types": true
2+
"nodeOptions": {
3+
"experimental-transform-types": true
4+
}
35
}

‎test/fixtures/rc/unknown-flag.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"some-unknown-flag": true
2+
"nodeOptions": {
3+
"some-unknown-flag": true
4+
}
35
}

‎test/fixtures/rc/v8-flag.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
{
2-
"abort-on-uncaught-exception": true
2+
"nodeOptions": {
3+
"abort-on-uncaught-exception": true
4+
}
35
}

‎test/parallel/test-config-file.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ test('should throw at flag not available in NODE_OPTIONS', async () => {
162162
fixtures.path('rc/not-node-options-flag.json'),
163163
'-p', '"Hello, World!"',
164164
]);
165-
match(result.stderr, /Unknown or not allowed option --test/);
165+
match(result.stderr, /Unknown or not allowed option test/);
166166
strictEqual(result.stdout, '');
167167
strictEqual(result.code, 9);
168168
});

0 commit comments

Comments
 (0)
Please sign in to comment.