src: namespace config file flags

PR-URL: https://github.com/nodejs/node/pull/57170
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
This commit is contained in:
Marco Ippolito authored and Node.js GitHub Bot committed 2025-02-24 09:54:56 +00:00
1 parent 992036ec32
commit 56773ba50d
20 files changed
+757 -690

No files matched your search

+10 -8
View File
@@ -928,17 +928,19 @@ with the following structure:
```json
{
"$schema": "https://nodejs.org/dist/REPLACEME/docs/node_config_json_schema.json",
"experimental-transform-types": true,
"import": [
"amaro/transform"
],
"disable-warning": "ExperimentalWarning",
"watch-path": "src",
"watch-preserve-output": true
"nodeOptions": {
"experimental-transform-types": true,
"import": [
"amaro/transform"
],
"disable-warning": "ExperimentalWarning",
"watch-path": "src",
"watch-preserve-output": true
}
}
```
Only flags that are allowed in [`NODE_OPTIONS`][] are supported.
In the `nodeOptions` field, only flags that are allowed in [`NODE_OPTIONS`][] are supported.
No-op flags are not supported.
Not all V8 flags are currently supported.
File diff suppressed because it is too large. Load diff
+14 -6
View File
@@ -45,33 +45,41 @@ function generateConfigJsonSchema() {
$schema: 'https://json-schema.org/draft/2020-12/schema',
additionalProperties: false,
properties: {
nodeOptions: {
__proto__: null,
additionalProperties: false,
properties: { __proto__: null },
type: 'object',
},
__proto__: null,
},
type: 'object',
};
const nodeOptions = schema.properties.nodeOptions.properties;
for (const { 0: key, 1: type } of map) {
const keyWithoutPrefix = StringPrototypeReplace(key, '--', '');
if (type === 'array') {
schema.properties[keyWithoutPrefix] = {
nodeOptions[keyWithoutPrefix] = {
__proto__: null,
oneOf: [
{ __proto__: null, type: 'string' },
{ __proto__: null, type: 'array', items: { __proto__: null, type: 'string', minItems: 1 } },
{ __proto__: null, items: { __proto__: null, type: 'string', minItems: 1 }, type: 'array' },
],
};
} else {
schema.properties[keyWithoutPrefix] = { __proto__: null, type };
nodeOptions[keyWithoutPrefix] = { __proto__: null, type };
}
}
// Sort the proerties by key alphabetically.
const sortedKeys = ArrayPrototypeSort(ObjectKeys(schema.properties));
const sortedKeys = ArrayPrototypeSort(ObjectKeys(nodeOptions));
const sortedProperties = ObjectFromEntries(
ArrayPrototypeMap(sortedKeys, (key) => [key, schema.properties[key]]),
ArrayPrototypeMap(sortedKeys, (key) => [key, nodeOptions[key]]),
);
schema.properties = sortedProperties;
schema.properties.nodeOptions.properties = sortedProperties;
return schema;
}
+133 -114
View File
@@ -27,6 +27,123 @@ std::optional<std::string_view> ConfigReader::GetDataFromArgs(
return std::nullopt;
}
ParseResult ConfigReader::ParseNodeOptions(
simdjson::ondemand::object* node_options_object) {
auto env_options_map = options_parser::MapEnvOptionsFlagInputType();
simdjson::ondemand::value ondemand_value;
std::string_view key;
for (auto field : *node_options_object) {
if (field.unescaped_key().get(key) || field.value().get(ondemand_value)) {
return ParseResult::InvalidContent;
}
// The key needs to match the CLI option
std::string prefix = "--";
auto it = env_options_map.find(prefix.append(key));
if (it != env_options_map.end()) {
switch (it->second) {
case options_parser::OptionType::kBoolean: {
bool result;
if (ondemand_value.get_bool().get(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
node_options_.push_back(it->first + "=" +
(result ? "true" : "false"));
break;
}
// String array can allow both string and array types
case options_parser::OptionType::kStringList: {
simdjson::ondemand::json_type field_type;
if (ondemand_value.type().get(field_type)) {
return ParseResult::InvalidContent;
}
switch (field_type) {
case simdjson::ondemand::json_type::array: {
std::vector<std::string> result;
simdjson::ondemand::array raw_imports;
if (ondemand_value.get_array().get(raw_imports)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
for (auto raw_import : raw_imports) {
std::string_view import;
if (raw_import.get_string(import)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
node_options_.push_back(it->first + "=" + std::string(import));
}
break;
}
case simdjson::ondemand::json_type::string: {
std::string result;
if (ondemand_value.get_string(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
node_options_.push_back(it->first + "=" + result);
break;
}
default:
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
break;
}
case options_parser::OptionType::kString: {
std::string result;
if (ondemand_value.get_string(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
node_options_.push_back(it->first + "=" + result);
break;
}
case options_parser::OptionType::kInteger: {
int64_t result;
if (ondemand_value.get_int64().get(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
node_options_.push_back(it->first + "=" + std::to_string(result));
break;
}
case options_parser::OptionType::kHostPort:
case options_parser::OptionType::kUInteger: {
uint64_t result;
if (ondemand_value.get_uint64().get(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
node_options_.push_back(it->first + "=" + std::to_string(result));
break;
}
case options_parser::OptionType::kNoOp: {
FPrintF(stderr,
"No-op flag %s is currently not supported\n",
it->first.c_str());
return ParseResult::InvalidContent;
break;
}
case options_parser::OptionType::kV8Option: {
FPrintF(stderr,
"V8 flag %s is currently not supported\n",
it->first.c_str());
return ParseResult::InvalidContent;
}
default:
UNREACHABLE();
}
} else {
FPrintF(stderr, "Unknown or not allowed option %s\n", key.data());
return ParseResult::InvalidContent;
}
}
return ParseResult::Valid;
}
ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
std::string file_content;
// Read the configuration file
@@ -59,137 +176,39 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
return ParseResult::InvalidContent;
}
auto env_options_map = options_parser::MapEnvOptionsFlagInputType();
simdjson::ondemand::value ondemand_value;
std::string_view key;
for (auto field : main_object) {
if (field.unescaped_key().get(key) || field.value().get(ondemand_value)) {
return ParseResult::InvalidContent;
}
// The key needs to match the CLI option
std::string prefix = "--";
auto it = env_options_map.find(prefix.append(key));
if (it != env_options_map.end()) {
switch (it->second) {
case options_parser::OptionType::kBoolean: {
bool result;
if (ondemand_value.get_bool().get(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
flags_.push_back(it->first + "=" + (result ? "true" : "false"));
break;
}
// String array can allow both string and array types
case options_parser::OptionType::kStringList: {
simdjson::ondemand::json_type field_type;
if (ondemand_value.type().get(field_type)) {
return ParseResult::InvalidContent;
}
switch (field_type) {
case simdjson::ondemand::json_type::array: {
std::vector<std::string> result;
simdjson::ondemand::array raw_imports;
if (ondemand_value.get_array().get(raw_imports)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
for (auto raw_import : raw_imports) {
std::string_view import;
if (raw_import.get_string(import)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
flags_.push_back(it->first + "=" + std::string(import));
}
break;
}
case simdjson::ondemand::json_type::string: {
std::string result;
if (ondemand_value.get_string(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
flags_.push_back(it->first + "=" + result);
break;
}
default:
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
break;
}
case options_parser::OptionType::kString: {
std::string result;
if (ondemand_value.get_string(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
flags_.push_back(it->first + "=" + result);
break;
}
case options_parser::OptionType::kInteger: {
int64_t result;
if (ondemand_value.get_int64().get(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
flags_.push_back(it->first + "=" + std::to_string(result));
break;
}
case options_parser::OptionType::kHostPort:
case options_parser::OptionType::kUInteger: {
uint64_t result;
if (ondemand_value.get_uint64().get(result)) {
FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
return ParseResult::InvalidContent;
}
flags_.push_back(it->first + "=" + std::to_string(result));
break;
}
case options_parser::OptionType::kNoOp: {
FPrintF(stderr,
"No-op flag %s is currently not supported\n",
it->first.c_str());
return ParseResult::InvalidContent;
break;
}
case options_parser::OptionType::kV8Option: {
FPrintF(stderr,
"V8 flag %s is currently not supported\n",
it->first.c_str());
return ParseResult::InvalidContent;
}
default:
UNREACHABLE();
}
} else {
FPrintF(stderr, "Unknown or not allowed option %s\n", key.data());
simdjson::ondemand::object node_options_object;
// If "nodeOptions" is an object, parse it
if (auto node_options_error =
main_object["nodeOptions"].get_object().get(node_options_object)) {
if (node_options_error != simdjson::error_code::NO_SUCH_FIELD) {
FPrintF(stderr,
"\"nodeOptions\" value unexpected for %s\n\n",
config_path.data());
return ParseResult::InvalidContent;
}
} else {
return ParseNodeOptions(&node_options_object);
}
return ParseResult::Valid;
}
std::string ConfigReader::AssignNodeOptions() {
if (flags_.empty()) {
if (node_options_.empty()) {
return "";
} else {
DCHECK_GT(flags_.size(), 0);
DCHECK_GT(node_options_.size(), 0);
std::string acc;
acc.reserve(flags_.size() * 2);
for (size_t i = 0; i < flags_.size(); ++i) {
acc.reserve(node_options_.size() * 2);
for (size_t i = 0; i < node_options_.size(); ++i) {
// The space is necessary at the beginning of the string
acc += " " + flags_[i];
acc += " " + node_options_[i];
}
return acc;
}
}
size_t ConfigReader::GetFlagsSize() {
return flags_.size();
return node_options_.size();
}
} // namespace node
+3 -1
View File
@@ -33,7 +33,9 @@ class ConfigReader {
size_t GetFlagsSize();
private:
std::vector<std::string> flags_;
ParseResult ParseNodeOptions(simdjson::ondemand::object* node_options_object);
std::vector<std::string> node_options_;
};
} // namespace node
+3 -1
View File
@@ -1,3 +1,5 @@
{
"inspect-port": 65535
"nodeOptions": {
"inspect-port": 65535
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"import": "./test/fixtures/printA.js"
"nodeOptions":{
"import": "./test/fixtures/printA.js"
}
}
+7 -5
View File
@@ -1,7 +1,9 @@
{
"import": [
"./test/fixtures/printA.js",
"./test/fixtures/printB.js",
"./test/fixtures/printC.js"
]
"nodeOptions": {
"import": [
"./test/fixtures/printA.js",
"./test/fixtures/printB.js",
"./test/fixtures/printC.js"
]
}
}
+5 -1
View File
@@ -1,3 +1,7 @@
{
"import": [1]
"nodeOptions": {
"import": [
1
]
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"max-http-header-size": -1
"nodeOptions": {
"max-http-header-size": -1
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"http-parser": true
"nodeOptions": {
"http-parser": true
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"--test": true
"nodeOptions": {
"test": true
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"max-http-header-size": 4294967295
"nodeOptions": {
"max-http-header-size": 4294967295
}
}
+4 -2
View File
@@ -1,4 +1,6 @@
{
"experimental-transform-types": true,
"experimental-transform-types": false
"nodeOptions": {
"experimental-transform-types": true,
"experimental-transform-types": false
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"import": "./test/fixtures/printA.js --experimental-transform-types"
"nodeOptions": {
"import": "./test/fixtures/printA.js --experimental-transform-types"
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"test-reporter": "dot"
"nodeOptions": {
"test-reporter": "dot"
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"experimental-transform-types": true
"nodeOptions": {
"experimental-transform-types": true
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"some-unknown-flag": true
"nodeOptions": {
"some-unknown-flag": true
}
}
+3 -1
View File
@@ -1,3 +1,5 @@
{
"abort-on-uncaught-exception": true
"nodeOptions": {
"abort-on-uncaught-exception": true
}
}
+1 -1
View File
@@ -162,7 +162,7 @@ test('should throw at flag not available in NODE_OPTIONS', async () => {
fixtures.path('rc/not-node-options-flag.json'),
'-p', '"Hello, World!"',
]);
match(result.stderr, /Unknown or not allowed option --test/);
match(result.stderr, /Unknown or not allowed option test/);
strictEqual(result.stdout, '');
strictEqual(result.code, 9);
});