Skip to content

Commit fe60554

Browse files
src: add config file support
1 parent 9ce1fff commit fe60554

23 files changed

+597
-0
lines changed

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,7 @@ doc: $(NODE_EXE) doc-only ## Build Node.js, and then build the documentation wit
807807

808808
out/doc:
809809
mkdir -p $@
810+
cp doc/node_config_json_schema.json $@
810811

811812
# If it's a source tarball, doc/api already contains the generated docs.
812813
# Just copy everything under doc/api over.

doc/api/cli.md

+71
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,77 @@ added: v23.6.0
911911
912912
Enable experimental import support for `.node` addons.
913913

914+
### `--experimental-config-file`
915+
916+
<!-- YAML
917+
added: REPLACEME
918+
-->
919+
920+
> Stability: 1.0 - Early development
921+
922+
Use this flag to specify a configuration file that will be loaded and parsed
923+
before the application starts.
924+
Node.js will read the configuration file and apply the settings.
925+
The configuration file should be a JSON file
926+
with the following structure:
927+
928+
```json
929+
{
930+
"experimental": {
931+
"transform_types": true
932+
},
933+
"import": [
934+
"amaro/transform"
935+
],
936+
"test": {
937+
"only": true
938+
}
939+
}
940+
```
941+
942+
Each key in the configuration file corresponds to a flag that can be passed
943+
as a command-line argument. The value of the key is the value that would be
944+
passed to the flag.
945+
946+
For example, the configuration file above is equivalent to
947+
the following command-line arguments:
948+
949+
```bash
950+
node --experimental-transform-types --import amaro/transform
951+
```
952+
953+
The priority in configuration is as follows:
954+
955+
* NODE\_OPTIONS and command-line options
956+
* Config file
957+
* Dotenv NODE\_OPTIONS
958+
959+
If multiple keys are present in the configuration file,
960+
the last one will override the previous ones.
961+
Unknown keys will be ignored.
962+
963+
It possible to use the official json schema to validate the configuration file,
964+
which may vary depending on the Node.js version.
965+
966+
```json
967+
{
968+
"$schema": "https://nodejs.org/dist/REPLACEME/docs/node_config_json_schema.json",
969+
}
970+
```
971+
972+
Node.js will not sanitize or perform validation on the user-provided configuration,
973+
so **NEVER** use untrusted configuration files.
974+
In the example below `--some-other-flag`
975+
will not be sanitized and will be passed to the Node.js process.
976+
977+
```json
978+
{
979+
"import": [
980+
"amaro/transform --some-other-flag"
981+
],
982+
}
983+
```
984+
914985
### `--experimental-eventsource`
915986

916987
<!-- YAML

doc/node.1

+3
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ Interpret the entry point as a URL.
166166
.It Fl -experimental-addon-modules
167167
Enable experimental addon module support.
168168
.
169+
.It Fl -experimental-config-file
170+
Enable support for experimental config file
171+
.
169172
.It Fl -experimental-import-meta-resolve
170173
Enable experimental ES modules support for import.meta.resolve().
171174
.

doc/node_config_json_schema.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"type": "object",
4+
"properties": {
5+
"experimental": {
6+
"type": "object",
7+
"properties": {
8+
"transform_types": {
9+
"type": "boolean"
10+
}
11+
},
12+
"required": [],
13+
"additionalProperties": false
14+
},
15+
"import": {
16+
"type": "array",
17+
"items": {
18+
"type": "string"
19+
}
20+
},
21+
"test": {
22+
"type": "object",
23+
"properties": {
24+
"only": {
25+
"type": "boolean"
26+
}
27+
},
28+
"required": [],
29+
"additionalProperties": false
30+
}
31+
},
32+
"additionalProperties": false
33+
}

lib/internal/process/pre_execution.js

+8
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ function prepareExecution(options) {
116116
initializeSourceMapsHandlers();
117117
initializeDeprecations();
118118

119+
setupConfigFile();
120+
119121
require('internal/dns/utils').initializeDns();
120122

121123
if (isMainThread) {
@@ -312,6 +314,12 @@ function setupSQLite() {
312314
BuiltinModule.allowRequireByUsers('sqlite');
313315
}
314316

317+
function setupConfigFile() {
318+
if (getOptionValue('--experimental-config-file')) {
319+
emitExperimentalWarning('--experimental-config-file');
320+
}
321+
}
322+
315323
function setupQuic() {
316324
if (!getOptionValue('--experimental-quic')) {
317325
return;

node.gyp

+2
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
'src/node_process_events.cc',
131131
'src/node_process_methods.cc',
132132
'src/node_process_object.cc',
133+
'src/node_rc.cc',
133134
'src/node_realm.cc',
134135
'src/node_report.cc',
135136
'src/node_report_module.cc',
@@ -262,6 +263,7 @@
262263
'src/node_platform.h',
263264
'src/node_process.h',
264265
'src/node_process-inl.h',
266+
'src/node_rc.h',
265267
'src/node_realm.h',
266268
'src/node_realm-inl.h',
267269
'src/node_report.h',

src/node.cc

+21
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#include "node.h"
2323
#include "node_dotenv.h"
24+
#include "node_rc.h"
2425
#include "node_task_runner.h"
2526

2627
// ========== local headers ==========
@@ -150,6 +151,9 @@ namespace per_process {
150151
// Instance is used to store environment variables including NODE_OPTIONS.
151152
node::Dotenv dotenv_file = Dotenv();
152153

154+
// node_rc.h
155+
node::ConfigReader config_reader = ConfigReader();
156+
153157
// node_revert.h
154158
// Bit flag used to track security reverts.
155159
unsigned int reverted_cve = 0;
@@ -884,6 +888,23 @@ static ExitCode InitializeNodeWithArgsInternal(
884888
per_process::dotenv_file.AssignNodeOptionsIfAvailable(&node_options);
885889
}
886890

891+
auto result = per_process::config_reader.GetDataFromArgs(*argv);
892+
if (result.has_value()) {
893+
switch (per_process::config_reader.ParseConfig(result.value())) {
894+
case ConfigReader::ParseResult::Valid:
895+
break;
896+
case ConfigReader::ParseResult::InvalidContent:
897+
errors->push_back(result.value() + ": invalid format");
898+
break;
899+
case ConfigReader::ParseResult::FileError:
900+
errors->push_back(result.value() + ": not found");
901+
break;
902+
default:
903+
UNREACHABLE();
904+
}
905+
per_process::config_reader.AssignNodeOptions(&node_options);
906+
}
907+
887908
#if !defined(NODE_WITHOUT_NODE_OPTIONS)
888909
if (!(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv)) {
889910
// NODE_OPTIONS environment variable is preferred over the file one.

src/node_options.cc

+3
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
671671
"set environment variables from supplied file",
672672
&EnvironmentOptions::optional_env_file);
673673
Implies("--env-file-if-exists", "[has_env_file_string]");
674+
AddOption("--experimental-config-file",
675+
"set config file from supplied file",
676+
&EnvironmentOptions::experimental_config_file);
674677
AddOption("--test",
675678
"launch test runner on startup",
676679
&EnvironmentOptions::test_runner);

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ class EnvironmentOptions : public Options {
256256

257257
bool report_exclude_env = false;
258258
bool report_exclude_network = false;
259+
std::string experimental_config_file;
259260

260261
inline DebugOptions* get_debug_options() { return &debug_options_; }
261262
inline const DebugOptions& debug_options() const { return debug_options_; }

0 commit comments

Comments
 (0)