Skip to content

Commit f55a9b6

Browse files
pmarchinigeeksilva97
authored andcommitted
src: support namespace options in configuration file
PR-URL: #58073 Reviewed-By: Marco Ippolito <[email protected]> Reviewed-By: Giovanni Bucci <[email protected]> Reviewed-By: Daniel Lemire <[email protected]>
1 parent 2986eca commit f55a9b6

20 files changed

+1024
-276
lines changed

doc/api/cli.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -934,11 +934,19 @@ with the following structure:
934934
],
935935
"watch-path": "src",
936936
"watch-preserve-output": true
937+
},
938+
"testRunner": {
939+
"test-isolation": "process"
937940
}
938941
}
939942
```
940943

941-
In the `nodeOptions` field, only flags that are allowed in [`NODE_OPTIONS`][] are supported.
944+
The configuration file supports namespace-specific options:
945+
946+
* The `nodeOptions` field contains CLI flags that are allowed in [`NODE_OPTIONS`][].
947+
948+
* Namespace fields like `testRunner` contain configuration specific to that subsystem.
949+
942950
No-op flags are not supported.
943951
Not all V8 flags are currently supported.
944952

@@ -952,7 +960,7 @@ For example, the configuration file above is equivalent to
952960
the following command-line arguments:
953961

954962
```bash
955-
node --import amaro/strip --watch-path=src --watch-preserve-output
963+
node --import amaro/strip --watch-path=src --watch-preserve-output --test-isolation=process
956964
```
957965

958966
The priority in configuration is as follows:
@@ -965,11 +973,10 @@ Values in the configuration file will not override the values in the environment
965973
variables and command-line options, but will override the values in the `NODE_OPTIONS`
966974
env file parsed by the `--env-file` flag.
967975

968-
If duplicate keys are present in the configuration file, only
969-
the first key will be used.
976+
Keys cannot be duplicated within the same or different namespaces.
970977

971978
The configuration parser will throw an error if the configuration file contains
972-
unknown keys or keys that cannot used in `NODE_OPTIONS`.
979+
unknown keys or keys that cannot be used in a namespace.
973980

974981
Node.js will not sanitize or perform validation on the user-provided configuration,
975982
so **NEVER** use untrusted configuration files.

doc/node-config-schema.json

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,135 @@
590590
}
591591
},
592592
"type": "object"
593+
},
594+
"testRunner": {
595+
"type": "object",
596+
"additionalProperties": false,
597+
"properties": {
598+
"experimental-test-coverage": {
599+
"type": "boolean"
600+
},
601+
"experimental-test-module-mocks": {
602+
"type": "boolean"
603+
},
604+
"test-concurrency": {
605+
"type": "number"
606+
},
607+
"test-coverage-branches": {
608+
"type": "number"
609+
},
610+
"test-coverage-exclude": {
611+
"oneOf": [
612+
{
613+
"type": "string"
614+
},
615+
{
616+
"items": {
617+
"type": "string",
618+
"minItems": 1
619+
},
620+
"type": "array"
621+
}
622+
]
623+
},
624+
"test-coverage-functions": {
625+
"type": "number"
626+
},
627+
"test-coverage-include": {
628+
"oneOf": [
629+
{
630+
"type": "string"
631+
},
632+
{
633+
"items": {
634+
"type": "string",
635+
"minItems": 1
636+
},
637+
"type": "array"
638+
}
639+
]
640+
},
641+
"test-coverage-lines": {
642+
"type": "number"
643+
},
644+
"test-force-exit": {
645+
"type": "boolean"
646+
},
647+
"test-global-setup": {
648+
"type": "string"
649+
},
650+
"test-isolation": {
651+
"type": "string"
652+
},
653+
"test-name-pattern": {
654+
"oneOf": [
655+
{
656+
"type": "string"
657+
},
658+
{
659+
"items": {
660+
"type": "string",
661+
"minItems": 1
662+
},
663+
"type": "array"
664+
}
665+
]
666+
},
667+
"test-only": {
668+
"type": "boolean"
669+
},
670+
"test-reporter": {
671+
"oneOf": [
672+
{
673+
"type": "string"
674+
},
675+
{
676+
"items": {
677+
"type": "string",
678+
"minItems": 1
679+
},
680+
"type": "array"
681+
}
682+
]
683+
},
684+
"test-reporter-destination": {
685+
"oneOf": [
686+
{
687+
"type": "string"
688+
},
689+
{
690+
"items": {
691+
"type": "string",
692+
"minItems": 1
693+
},
694+
"type": "array"
695+
}
696+
]
697+
},
698+
"test-shard": {
699+
"type": "string"
700+
},
701+
"test-skip-pattern": {
702+
"oneOf": [
703+
{
704+
"type": "string"
705+
},
706+
{
707+
"items": {
708+
"type": "string",
709+
"minItems": 1
710+
},
711+
"type": "array"
712+
}
713+
]
714+
},
715+
"test-timeout": {
716+
"type": "number"
717+
},
718+
"test-update-snapshots": {
719+
"type": "boolean"
720+
}
721+
}
593722
}
594723
},
595724
"type": "object"

lib/internal/options.js

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
getCLIOptionsInfo,
1414
getEmbedderOptions: getEmbedderOptionsFromBinding,
1515
getEnvOptionsInputType,
16+
getNamespaceOptionsInputType,
1617
} = internalBinding('options');
1718

1819
let warnOnAllowUnauthorized = true;
@@ -38,7 +39,22 @@ function getEmbedderOptions() {
3839
}
3940

4041
function generateConfigJsonSchema() {
41-
const map = getEnvOptionsInputType();
42+
const envOptionsMap = getEnvOptionsInputType();
43+
const namespaceOptionsMap = getNamespaceOptionsInputType();
44+
45+
function createPropertyForType(type) {
46+
if (type === 'array') {
47+
return {
48+
__proto__: null,
49+
oneOf: [
50+
{ __proto__: null, type: 'string' },
51+
{ __proto__: null, items: { __proto__: null, type: 'string', minItems: 1 }, type: 'array' },
52+
],
53+
};
54+
}
55+
56+
return { __proto__: null, type };
57+
}
4258

4359
const schema = {
4460
__proto__: null,
@@ -60,31 +76,58 @@ function generateConfigJsonSchema() {
6076
type: 'object',
6177
};
6278

63-
const nodeOptions = schema.properties.nodeOptions.properties;
79+
// Get the root properties object for adding namespaces
80+
const rootProperties = schema.properties;
81+
const nodeOptions = rootProperties.nodeOptions.properties;
6482

65-
for (const { 0: key, 1: type } of map) {
83+
// Add env options to nodeOptions (backward compatibility)
84+
for (const { 0: key, 1: type } of envOptionsMap) {
6685
const keyWithoutPrefix = StringPrototypeReplace(key, '--', '');
67-
if (type === 'array') {
68-
nodeOptions[keyWithoutPrefix] = {
69-
__proto__: null,
70-
oneOf: [
71-
{ __proto__: null, type: 'string' },
72-
{ __proto__: null, items: { __proto__: null, type: 'string', minItems: 1 }, type: 'array' },
73-
],
74-
};
75-
} else {
76-
nodeOptions[keyWithoutPrefix] = { __proto__: null, type };
86+
nodeOptions[keyWithoutPrefix] = createPropertyForType(type);
87+
}
88+
89+
// Add namespace properties at the root level
90+
for (const { 0: namespace, 1: optionsMap } of namespaceOptionsMap) {
91+
// Create namespace object at the root level
92+
rootProperties[namespace] = {
93+
__proto__: null,
94+
type: 'object',
95+
additionalProperties: false,
96+
properties: { __proto__: null },
97+
};
98+
99+
const namespaceProperties = rootProperties[namespace].properties;
100+
101+
// Add all options for this namespace
102+
for (const { 0: optionName, 1: optionType } of optionsMap) {
103+
const keyWithoutPrefix = StringPrototypeReplace(optionName, '--', '');
104+
namespaceProperties[keyWithoutPrefix] = createPropertyForType(optionType);
77105
}
106+
107+
// Sort the namespace properties alphabetically
108+
const sortedNamespaceKeys = ArrayPrototypeSort(ObjectKeys(namespaceProperties));
109+
const sortedNamespaceProperties = ObjectFromEntries(
110+
ArrayPrototypeMap(sortedNamespaceKeys, (key) => [key, namespaceProperties[key]]),
111+
);
112+
rootProperties[namespace].properties = sortedNamespaceProperties;
78113
}
79114

80-
// Sort the proerties by key alphabetically.
115+
// Sort the top-level properties by key alphabetically
81116
const sortedKeys = ArrayPrototypeSort(ObjectKeys(nodeOptions));
82117
const sortedProperties = ObjectFromEntries(
83118
ArrayPrototypeMap(sortedKeys, (key) => [key, nodeOptions[key]]),
84119
);
85120

86121
schema.properties.nodeOptions.properties = sortedProperties;
87122

123+
// Also sort the root level properties
124+
const sortedRootKeys = ArrayPrototypeSort(ObjectKeys(rootProperties));
125+
const sortedRootProperties = ObjectFromEntries(
126+
ArrayPrototypeMap(sortedRootKeys, (key) => [key, rootProperties[key]]),
127+
);
128+
129+
schema.properties = sortedRootProperties;
130+
88131
return schema;
89132
}
90133

lib/internal/test_runner/runner.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,13 @@ let debug = require('internal/util/debuglog').debuglog('test_runner', (fn) => {
9696
});
9797

9898
const kIsolatedProcessName = Symbol('kIsolatedProcessName');
99-
const kFilterArgs = ['--test', '--experimental-test-coverage', '--watch'];
99+
const kFilterArgs = [
100+
'--test',
101+
'--experimental-test-coverage',
102+
'--watch',
103+
'--experimental-default-config-file',
104+
'--experimental-config-file',
105+
];
100106
const kFilterArgValues = ['--test-reporter', '--test-reporter-destination'];
101107
const kDiagnosticsFilterArgs = ['tests', 'suites', 'pass', 'fail', 'cancelled', 'skipped', 'todo', 'duration_ms'];
102108

src/node.cc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,7 @@ static ExitCode InitializeNodeWithArgsInternal(
972972
default:
973973
UNREACHABLE();
974974
}
975-
node_options_from_config = per_process::config_reader.AssignNodeOptions();
975+
node_options_from_config = per_process::config_reader.GetNodeOptions();
976976
// (@marco-ippolito) Avoid reparsing the env options again
977977
std::vector<std::string> env_argv_from_config =
978978
ParseNodeOptionsEnvVar(node_options_from_config, errors);
@@ -1018,7 +1018,19 @@ static ExitCode InitializeNodeWithArgsInternal(
10181018
#endif
10191019

10201020
if (!(flags & ProcessInitializationFlags::kDisableCLIOptions)) {
1021-
const ExitCode exit_code =
1021+
// Parse the options coming from the config file.
1022+
// This is done before parsing the command line options
1023+
// as the cli flags are expected to override the config file ones.
1024+
std::vector<std::string> extra_argv =
1025+
per_process::config_reader.GetNamespaceFlags();
1026+
// [0] is expected to be the program name, fill it in from the real argv.
1027+
extra_argv.insert(extra_argv.begin(), argv->at(0));
1028+
// Parse the extra argv coming from the config file
1029+
ExitCode exit_code = ProcessGlobalArgsInternal(
1030+
&extra_argv, nullptr, errors, kDisallowedInEnvvar);
1031+
if (exit_code != ExitCode::kNoFailure) return exit_code;
1032+
// Parse options coming from the command line.
1033+
exit_code =
10221034
ProcessGlobalArgsInternal(argv, exec_argv, errors, kDisallowedInEnvvar);
10231035
if (exit_code != ExitCode::kNoFailure) return exit_code;
10241036
}

0 commit comments

Comments
 (0)