Skip to content

Commit 689209b

Browse files
committed
src: add support to override local env variables option
1 parent f8e325e commit 689209b

File tree

10 files changed

+101
-15
lines changed

10 files changed

+101
-15
lines changed

doc/api/cli.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,17 @@ Export keyword before a key is ignored:
726726
export USERNAME="nodejs" # will result in `nodejs` as the value.
727727
```
728728

729+
### `--override-env-var`
730+
731+
> Stability: 1.1 - Active development
732+
733+
<!-- YAML
734+
added: REPLACEME
735+
-->
736+
737+
Override existing environment variables on your machine with values specified in
738+
your environment files.
739+
729740
### `-e`, `--eval "script"`
730741

731742
<!-- YAML

doc/api/process.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2280,17 +2280,25 @@ process.kill(process.pid, 'SIGHUP');
22802280
When `SIGUSR1` is received by a Node.js process, Node.js will start the
22812281
debugger. See [Signal Events][].
22822282
2283-
## `process.loadEnvFile(path)`
2283+
## `process.loadEnvFile(path, options)`
22842284
22852285
<!-- YAML
22862286
added:
22872287
- v21.7.0
22882288
- v20.12.0
2289+
changes:
2290+
- version: REPLACEME
2291+
pr-url: https://github.com/nodejs/node/pull/54543
2292+
description: Add support to override local environment variables option.
22892293
-->
22902294
22912295
> Stability: 1.1 - Active development
22922296
22932297
* `path` {string | URL | Buffer | undefined}. **Default:** `'./.env'`
2298+
* `options` {Object} Used to provide arguments for parsing environment variables
2299+
files. `options` supports the following properties:
2300+
* `override` {boolean} to override local environment variables of your
2301+
machine. **Default:** `false`.
22942302
22952303
Loads the `.env` file into `process.env`. Usage of `NODE_OPTIONS`
22962304
in the `.env` file will not have any effect on Node.js.
@@ -2305,6 +2313,16 @@ import { loadEnvFile } from 'node:process';
23052313
loadEnvFile();
23062314
```
23072315
2316+
```cjs
2317+
const { loadEnvFile } = require('node:process');
2318+
loadEnvFile('.env', { override: true });
2319+
```
2320+
2321+
```mjs
2322+
import { loadEnvFile } from 'node:process';
2323+
loadEnvFile('.env', { override: true });
2324+
```
2325+
23082326
## `process.mainModule`
23092327
23102328
<!-- YAML

lib/internal/process/per_thread.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,23 @@ function wrapProcessMethods(binding) {
251251
/**
252252
* Loads the `.env` file to process.env.
253253
* @param {string | URL | Buffer | undefined} path
254+
* @param {{
255+
* override?: boolean;
256+
* }} [options]
254257
*/
255-
function loadEnvFile(path = undefined) { // Provide optional value so that `loadEnvFile.length` returns 0
258+
function loadEnvFile(path = undefined, options = { override: false }) {
259+
// Provide optional value so that `loadEnvFile.length` returns 0
260+
if (arguments.length === 1 && typeof path === 'object') {
261+
options = path;
262+
path = undefined;
263+
}
264+
validateObject(options, 'options');
256265
if (path != null) {
257266
path = getValidatedPath(path);
258-
_loadEnvFile(toNamespacedPath(path));
259267
} else {
260-
_loadEnvFile();
268+
path = getValidatedPath('.env');
261269
}
270+
_loadEnvFile(toNamespacedPath(path), options.override);
262271
}
263272

264273

src/node.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,8 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
360360
#endif
361361

362362
if (env->options()->has_env_file_string) {
363-
per_process::dotenv_file.SetEnvironment(env);
363+
per_process::dotenv_file.SetEnvironment(env,
364+
env->options()->override_env_var);
364365
}
365366

366367
// TODO(joyeecheung): move these conditions into JS land and let the

src/node_dotenv.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ std::vector<std::string> Dotenv::GetPathFromArgs(
5151
return paths;
5252
}
5353

54-
void Dotenv::SetEnvironment(node::Environment* env) {
54+
void Dotenv::SetEnvironment(node::Environment* env, bool override_env_var) {
5555
if (store_.empty()) {
5656
return;
5757
}
@@ -62,9 +62,10 @@ void Dotenv::SetEnvironment(node::Environment* env) {
6262
auto key = entry.first;
6363
auto value = entry.second;
6464

65-
auto existing = env->env_vars()->Get(key.data());
65+
bool can_set_env_var =
66+
!env->env_vars()->Get(key.data()).IsJust() || override_env_var;
6667

67-
if (existing.IsNothing()) {
68+
if (can_set_env_var) {
6869
env->env_vars()->Set(
6970
isolate,
7071
v8::String::NewFromUtf8(

src/node_dotenv.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class Dotenv {
2424
void ParseContent(const std::string_view content);
2525
ParseResult ParsePath(const std::string_view path);
2626
void AssignNodeOptionsIfAvailable(std::string* node_options);
27-
void SetEnvironment(Environment* env);
27+
void SetEnvironment(Environment* env, bool override_env_var);
2828
v8::Local<v8::Object> ToObject(Environment* env);
2929

3030
static std::vector<std::string> GetPathFromArgs(

src/node_options.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
627627
"set environment variables from supplied file",
628628
&EnvironmentOptions::env_file);
629629
Implies("--env-file", "[has_env_file_string]");
630+
AddOption("--override-env-var",
631+
"override environment variables set on machine with variables from "
632+
"supplied file",
633+
&EnvironmentOptions::override_env_var);
634+
Implies("--override-env-var", "[has_env_file_string]");
630635
AddOption("--test",
631636
"launch test runner on startup",
632637
&EnvironmentOptions::test_runner);

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ class EnvironmentOptions : public Options {
165165
std::string diagnostic_dir;
166166
std::string env_file;
167167
bool has_env_file_string = false;
168+
bool override_env_var = false;
168169
bool test_runner = false;
169170
uint64_t test_runner_concurrency = 0;
170171
uint64_t test_runner_timeout = 0;

src/node_process_methods.cc

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,15 @@ static void ReallyExit(const FunctionCallbackInfo<Value>& args) {
470470

471471
static void LoadEnvFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
472472
Environment* env = Environment::GetCurrent(args);
473-
std::string path = ".env";
474-
if (args.Length() == 1) {
475-
Utf8Value path_value(args.GetIsolate(), args[0]);
476-
path = path_value.ToString();
477-
}
473+
474+
CHECK_EQ(args.Length(), 2);
475+
CHECK(args[0]->IsString());
476+
CHECK(args[1]->IsBoolean());
477+
478+
Utf8Value path_value(args.GetIsolate(), args[0]);
479+
std::string path = path_value.ToString();
480+
481+
bool override_env_var = args[1]->IsTrue();
478482

479483
THROW_IF_INSUFFICIENT_PERMISSIONS(
480484
env, permission::PermissionScope::kFileSystemRead, path);
@@ -483,7 +487,7 @@ static void LoadEnvFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
483487

484488
switch (dotenv.ParsePath(path)) {
485489
case dotenv.ParseResult::Valid: {
486-
dotenv.SetEnvironment(env);
490+
dotenv.SetEnvironment(env, override_env_var);
487491
break;
488492
}
489493
case dotenv.ParseResult::InvalidContent: {

test/parallel/test-process-load-env-file.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,39 @@ describe('process.loadEnvFile()', () => {
8787
assert.strictEqual(child.code, 0);
8888
});
8989
});
90+
91+
describe('process.loadEnvFile(path, { override: true })', () => {
92+
93+
it('should not override the original value', async () => {
94+
process.env.BASIC = 'Original value';
95+
const code = `
96+
process.loadEnvFile(${JSON.stringify(validEnvFilePath)});
97+
const assert = require('assert');
98+
assert.strictEqual(process.env.BASIC, 'Original value');
99+
`.trim();
100+
const child = await common.spawnPromisified(
101+
process.execPath,
102+
[ '--eval', code ],
103+
{ cwd: __dirname },
104+
);
105+
assert.strictEqual(child.stderr, '');
106+
assert.strictEqual(child.code, 0);
107+
});
108+
109+
it('should override the original value', async () => {
110+
process.env.BASIC = 'Original value';
111+
const code = `
112+
process.loadEnvFile(${JSON.stringify(validEnvFilePath)}, {override: true});
113+
const assert = require('assert');
114+
assert.strictEqual(process.env.BASIC, 'basic');
115+
`.trim();
116+
const child = await common.spawnPromisified(
117+
process.execPath,
118+
[ '--eval', code ],
119+
{ cwd: __dirname },
120+
);
121+
assert.strictEqual(child.stderr, '');
122+
assert.strictEqual(child.code, 0);
123+
});
124+
125+
});

0 commit comments

Comments
 (0)