Skip to content

src: add --config-file=path support to Node.js #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,18 @@ For example, to run a module with "development" resolutions:
node -C development app.js
```

### `--config-file=path`

> Stability: 1.1 - Active development

<!-- YAML
added: REPLACEME
-->

Write a documentation for here.
- Mention the order of preferrence for environment variables, config file, and command line arguments.
- Mention the JSON schema.

### `--cpu-prof`

<!-- YAML
Expand Down
14 changes: 14 additions & 0 deletions doc/node-config-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"options": {
"type": "object",
"properties": {
"inspect": {
"type": "string"
}
}
}
}
}
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,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',
Expand Down Expand Up @@ -213,6 +214,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',
Expand Down
19 changes: 15 additions & 4 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "node.h"
#include "node_config_file.h"
#include "node_dotenv.h"

// ========== local headers ==========
Expand All @@ -30,6 +31,7 @@
#include "memory_tracker-inl.h"
#include "node_binding.h"
#include "node_builtins.h"
#include "node_config_file.h"
#include "node_errors.h"
#include "node_internals.h"
#include "node_main_instance.h"
Expand Down Expand Up @@ -145,6 +147,10 @@ namespace per_process {
// Instance is used to store environment variables including NODE_OPTIONS.
node::Dotenv dotenv_file = Dotenv();

// node_config_file.h
// Instance is used to store configuration variables.
node::ConfigFile config_file = ConfigFile();

// node_revert.h
// Bit flag used to track security reverts.
unsigned int reverted_cve = 0;
Expand Down Expand Up @@ -858,13 +864,18 @@ static ExitCode InitializeNodeWithArgsInternal(
HandleEnvOptions(per_process::cli_options->per_isolate->per_env);

std::string node_options;
auto file_paths = node::Dotenv::GetPathFromArgs(*argv);

if (!file_paths.empty()) {
// TODO(@anonrig): Prefer what to choose.
// - Environment variable
// - Config file
// - Dotenv file
auto config_file_path = node::ConfigFile::GetPathFromArgs(*argv);
auto dotenv_file_paths = node::Dotenv::GetPathFromArgs(*argv);

if (!dotenv_file_paths.empty()) {
CHECK(!per_process::v8_initialized);
auto cwd = Environment::GetCwd(Environment::GetExecPath(*argv));

for (const auto& file_path : file_paths) {
for (const auto& file_path : dotenv_file_paths) {
std::string path = cwd + kPathSeparator + file_path;
auto path_exists = per_process::dotenv_file.ParsePath(path);

Expand Down
86 changes: 86 additions & 0 deletions src/node_config_file.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#include "node_config_file.h"
#include "env-inl.h"
#include "simdjson.h"
#include "util.h"
#include "uv.h"

namespace node {

using v8::NewStringType;
using v8::String;

std::optional<std::string> ConfigFile::GetPathFromArgs(
const std::vector<std::string>& args) {
std::string_view flag = "--config-file";
// Match the last `--config-file`
// This is required to imitate the default behavior of Node.js CLI argument
// matching.
auto path =
std::find_if(args.rbegin(), args.rend(), [&flag](const std::string& arg) {
return strncmp(arg.c_str(), flag.data(), flag.size()) == 0;
});

if (path == args.rend()) {
return std::nullopt;
}

auto equal_char = path->find('=');

if (equal_char != std::string::npos) {
return path->substr(equal_char + 1);
}

auto next_arg = std::prev(path);

if (next_arg == args.rend()) {
return std::nullopt;
}

return *next_arg;
}

bool ConfigFile::ParsePath(const std::string_view path) {
std::string raw_file;
if (ReadFileSync(&raw_file, path.data()) < 0) {
// Loading file failed.
// TODO(@anonrig): Throw appropiate error.
return false;
}

simdjson::ondemand::parser parser;
simdjson::ondemand::document document;
simdjson::ondemand::object main_object;
auto error = parser.iterate(raw_file).get(main_object);

if (error || document.get_object().get(main_object)) {
return false;
}

simdjson::ondemand::value ondemand_value;
simdjson::ondemand::raw_json_string raw_json_string;
std::string_view key;
std::string_view value;

// Config file schema is kept up to date on doc/node-config-schema.json
for (auto field : main_object) {
if (field.key().get(raw_json_string) || field.value().get(ondemand_value)) {
return false;
}

if (key == "inspect") {
// Implement the rest.
}
}

return true;
}

void ConfigFile::AssignNodeOptionsIfAvailable(std::string* node_options) {
// TODO(@anonrig): Implement this.
}

void ConfigFile::SetEnvironment(Environment* env) {
// TODO(@anonrig): Implement this.
}

} // namespace node
34 changes: 34 additions & 0 deletions src/node_config_file.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef SRC_NODE_CONFIG_FILE_H_
#define SRC_NODE_CONFIG_FILE_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "util-inl.h"

#include <optional>
#include <map>

namespace node {

class ConfigFile {
public:
ConfigFile() = default;
ConfigFile(const ConfigFile& d) = default;
ConfigFile(ConfigFile&& d) noexcept = default;
ConfigFile& operator=(ConfigFile&& d) noexcept = default;
ConfigFile& operator=(const ConfigFile& d) = default;
~ConfigFile() = default;

bool ParsePath(const std::string_view path);
void AssignNodeOptionsIfAvailable(std::string* node_options);
void SetEnvironment(Environment* env);

static std::optional<std::string> GetPathFromArgs(
const std::vector<std::string>& args);
};

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_NODE_CONFIG_FILE_H_
4 changes: 4 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ DebugOptionsParser::DebugOptionsParser() {
}

EnvironmentOptionsParser::EnvironmentOptionsParser() {
AddOption("--config-file",
"set node.js configuration from supplied file",
&EnvironmentOptions::env_file);
Implies("--config-file", "[has_config_file_string]");
AddOption("--conditions",
"additional user conditions for conditional exports and imports",
&EnvironmentOptions::conditions,
Expand Down
2 changes: 2 additions & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ class EnvironmentOptions : public Options {
std::vector<std::string> allow_fs_write;
bool allow_child_process = false;
bool allow_worker_threads = false;
std::string config_file;
bool has_config_file_string = false;
bool experimental_repl_await = true;
bool experimental_vm_modules = false;
bool expose_internals = false;
Expand Down