Skip to content

Commit 95f4f38

Browse files
src: implicitly enable namespace in config
1 parent be02a02 commit 95f4f38

File tree

9 files changed

+120
-4
lines changed

9 files changed

+120
-4
lines changed

doc/api/cli.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,40 @@ The configuration file supports namespace-specific options:
10291029

10301030
* The `nodeOptions` field contains CLI flags that are allowed in [`NODE_OPTIONS`][].
10311031

1032-
* Namespace fields like `test` contain configuration specific to that subsystem.
1032+
* Namespace fields like `test`, `watch`, and `permission` contain configuration specific to that subsystem.
1033+
1034+
When a namespace is present in the
1035+
configuration file, Node.js automatically enables the corresponding flag
1036+
(e.g., `--test`, `--watch`, `--permission`). This allows you to configure
1037+
subsystem-specific options without explicitly passing the flag on the command line.
1038+
1039+
For example:
1040+
1041+
```json
1042+
{
1043+
"test": {
1044+
"test-isolation": "process"
1045+
}
1046+
}
1047+
```
1048+
1049+
is equivalent to:
1050+
1051+
```bash
1052+
node --test --test-isolation=process
1053+
```
1054+
1055+
To disable the automatic flag while still using namespace options, you can
1056+
explicitly set the flag to `false` within the namespace:
1057+
1058+
```json
1059+
{
1060+
"test": {
1061+
"test": false,
1062+
"test-isolation": "process"
1063+
}
1064+
}
1065+
```
10331066

10341067
No-op flags are not supported.
10351068
Not all V8 flags are currently supported.

doc/api/permissions.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,11 @@ Example `node.config.json`:
175175
}
176176
```
177177

178-
Run with the configuration file:
178+
When the `permission` namespace is present in the configuration file, Node.js
179+
automatically enables the `--permission` flag. Run with:
179180

180181
```console
181-
$ node --permission --experimental-default-config-file app.js
182+
$ node --experimental-default-config-file app.js
182183
```
183184

184185
#### Using the Permission Model with `npx`

src/node_config_file.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
255255
available_namespaces.end());
256256
// Create a set to track unique options
257257
std::unordered_set<std::string> unique_options;
258+
// Namespaces in OPTION_NAMESPACE_LIST
259+
std::unordered_set<std::string> namespaces_with_implicit_flags;
260+
258261
// Iterate through the main object to find all namespaces
259262
for (auto field : main_object) {
260263
std::string_view field_name;
@@ -281,6 +284,15 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
281284
continue;
282285
}
283286

287+
// List of implicit namespace flags
288+
for (auto ns_enum : options_parser::AllNamespaces()) {
289+
std::string ns_str = options_parser::NamespaceEnumToString(ns_enum);
290+
if (!ns_str.empty() && namespace_name == ns_str) {
291+
namespaces_with_implicit_flags.insert(namespace_name);
292+
break;
293+
}
294+
}
295+
284296
// Get the namespace object
285297
simdjson::ondemand::object namespace_object;
286298
auto field_error = field.value().get_object().get(namespace_object);
@@ -302,6 +314,17 @@ ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
302314
}
303315
}
304316

317+
// Add implicit flags for namespaces (--test, --permission, --watch)
318+
// These flags are automatically enabled when their namespace is present
319+
for (const auto& ns : namespaces_with_implicit_flags) {
320+
std::string flag = "--" + ns;
321+
std::string no_flag = "--no-" + ns;
322+
// We skip if the user has already set the flag or its negation
323+
if (!unique_options.contains(flag) && !unique_options.contains(no_flag)) {
324+
namespace_options_.push_back(flag);
325+
}
326+
}
327+
305328
return ParseResult::Valid;
306329
}
307330

test/fixtures/options-as-flags/test-config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"max-http-header-size": 8192
55
},
66
"test": {
7+
"test": false,
78
"test-isolation": "none"
89
}
910
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"test": {}
2+
"permission": {}
33
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"permission": {
3+
"allow-fs-read": "*"
4+
}
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"test": {
3+
"test": false,
4+
"test-isolation": "none"
5+
}
6+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"watch": {
3+
"watch-preserve-output": true
4+
}
5+
}

test/parallel/test-config-file.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ describe('namespace-scoped options', () => {
407407
'--expose-internals',
408408
'--experimental-config-file',
409409
fixtures.path('rc/namespaced/node.config.json'),
410+
'--no-test',
410411
'-p', 'require("internal/options").getOptionValue("--test-isolation")',
411412
]);
412413
assert.strictEqual(result.stderr, '');
@@ -483,6 +484,7 @@ describe('namespace-scoped options', () => {
483484
'--test-isolation', 'process',
484485
'--experimental-config-file',
485486
fixtures.path('rc/namespaced/node.config.json'),
487+
'--no-test',
486488
'-p', 'require("internal/options").getOptionValue("--test-isolation")',
487489
]);
488490
assert.strictEqual(result.stderr, '');
@@ -498,6 +500,7 @@ describe('namespace-scoped options', () => {
498500
'--test-coverage-exclude', 'cli-pattern2',
499501
'--experimental-config-file',
500502
fixtures.path('rc/namespace-with-array.json'),
503+
'--no-test',
501504
'-p', 'JSON.stringify(require("internal/options").getOptionValue("--test-coverage-exclude"))',
502505
]);
503506
assert.strictEqual(result.stderr, '');
@@ -520,6 +523,7 @@ describe('namespace-scoped options', () => {
520523
'--expose-internals',
521524
'--experimental-config-file',
522525
fixtures.path('rc/namespace-with-disallowed-envvar.json'),
526+
'--no-test',
523527
'-p', 'require("internal/options").getOptionValue("--test-concurrency")',
524528
]);
525529
assert.strictEqual(result.stderr, '');
@@ -536,6 +540,7 @@ describe('namespace-scoped options', () => {
536540
'--test-concurrency', '2',
537541
'--experimental-config-file',
538542
fixtures.path('rc/namespace-with-disallowed-envvar.json'),
543+
'--no-test',
539544
'-p', 'require("internal/options").getOptionValue("--test-concurrency")',
540545
]);
541546
assert.strictEqual(result.stderr, '');
@@ -554,4 +559,41 @@ describe('namespace-scoped options', () => {
554559
assert.strictEqual(result.stdout, '');
555560
assert.strictEqual(result.code, 9);
556561
});
562+
563+
it('should automatically enable --test flag when test namespace is present', async () => {
564+
const result = await spawnPromisified(process.execPath, [
565+
'--no-warnings',
566+
'--experimental-config-file',
567+
fixtures.path('rc/namespaced/node.config.json'),
568+
fixtures.path('rc/test.js'),
569+
]);
570+
assert.strictEqual(result.code, 0);
571+
assert.match(result.stdout, /tests 1/);
572+
});
573+
574+
it('should automatically enable --permission flag when permission namespace is present', async () => {
575+
const result = await spawnPromisified(process.execPath, [
576+
'--no-warnings',
577+
'--expose-internals',
578+
'--experimental-config-file',
579+
fixtures.path('rc/permission-namespace.json'),
580+
'-p', 'require("internal/options").getOptionValue("--permission")',
581+
]);
582+
assert.strictEqual(result.stderr, '');
583+
assert.strictEqual(result.stdout, 'true\n');
584+
assert.strictEqual(result.code, 0);
585+
});
586+
587+
it('should respect explicit test: false in test namespace', async () => {
588+
const result = await spawnPromisified(process.execPath, [
589+
'--no-warnings',
590+
'--expose-internals',
591+
'--experimental-config-file',
592+
fixtures.path('rc/test-namespace-explicit-false.json'),
593+
'-p', 'require("internal/options").getOptionValue("--test")',
594+
]);
595+
assert.strictEqual(result.stderr, '');
596+
assert.strictEqual(result.stdout, 'false\n');
597+
assert.strictEqual(result.code, 0);
598+
});
557599
});

0 commit comments

Comments
 (0)