Skip to content

Commit

Permalink
src: add config file support
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-ippolito committed Feb 16, 2025
1 parent 9ce1fff commit fe60554
Show file tree
Hide file tree
Showing 23 changed files with 597 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,7 @@ doc: $(NODE_EXE) doc-only ## Build Node.js, and then build the documentation wit

out/doc:
mkdir -p $@
cp doc/node_config_json_schema.json $@

# If it's a source tarball, doc/api already contains the generated docs.
# Just copy everything under doc/api over.
Expand Down
71 changes: 71 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,77 @@ added: v23.6.0
Enable experimental import support for `.node` addons.

### `--experimental-config-file`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.0 - Early development
Use this flag to specify a configuration file that will be loaded and parsed
before the application starts.
Node.js will read the configuration file and apply the settings.
The configuration file should be a JSON file
with the following structure:

```json
{
"experimental": {
"transform_types": true
},
"import": [
"amaro/transform"
],
"test": {
"only": true
}
}
```

Each key in the configuration file corresponds to a flag that can be passed
as a command-line argument. The value of the key is the value that would be
passed to the flag.

For example, the configuration file above is equivalent to
the following command-line arguments:

```bash
node --experimental-transform-types --import amaro/transform
```

The priority in configuration is as follows:

* NODE\_OPTIONS and command-line options
* Config file
* Dotenv NODE\_OPTIONS

If multiple keys are present in the configuration file,
the last one will override the previous ones.
Unknown keys will be ignored.

It possible to use the official json schema to validate the configuration file,
which may vary depending on the Node.js version.

```json
{
"$schema": "https://nodejs.org/dist/REPLACEME/docs/node_config_json_schema.json",
}
```

Node.js will not sanitize or perform validation on the user-provided configuration,
so **NEVER** use untrusted configuration files.
In the example below `--some-other-flag`
will not be sanitized and will be passed to the Node.js process.

```json
{
"import": [
"amaro/transform --some-other-flag"
],
}
```

### `--experimental-eventsource`

<!-- YAML
Expand Down
3 changes: 3 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ Interpret the entry point as a URL.
.It Fl -experimental-addon-modules
Enable experimental addon module support.
.
.It Fl -experimental-config-file
Enable support for experimental config file
.
.It Fl -experimental-import-meta-resolve
Enable experimental ES modules support for import.meta.resolve().
.
Expand Down
33 changes: 33 additions & 0 deletions doc/node_config_json_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"experimental": {
"type": "object",
"properties": {
"transform_types": {
"type": "boolean"
}
},
"required": [],
"additionalProperties": false
},
"import": {
"type": "array",
"items": {
"type": "string"
}
},
"test": {
"type": "object",
"properties": {
"only": {
"type": "boolean"
}
},
"required": [],
"additionalProperties": false
}
},
"additionalProperties": false
}
8 changes: 8 additions & 0 deletions lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ function prepareExecution(options) {
initializeSourceMapsHandlers();
initializeDeprecations();

setupConfigFile();

require('internal/dns/utils').initializeDns();

if (isMainThread) {
Expand Down Expand Up @@ -312,6 +314,12 @@ function setupSQLite() {
BuiltinModule.allowRequireByUsers('sqlite');
}

function setupConfigFile() {
if (getOptionValue('--experimental-config-file')) {
emitExperimentalWarning('--experimental-config-file');
}
}

function setupQuic() {
if (!getOptionValue('--experimental-quic')) {
return;
Expand Down
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
'src/node_process_events.cc',
'src/node_process_methods.cc',
'src/node_process_object.cc',
'src/node_rc.cc',
'src/node_realm.cc',
'src/node_report.cc',
'src/node_report_module.cc',
Expand Down Expand Up @@ -262,6 +263,7 @@
'src/node_platform.h',
'src/node_process.h',
'src/node_process-inl.h',
'src/node_rc.h',
'src/node_realm.h',
'src/node_realm-inl.h',
'src/node_report.h',
Expand Down
21 changes: 21 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "node.h"
#include "node_dotenv.h"
#include "node_rc.h"
#include "node_task_runner.h"

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

// node_rc.h
node::ConfigReader config_reader = ConfigReader();

// node_revert.h
// Bit flag used to track security reverts.
unsigned int reverted_cve = 0;
Expand Down Expand Up @@ -884,6 +888,23 @@ static ExitCode InitializeNodeWithArgsInternal(
per_process::dotenv_file.AssignNodeOptionsIfAvailable(&node_options);
}

auto result = per_process::config_reader.GetDataFromArgs(*argv);
if (result.has_value()) {
switch (per_process::config_reader.ParseConfig(result.value())) {
case ConfigReader::ParseResult::Valid:
break;
case ConfigReader::ParseResult::InvalidContent:
errors->push_back(result.value() + ": invalid format");
break;
case ConfigReader::ParseResult::FileError:
errors->push_back(result.value() + ": not found");
break;
default:
UNREACHABLE();
}
per_process::config_reader.AssignNodeOptions(&node_options);
}

#if !defined(NODE_WITHOUT_NODE_OPTIONS)
if (!(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv)) {
// NODE_OPTIONS environment variable is preferred over the file one.
Expand Down
3 changes: 3 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"set environment variables from supplied file",
&EnvironmentOptions::optional_env_file);
Implies("--env-file-if-exists", "[has_env_file_string]");
AddOption("--experimental-config-file",
"set config file from supplied file",
&EnvironmentOptions::experimental_config_file);
AddOption("--test",
"launch test runner on startup",
&EnvironmentOptions::test_runner);
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ class EnvironmentOptions : public Options {

bool report_exclude_env = false;
bool report_exclude_network = false;
std::string experimental_config_file;

inline DebugOptions* get_debug_options() { return &debug_options_; }
inline const DebugOptions& debug_options() const { return debug_options_; }
Expand Down
Loading

0 comments on commit fe60554

Please sign in to comment.