From e7ed568b67b965a55fd6020d358844e8d90e347d Mon Sep 17 00:00:00 2001 From: Marco Ippolito Date: Wed, 29 Apr 2026 12:59:41 +0200 Subject: [PATCH] src: swap dotenv and config file parsing order Signed-off-by: Marco Ippolito --- doc/api/cli.md | 8 ++++---- src/node.cc | 7 +++++-- src/node_dotenv.cc | 2 +- test/parallel/test-config-file.js | 8 ++++---- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/doc/api/cli.md b/doc/api/cli.md index e979ec95c4259d..b5428453787e4a 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1143,12 +1143,12 @@ node --import amaro/strip --watch-path=src --watch-preserve-output --test-isolat The priority in configuration is as follows: 1. NODE\_OPTIONS and command-line options -2. Configuration file -3. Dotenv NODE\_OPTIONS +2. Dotenv NODE\_OPTIONS +3. Configuration file 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. +variables, command-line options, or the `NODE_OPTIONS` env file parsed by the +`--env-file` flag. Keys cannot be duplicated within the same or different namespaces. diff --git a/src/node.cc b/src/node.cc index 92d0402b7dfbaf..4ba019ddca05f4 100644 --- a/src/node.cc +++ b/src/node.cc @@ -870,6 +870,7 @@ static ExitCode InitializeNodeWithArgsInternal( HandleEnvOptions(per_process::cli_options->per_isolate->per_env); std::string node_options; + std::string node_options_from_dotenv; auto env_files = node::Dotenv::GetDataFromArgs(*argv); if (!env_files.empty()) { @@ -896,7 +897,8 @@ static ExitCode InitializeNodeWithArgsInternal( } } - per_process::dotenv_file.AssignNodeOptionsIfAvailable(&node_options); + per_process::dotenv_file.AssignNodeOptionsIfAvailable( + &node_options_from_dotenv); } std::string node_options_from_config; @@ -932,9 +934,10 @@ static ExitCode InitializeNodeWithArgsInternal( 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; } + node_options = node_options_from_config + node_options_from_dotenv; + #if !defined(NODE_WITHOUT_NODE_OPTIONS) bool should_parse_node_options = !(flags & ProcessInitializationFlags::kDisableNodeOptionsEnv); diff --git a/src/node_dotenv.cc b/src/node_dotenv.cc index e1940904d1c039..e42eb3c9deaf7b 100644 --- a/src/node_dotenv.cc +++ b/src/node_dotenv.cc @@ -356,7 +356,7 @@ void Dotenv::AssignNodeOptionsIfAvailable(std::string* node_options) const { auto match = store_.find("NODE_OPTIONS"); if (match != store_.end()) { - *node_options = match->second; + *node_options = " " + match->second; } } diff --git a/test/parallel/test-config-file.js b/test/parallel/test-config-file.js index 334884d9ed893a..c0307be1cb5b07 100644 --- a/test/parallel/test-config-file.js +++ b/test/parallel/test-config-file.js @@ -86,16 +86,16 @@ test('should throw an error when a flag is declared twice', async () => { assert.strictEqual(result.code, 9); }); -test('should override env-file', onlyWithAmaroAndNodeOptions, async () => { +test('should not override env-file', onlyWithAmaroAndNodeOptions, async () => { const result = await spawnPromisified(process.execPath, [ '--no-warnings', `--experimental-config-file=${fixtures.path('rc/strip-types.json')}`, '--env-file', fixtures.path('dotenv/node-options-no-tranform.env'), fixtures.path('typescript/ts/test-typescript.ts'), ]); - assert.strictEqual(result.stderr, ''); - assert.match(result.stdout, /Hello, TypeScript!/); - assert.strictEqual(result.code, 0); + assert.match(result.stderr, /SyntaxError/); + assert.strictEqual(result.stdout, ''); + assert.strictEqual(result.code, 1); }); test('should not override NODE_OPTIONS', onlyWithAmaro, async () => {