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 12, 2025
1 parent 9ce1fff commit 272828d
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 0 deletions.
40 changes: 40 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,46 @@ 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
}
```

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/node_config_json_schema.json",
"experimental_transform_types": true
}
```

### `--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
10 changes: 10 additions & 0 deletions doc/node_config_json_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"experimental_transform_types": {
"type": "boolean"
}
},
"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
97 changes: 97 additions & 0 deletions src/node_rc.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "node_rc.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "node_errors.h"
#include "node_file.h"
#include "node_internals.h"
#include "simdjson.h"

#include <functional>
#include <map>
#include <string>

namespace node {

std::optional<std::string> ConfigReader::GetDataFromArgs(
const std::vector<std::string>& args) {
constexpr std::string_view flag = "--experimental-config-file";

for (auto it = args.begin(); it != args.end(); ++it) {
if (*it == flag) {
// Case: "--experimental-config-file foo"
if (auto next = std::next(it); next != args.end()) {
return *next;
}
} else if (it->starts_with(flag)) {
// Case: "--experimental-config-file=foo"
if (it->size() > flag.size() && (*it)[flag.size()] == '=') {
return it->substr(flag.size() + 1);
}
}
}

return std::nullopt;
}

ConfigReader::ParseResult ConfigReader::ParseConfig(
const std::string& config_path) {
std::string file_content;
// Read the configuration file
int r = ReadFileSync(&file_content, config_path.c_str());
if (r != 0) {
const char* err = uv_strerror(r);
FPrintF(
stderr, "Cannot read configuration from %s: %s\n", config_path, err);
return ParseResult::FileError;
}

// Parse the configuration file
simdjson::ondemand::parser json_parser;
simdjson::ondemand::document document;
if (json_parser.iterate(file_content).get(document)) {
FPrintF(stderr, "Can't parse %s\n", config_path.c_str());
return ParseResult::InvalidContent;
}

simdjson::ondemand::object main_object;
// If document is not an object, throw an error.
if (auto root_error = document.get_object().get(main_object)) {
if (root_error == simdjson::error_code::INCORRECT_TYPE) {
FPrintF(stderr,
"Root value unexpected not an object for %s\n\n",
config_path.c_str());
} else {
FPrintF(stderr, "Can't parse %s\n", config_path.c_str());
}
return ParseResult::InvalidContent;
}

ConfigReader::Config config;
simdjson::ondemand::value ondemand_value;
simdjson::ondemand::raw_json_string key;

for (auto field : main_object) {
if (field.key().get(key) || field.value().get(ondemand_value)) {
return ParseResult::InvalidContent;
}
if (key == "experimental_transform_types") {
if (ondemand_value.get_bool().get(config.experimental_transform_types)) {
FPrintF(stderr, "Invalid value for experimental_transform_types\n");
return ParseResult::InvalidContent;
}
}
}

config_ = config;
return ParseResult::Valid;
}

void ConfigReader::AssignNodeOptions(std::string* node_options) {
std::string result = "";
if (config_.experimental_transform_types) {
result += "--experimental-transform-types";
}
*node_options = result;
return;
}
} // namespace node
35 changes: 35 additions & 0 deletions src/node_rc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef SRC_NODE_RC_H_
#define SRC_NODE_RC_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <map>
#include <string>
#include <variant>
#include "simdjson.h"
#include "util-inl.h"

namespace node {

class ConfigReader {
public:
enum ParseResult { Valid, FileError, InvalidContent };
struct Config {
bool experimental_transform_types;
};
ConfigReader::ParseResult ParseConfig(const std::string& config_path);

std::optional<std::string> GetDataFromArgs(
const std::vector<std::string>& args);

void AssignNodeOptions(std::string* node_options);

private:
ConfigReader::Config config_;
};

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_NODE_RC_H_
1 change: 1 addition & 0 deletions test/fixtures/dotenv/node-options-no-tranform.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NODE_OPTIONS="--no-experimental-strip-types"
4 changes: 4 additions & 0 deletions test/fixtures/rc/empty-object.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{

}

1 change: 1 addition & 0 deletions test/fixtures/rc/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

4 changes: 4 additions & 0 deletions test/fixtures/rc/override-property.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"experimental_transform_types": true,
"experimental_transform_types": false
}
3 changes: 3 additions & 0 deletions test/fixtures/rc/transform-types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"experimental_transform_types": true
}
Loading

0 comments on commit 272828d

Please sign in to comment.