Skip to content

Commit ce30b4e

Browse files
authored
src: support multiple --env-file declarations
PR-URL: #49542 Refs: #49148 Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
1 parent c159b90 commit ce30b4e

File tree

6 files changed

+50
-36
lines changed

6 files changed

+50
-36
lines changed

doc/api/cli.md

+7
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,13 @@ variables which configure Node.js][environment_variables], such as `NODE_OPTIONS
10161016
are parsed and applied. If the same variable is defined in the environment and
10171017
in the file, the value from the environment takes precedence.
10181018

1019+
You can pass multiple `--env-file` arguments. Subsequent files override
1020+
pre-existing variables defined in previous files.
1021+
1022+
```bash
1023+
node --env-file=.env --env-file=.development.env index.js
1024+
```
1025+
10191026
The format of the file should be one line per key-value pair of environment
10201027
variable name and value separated by `=`:
10211028

src/node.cc

+9-5
Original file line numberDiff line numberDiff line change
@@ -841,13 +841,17 @@ static ExitCode InitializeNodeWithArgsInternal(
841841
HandleEnvOptions(per_process::cli_options->per_isolate->per_env);
842842

843843
std::string node_options;
844-
auto file_path = node::Dotenv::GetPathFromArgs(*argv);
844+
auto file_paths = node::Dotenv::GetPathFromArgs(*argv);
845845

846-
if (file_path.has_value()) {
847-
auto cwd = Environment::GetCwd(Environment::GetExecPath(*argv));
848-
std::string path = cwd + kPathSeparator + file_path.value();
846+
if (!file_paths.empty()) {
849847
CHECK(!per_process::v8_initialized);
850-
per_process::dotenv_file.ParsePath(path);
848+
auto cwd = Environment::GetCwd(Environment::GetExecPath(*argv));
849+
850+
for (const auto& file_path : file_paths) {
851+
std::string path = cwd + kPathSeparator + file_path;
852+
per_process::dotenv_file.ParsePath(path);
853+
}
854+
851855
per_process::dotenv_file.AssignNodeOptionsIfAvailable(&node_options);
852856
}
853857

src/node_dotenv.cc

+25-25
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,34 @@ namespace node {
88
using v8::NewStringType;
99
using v8::String;
1010

11-
std::optional<std::string> Dotenv::GetPathFromArgs(
11+
std::vector<std::string> Dotenv::GetPathFromArgs(
1212
const std::vector<std::string>& args) {
13-
std::string_view flag = "--env-file";
14-
// Match the last `--env-file`
15-
// This is required to imitate the default behavior of Node.js CLI argument
16-
// matching.
17-
auto path =
18-
std::find_if(args.rbegin(), args.rend(), [&flag](const std::string& arg) {
19-
return strncmp(arg.c_str(), flag.data(), flag.size()) == 0;
20-
});
21-
22-
if (path == args.rend()) {
23-
return std::nullopt;
24-
}
25-
26-
auto equal_char = path->find('=');
27-
28-
if (equal_char != std::string::npos) {
29-
return path->substr(equal_char + 1);
30-
}
31-
32-
auto next_arg = std::prev(path);
13+
const auto find_match = [](const std::string& arg) {
14+
const std::string_view flag = "--env-file";
15+
return strncmp(arg.c_str(), flag.data(), flag.size()) == 0;
16+
};
17+
std::vector<std::string> paths;
18+
auto path = std::find_if(args.begin(), args.end(), find_match);
19+
20+
while (path != args.end()) {
21+
auto equal_char = path->find('=');
22+
23+
if (equal_char != std::string::npos) {
24+
paths.push_back(path->substr(equal_char + 1));
25+
} else {
26+
auto next_path = std::next(path);
27+
28+
if (next_path == args.end()) {
29+
return paths;
30+
}
31+
32+
paths.push_back(*next_path);
33+
}
3334

34-
if (next_arg == args.rend()) {
35-
return std::nullopt;
35+
path = std::find_if(++path, args.end(), find_match);
3636
}
3737

38-
return *next_arg;
38+
return paths;
3939
}
4040

4141
void Dotenv::SetEnvironment(node::Environment* env) {
@@ -163,7 +163,7 @@ void Dotenv::ParseLine(const std::string_view line) {
163163
value.erase(value.size() - 1);
164164
}
165165

166-
store_.emplace(key, value);
166+
store_.insert_or_assign(std::string(key), value);
167167
}
168168

169169
} // namespace node

src/node_dotenv.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
#include "util-inl.h"
77

88
#include <map>
9-
#include <optional>
109

1110
namespace node {
1211

@@ -23,7 +22,7 @@ class Dotenv {
2322
void AssignNodeOptionsIfAvailable(std::string* node_options);
2423
void SetEnvironment(Environment* env);
2524

26-
static std::optional<std::string> GetPathFromArgs(
25+
static std::vector<std::string> GetPathFromArgs(
2726
const std::vector<std::string>& args);
2827

2928
private:

test/fixtures/dotenv/node-options.env

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ NODE_NO_WARNINGS=1
33
NODE_OPTIONS="--experimental-permission --allow-fs-read=*"
44
TZ=Pacific/Honolulu
55
UV_THREADPOOL_SIZE=5
6+
BASIC=overridden

test/parallel/test-dotenv-edge-cases.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,20 @@ const assert = require('node:assert');
55
const { describe, it } = require('node:test');
66

77
const validEnvFilePath = '../fixtures/dotenv/valid.env';
8-
const relativePath = '../fixtures/dotenv/node-options.env';
8+
const nodeOptionsEnvFilePath = '../fixtures/dotenv/node-options.env';
99

1010
describe('.env supports edge cases', () => {
1111

12-
it('should use the last --env-file declaration', async () => {
12+
it('supports multiple declarations', async () => {
13+
// process.env.BASIC is equal to `basic` because the second .env file overrides it.
1314
const code = `
14-
require('assert').strictEqual(process.env.BASIC, 'basic');
15+
const assert = require('assert');
16+
assert.strictEqual(process.env.BASIC, 'basic');
17+
assert.strictEqual(process.env.NODE_NO_WARNINGS, '1');
1518
`.trim();
1619
const child = await common.spawnPromisified(
1720
process.execPath,
18-
[ `--env-file=${relativePath}`, `--env-file=${validEnvFilePath}`, '--eval', code ],
21+
[ `--env-file=${nodeOptionsEnvFilePath}`, `--env-file=${validEnvFilePath}`, '--eval', code ],
1922
{ cwd: __dirname },
2023
);
2124
assert.strictEqual(child.stderr, '');

0 commit comments

Comments
 (0)