diff --git a/Makefile b/Makefile
index 39f5ee5e12a9b7..69c026755bf230 100644
--- a/Makefile
+++ b/Makefile
@@ -809,6 +809,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.
diff --git a/doc/api/cli.md b/doc/api/cli.md
index 855d83a2d2ba37..37ce213570b25b 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -911,6 +911,69 @@ added: v23.6.0
Enable experimental import support for `.node` addons.
+### `--experimental-config-file`
+
+
+
+> 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
+{
+ "$schema": "https://nodejs.org/dist/REPLACEME/docs/node_config_json_schema.json",
+ "experimental-transform-types": true,
+ "import": [
+ "amaro/transform"
+ ],
+ "disable-warning": "ExperimentalWarning",
+ "watch-path": "src",
+ "watch-preserve-output": true
+}
+```
+
+Only flags that are allowed in [`NODE_OPTIONS`][] are supported.
+No-op flags are not supported.
+Not all V8 flags are currently supported.
+
+It is possible to use the [official JSON schema](../node_config_json_schema.json)
+to validate the configuration file, which may vary depending on the Node.js version.
+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 --disable-warning=ExperimentalWarning --watch-path=src --watch-preserve-output
+```
+
+The priority in configuration is as follows:
+
+1. NODE\_OPTIONS and command-line options
+2. Configuration file
+3. Dotenv NODE\_OPTIONS
+
+Values in the configuration file will not override the values in the environment
+variables and command-line options, but will override the values in the `NODE_OPTIONS`
+env file parsed by the `--env-file` flag.
+
+If duplicate keys are present in the configuration file, only
+the first key will be used.
+
+The configuration parser will throw an error if the configuration file contains
+unknown keys or keys that cannot used in `NODE_OPTIONS`.
+
+Node.js will not sanitize or perform validation on the user-provided configuration,
+so **NEVER** use untrusted configuration files.
+
### `--experimental-eventsource`
+
+An attempt was made to get options before the bootstrapping was completed.
+
### `ERR_OUT_OF_RANGE`
diff --git a/doc/node.1 b/doc/node.1
index d33bb82b7670e7..ad8873b423105c 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -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().
.
diff --git a/doc/node_config_json_schema.json b/doc/node_config_json_schema.json
new file mode 100644
index 00000000000000..f76fbef3c7c3fd
--- /dev/null
+++ b/doc/node_config_json_schema.json
@@ -0,0 +1,578 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "additionalProperties": false,
+ "properties": {
+ "addons": {
+ "type": "boolean"
+ },
+ "allow-addons": {
+ "type": "boolean"
+ },
+ "allow-child-process": {
+ "type": "boolean"
+ },
+ "allow-fs-read": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "allow-fs-write": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "allow-wasi": {
+ "type": "boolean"
+ },
+ "allow-worker": {
+ "type": "boolean"
+ },
+ "async-context-frame": {
+ "type": "boolean"
+ },
+ "conditions": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "cpu-prof": {
+ "type": "boolean"
+ },
+ "cpu-prof-dir": {
+ "type": "string"
+ },
+ "cpu-prof-interval": {
+ "type": "number"
+ },
+ "cpu-prof-name": {
+ "type": "string"
+ },
+ "debug-arraybuffer-allocations": {
+ "type": "boolean"
+ },
+ "deprecation": {
+ "type": "boolean"
+ },
+ "diagnostic-dir": {
+ "type": "string"
+ },
+ "disable-proto": {
+ "type": "string"
+ },
+ "disable-sigusr1": {
+ "type": "boolean"
+ },
+ "disable-warning": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "disable-wasm-trap-handler": {
+ "type": "boolean"
+ },
+ "dns-result-order": {
+ "type": "string"
+ },
+ "enable-fips": {
+ "type": "boolean"
+ },
+ "enable-source-maps": {
+ "type": "boolean"
+ },
+ "entry-url": {
+ "type": "boolean"
+ },
+ "experimental-addon-modules": {
+ "type": "boolean"
+ },
+ "experimental-detect-module": {
+ "type": "boolean"
+ },
+ "experimental-eventsource": {
+ "type": "boolean"
+ },
+ "experimental-global-navigator": {
+ "type": "boolean"
+ },
+ "experimental-import-meta-resolve": {
+ "type": "boolean"
+ },
+ "experimental-loader": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "experimental-print-required-tla": {
+ "type": "boolean"
+ },
+ "experimental-repl-await": {
+ "type": "boolean"
+ },
+ "experimental-require-module": {
+ "type": "boolean"
+ },
+ "experimental-shadow-realm": {
+ "type": "boolean"
+ },
+ "experimental-sqlite": {
+ "type": "boolean"
+ },
+ "experimental-strip-types": {
+ "type": "boolean"
+ },
+ "experimental-transform-types": {
+ "type": "boolean"
+ },
+ "experimental-vm-modules": {
+ "type": "boolean"
+ },
+ "experimental-wasm-modules": {
+ "type": "boolean"
+ },
+ "experimental-websocket": {
+ "type": "boolean"
+ },
+ "experimental-webstorage": {
+ "type": "boolean"
+ },
+ "extra-info-on-fatal-exception": {
+ "type": "boolean"
+ },
+ "force-async-hooks-checks": {
+ "type": "boolean"
+ },
+ "force-context-aware": {
+ "type": "boolean"
+ },
+ "force-fips": {
+ "type": "boolean"
+ },
+ "force-node-api-uncaught-exceptions-policy": {
+ "type": "boolean"
+ },
+ "frozen-intrinsics": {
+ "type": "boolean"
+ },
+ "global-search-paths": {
+ "type": "boolean"
+ },
+ "heap-prof": {
+ "type": "boolean"
+ },
+ "heap-prof-dir": {
+ "type": "string"
+ },
+ "heap-prof-interval": {
+ "type": "number"
+ },
+ "heap-prof-name": {
+ "type": "string"
+ },
+ "heapsnapshot-near-heap-limit": {
+ "type": "number"
+ },
+ "heapsnapshot-signal": {
+ "type": "string"
+ },
+ "icu-data-dir": {
+ "type": "string"
+ },
+ "import": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "input-type": {
+ "type": "string"
+ },
+ "insecure-http-parser": {
+ "type": "boolean"
+ },
+ "inspect": {
+ "type": "boolean"
+ },
+ "inspect-brk": {
+ "type": "boolean"
+ },
+ "inspect-port": {
+ "type": "number"
+ },
+ "inspect-publish-uid": {
+ "type": "string"
+ },
+ "inspect-wait": {
+ "type": "boolean"
+ },
+ "localstorage-file": {
+ "type": "string"
+ },
+ "max-http-header-size": {
+ "type": "number"
+ },
+ "network-family-autoselection": {
+ "type": "boolean"
+ },
+ "network-family-autoselection-attempt-timeout": {
+ "type": "number"
+ },
+ "node-snapshot": {
+ "type": "boolean"
+ },
+ "openssl-config": {
+ "type": "string"
+ },
+ "openssl-legacy-provider": {
+ "type": "boolean"
+ },
+ "openssl-shared-config": {
+ "type": "boolean"
+ },
+ "pending-deprecation": {
+ "type": "boolean"
+ },
+ "permission": {
+ "type": "boolean"
+ },
+ "preserve-symlinks": {
+ "type": "boolean"
+ },
+ "preserve-symlinks-main": {
+ "type": "boolean"
+ },
+ "redirect-warnings": {
+ "type": "string"
+ },
+ "report-compact": {
+ "type": "boolean"
+ },
+ "report-dir": {
+ "type": "string"
+ },
+ "report-exclude-env": {
+ "type": "boolean"
+ },
+ "report-exclude-network": {
+ "type": "boolean"
+ },
+ "report-filename": {
+ "type": "string"
+ },
+ "report-on-fatalerror": {
+ "type": "boolean"
+ },
+ "report-on-signal": {
+ "type": "boolean"
+ },
+ "report-signal": {
+ "type": "string"
+ },
+ "report-uncaught-exception": {
+ "type": "boolean"
+ },
+ "require": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "secure-heap": {
+ "type": "number"
+ },
+ "secure-heap-min": {
+ "type": "number"
+ },
+ "snapshot-blob": {
+ "type": "string"
+ },
+ "stack-trace-limit": {
+ "type": "number"
+ },
+ "test-coverage-branches": {
+ "type": "number"
+ },
+ "test-coverage-exclude": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "test-coverage-functions": {
+ "type": "number"
+ },
+ "test-coverage-include": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "test-coverage-lines": {
+ "type": "number"
+ },
+ "test-isolation": {
+ "type": "string"
+ },
+ "test-name-pattern": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "test-only": {
+ "type": "boolean"
+ },
+ "test-reporter": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "test-reporter-destination": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "test-shard": {
+ "type": "string"
+ },
+ "test-skip-pattern": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "throw-deprecation": {
+ "type": "boolean"
+ },
+ "title": {
+ "type": "string"
+ },
+ "tls-cipher-list": {
+ "type": "string"
+ },
+ "tls-keylog": {
+ "type": "string"
+ },
+ "tls-max-v1.2": {
+ "type": "boolean"
+ },
+ "tls-max-v1.3": {
+ "type": "boolean"
+ },
+ "tls-min-v1.0": {
+ "type": "boolean"
+ },
+ "tls-min-v1.1": {
+ "type": "boolean"
+ },
+ "tls-min-v1.2": {
+ "type": "boolean"
+ },
+ "tls-min-v1.3": {
+ "type": "boolean"
+ },
+ "trace-deprecation": {
+ "type": "boolean"
+ },
+ "trace-env": {
+ "type": "boolean"
+ },
+ "trace-env-js-stack": {
+ "type": "boolean"
+ },
+ "trace-env-native-stack": {
+ "type": "boolean"
+ },
+ "trace-event-categories": {
+ "type": "string"
+ },
+ "trace-event-file-pattern": {
+ "type": "string"
+ },
+ "trace-exit": {
+ "type": "boolean"
+ },
+ "trace-promises": {
+ "type": "boolean"
+ },
+ "trace-require-module": {
+ "type": "string"
+ },
+ "trace-sigint": {
+ "type": "boolean"
+ },
+ "trace-sync-io": {
+ "type": "boolean"
+ },
+ "trace-tls": {
+ "type": "boolean"
+ },
+ "trace-uncaught": {
+ "type": "boolean"
+ },
+ "trace-warnings": {
+ "type": "boolean"
+ },
+ "track-heap-objects": {
+ "type": "boolean"
+ },
+ "unhandled-rejections": {
+ "type": "string"
+ },
+ "use-bundled-ca": {
+ "type": "boolean"
+ },
+ "use-largepages": {
+ "type": "string"
+ },
+ "use-openssl-ca": {
+ "type": "boolean"
+ },
+ "use-system-ca": {
+ "type": "boolean"
+ },
+ "v8-pool-size": {
+ "type": "number"
+ },
+ "verify-base-objects": {
+ "type": "boolean"
+ },
+ "warnings": {
+ "type": "boolean"
+ },
+ "watch": {
+ "type": "boolean"
+ },
+ "watch-path": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minItems": 1
+ }
+ }
+ ]
+ },
+ "watch-preserve-output": {
+ "type": "boolean"
+ },
+ "zero-fill-buffers": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+}
diff --git a/lib/internal/options.js b/lib/internal/options.js
index 1192b46c9ede82..f23b75c0a8f3a2 100644
--- a/lib/internal/options.js
+++ b/lib/internal/options.js
@@ -1,9 +1,18 @@
'use strict';
+const {
+ ArrayPrototypeMap,
+ ArrayPrototypeSort,
+ ObjectFromEntries,
+ ObjectKeys,
+ StringPrototypeReplace,
+} = primordials;
+
const {
getCLIOptionsValues,
getCLIOptionsInfo,
getEmbedderOptions: getEmbedderOptionsFromBinding,
+ getEnvOptionsInputType,
} = internalBinding('options');
let warnOnAllowUnauthorized = true;
@@ -28,6 +37,45 @@ function getEmbedderOptions() {
return embedderOptions ??= getEmbedderOptionsFromBinding();
}
+function generateConfigJsonSchema() {
+ const map = getEnvOptionsInputType();
+
+ const schema = {
+ __proto__: null,
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
+ additionalProperties: false,
+ properties: {
+ __proto__: null,
+ },
+ type: 'object',
+ };
+
+ for (const { 0: key, 1: type } of map) {
+ const keyWithoutPrefix = StringPrototypeReplace(key, '--', '');
+ if (type === 'array') {
+ schema.properties[keyWithoutPrefix] = {
+ __proto__: null,
+ oneOf: [
+ { __proto__: null, type: 'string' },
+ { __proto__: null, type: 'array', items: { __proto__: null, type: 'string', minItems: 1 } },
+ ],
+ };
+ } else {
+ schema.properties[keyWithoutPrefix] = { __proto__: null, type };
+ }
+ }
+
+ // Sort the proerties by key alphabetically.
+ const sortedKeys = ArrayPrototypeSort(ObjectKeys(schema.properties));
+ const sortedProperties = ObjectFromEntries(
+ ArrayPrototypeMap(sortedKeys, (key) => [key, schema.properties[key]]),
+ );
+
+ schema.properties = sortedProperties;
+
+ return schema;
+}
+
function refreshOptions() {
optionsDict = undefined;
}
@@ -55,5 +103,6 @@ module.exports = {
getOptionValue,
getAllowUnauthorized,
getEmbedderOptions,
+ generateConfigJsonSchema,
refreshOptions,
};
diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js
index 109890e5986ee4..c705fce930da24 100644
--- a/lib/internal/process/pre_execution.js
+++ b/lib/internal/process/pre_execution.js
@@ -116,6 +116,8 @@ function prepareExecution(options) {
initializeSourceMapsHandlers();
initializeDeprecations();
+ initializeConfigFileSupport();
+
require('internal/dns/utils').initializeDns();
if (isMainThread) {
@@ -312,6 +314,12 @@ function setupSQLite() {
BuiltinModule.allowRequireByUsers('sqlite');
}
+function initializeConfigFileSupport() {
+ if (getOptionValue('--experimental-config-file')) {
+ emitExperimentalWarning('--experimental-config-file');
+ }
+}
+
function setupQuic() {
if (!getOptionValue('--experimental-quic')) {
return;
diff --git a/node.gyp b/node.gyp
index 42a1160ad4af7b..726403ae0bb9f4 100644
--- a/node.gyp
+++ b/node.gyp
@@ -105,6 +105,7 @@
'src/node_buffer.cc',
'src/node_builtins.cc',
'src/node_config.cc',
+ 'src/node_config_file.cc',
'src/node_constants.cc',
'src/node_contextify.cc',
'src/node_credentials.cc',
@@ -230,6 +231,7 @@
'src/node_blob.h',
'src/node_buffer.h',
'src/node_builtins.h',
+ 'src/node_config_file.h',
'src/node_constants.h',
'src/node_context_data.h',
'src/node_contextify.h',
diff --git a/src/node.cc b/src/node.cc
index 61e7287a4f73a3..4cdbcda7343293 100644
--- a/src/node.cc
+++ b/src/node.cc
@@ -20,6 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "node.h"
+#include "node_config_file.h"
#include "node_dotenv.h"
#include "node_task_runner.h"
@@ -150,6 +151,9 @@ namespace per_process {
// Instance is used to store environment variables including NODE_OPTIONS.
node::Dotenv dotenv_file = Dotenv();
+// node_config_file.h
+node::ConfigReader config_reader = ConfigReader();
+
// node_revert.h
// Bit flag used to track security reverts.
unsigned int reverted_cve = 0;
@@ -884,6 +888,36 @@ static ExitCode InitializeNodeWithArgsInternal(
per_process::dotenv_file.AssignNodeOptionsIfAvailable(&node_options);
}
+ std::string node_options_from_config;
+ if (auto path = per_process::config_reader.GetDataFromArgs(*argv)) {
+ switch (per_process::config_reader.ParseConfig(*path)) {
+ case ParseResult::Valid:
+ break;
+ case ParseResult::InvalidContent:
+ errors->push_back(std::string(*path) + ": invalid content");
+ break;
+ case ParseResult::FileError:
+ errors->push_back(std::string(*path) + ": not found");
+ break;
+ default:
+ UNREACHABLE();
+ }
+ node_options_from_config = per_process::config_reader.AssignNodeOptions();
+ // (@marco-ippolito) Avoid reparsing the env options again
+ std::vector env_argv_from_config =
+ ParseNodeOptionsEnvVar(node_options_from_config, errors);
+
+ // Check the number of flags in NODE_OPTIONS from the config file
+ // matches the parsed ones. This avoid users from sneaking in
+ // additional flags.
+ if (env_argv_from_config.size() !=
+ per_process::config_reader.GetFlagsSize()) {
+ errors->emplace_back("The number of NODE_OPTIONS doesn't match "
+ "the number of flags in the config file");
+ }
+ node_options += node_options_from_config;
+ }
+
#if !defined(NODE_WITHOUT_NODE_OPTIONS)
if (!(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv)) {
// NODE_OPTIONS environment variable is preferred over the file one.
diff --git a/src/node_config_file.cc b/src/node_config_file.cc
new file mode 100644
index 00000000000000..700c3e458dad23
--- /dev/null
+++ b/src/node_config_file.cc
@@ -0,0 +1,195 @@
+#include "node_config_file.h"
+#include "debug_utils-inl.h"
+#include "simdjson.h"
+
+#include
+
+namespace node {
+
+std::optional ConfigReader::GetDataFromArgs(
+ const std::vector& 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;
+}
+
+ParseResult ConfigReader::ParseConfig(const std::string_view& config_path) {
+ std::string file_content;
+ // Read the configuration file
+ int r = ReadFileSync(&file_content, config_path.data());
+ 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.data());
+ 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.data());
+ } else {
+ FPrintF(stderr, "Can't parse %s\n", config_path.data());
+ }
+ return ParseResult::InvalidContent;
+ }
+
+ auto env_options_map = options_parser::MapEnvOptionsFlagInputType();
+ simdjson::ondemand::value ondemand_value;
+ std::string_view key;
+
+ for (auto field : main_object) {
+ if (field.unescaped_key().get(key) || field.value().get(ondemand_value)) {
+ return ParseResult::InvalidContent;
+ }
+
+ // The key needs to match the CLI option
+ std::string prefix = "--";
+ auto it = env_options_map.find(prefix.append(key));
+ if (it != env_options_map.end()) {
+ switch (it->second) {
+ case options_parser::OptionType::kBoolean: {
+ bool result;
+ if (ondemand_value.get_bool().get(result)) {
+ FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
+ return ParseResult::InvalidContent;
+ }
+ flags_.push_back(it->first + "=" + (result ? "true" : "false"));
+ break;
+ }
+ // String array can allow both string and array types
+ case options_parser::OptionType::kStringList: {
+ simdjson::ondemand::json_type field_type;
+ if (ondemand_value.type().get(field_type)) {
+ return ParseResult::InvalidContent;
+ }
+ switch (field_type) {
+ case simdjson::ondemand::json_type::array: {
+ std::vector result;
+ simdjson::ondemand::array raw_imports;
+ if (ondemand_value.get_array().get(raw_imports)) {
+ FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
+ return ParseResult::InvalidContent;
+ }
+ for (auto raw_import : raw_imports) {
+ std::string_view import;
+ if (raw_import.get_string(import)) {
+ FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
+ return ParseResult::InvalidContent;
+ }
+ flags_.push_back(it->first + "=" + std::string(import));
+ }
+ break;
+ }
+ case simdjson::ondemand::json_type::string: {
+ std::string result;
+ if (ondemand_value.get_string(result)) {
+ FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
+ return ParseResult::InvalidContent;
+ }
+ flags_.push_back(it->first + "=" + result);
+ break;
+ }
+ default:
+ FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
+ return ParseResult::InvalidContent;
+ }
+ break;
+ }
+ case options_parser::OptionType::kString: {
+ std::string result;
+ if (ondemand_value.get_string(result)) {
+ FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
+ return ParseResult::InvalidContent;
+ }
+ flags_.push_back(it->first + "=" + result);
+ break;
+ }
+ case options_parser::OptionType::kInteger: {
+ int64_t result;
+ if (ondemand_value.get_int64().get(result)) {
+ FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
+ return ParseResult::InvalidContent;
+ }
+ flags_.push_back(it->first + "=" + std::to_string(result));
+ break;
+ }
+ case options_parser::OptionType::kHostPort:
+ case options_parser::OptionType::kUInteger: {
+ uint64_t result;
+ if (ondemand_value.get_uint64().get(result)) {
+ FPrintF(stderr, "Invalid value for %s\n", it->first.c_str());
+ return ParseResult::InvalidContent;
+ }
+ flags_.push_back(it->first + "=" + std::to_string(result));
+ break;
+ }
+ case options_parser::OptionType::kNoOp: {
+ FPrintF(stderr,
+ "No-op flag %s is currently not supported\n",
+ it->first.c_str());
+ return ParseResult::InvalidContent;
+ break;
+ }
+ case options_parser::OptionType::kV8Option: {
+ FPrintF(stderr,
+ "V8 flag %s is currently not supported\n",
+ it->first.c_str());
+ return ParseResult::InvalidContent;
+ }
+ default:
+ UNREACHABLE();
+ }
+ } else {
+ FPrintF(stderr, "Unknown or not allowed option %s\n", key.data());
+ return ParseResult::InvalidContent;
+ }
+ }
+
+ return ParseResult::Valid;
+}
+
+std::string ConfigReader::AssignNodeOptions() {
+ if (flags_.empty()) {
+ return "";
+ } else {
+ DCHECK_GT(flags_.size(), 0);
+ std::string acc;
+ acc.reserve(flags_.size() * 2);
+ for (size_t i = 0; i < flags_.size(); ++i) {
+ // The space is necessary at the beginning of the string
+ acc += " " + flags_[i];
+ }
+ return acc;
+ }
+}
+
+size_t ConfigReader::GetFlagsSize() {
+ return flags_.size();
+}
+} // namespace node
diff --git a/src/node_config_file.h b/src/node_config_file.h
new file mode 100644
index 00000000000000..938f3647d8eb8e
--- /dev/null
+++ b/src/node_config_file.h
@@ -0,0 +1,43 @@
+#ifndef SRC_NODE_CONFIG_FILE_H_
+#define SRC_NODE_CONFIG_FILE_H_
+
+#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
+
+#include