Skip to content

Commit 3607629

Browse files
committed
src: add support to override local env variables option
1 parent f7a9cdc commit 3607629

File tree

10 files changed

+140
-16
lines changed

10 files changed

+140
-16
lines changed

doc/api/cli.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,23 @@ Export keyword before a key is ignored:
730730
export USERNAME="nodejs" # will result in `nodejs` as the value.
731731
```
732732

733+
### `--env-file-override-local`
734+
735+
> Stability: 1.1 - Active development
736+
737+
<!-- YAML
738+
added: REPLACEME
739+
-->
740+
741+
Override existing environment variables on your machine with values specified in
742+
your environment files.
743+
744+
```bash
745+
node --env-file=.env --env-file-override-local index.js
746+
```
747+
748+
To override variables during runtime, use [`process.loadEnvFile({ override: true })`][]
749+
733750
### `-e`, `--eval "script"`
734751

735752
<!-- YAML
@@ -3212,6 +3229,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
32123229
[`dnsPromises.lookup()`]: dns.md#dnspromiseslookuphostname-options
32133230
[`import` specifier]: esm.md#import-specifiers
32143231
[`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: net.md#netgetdefaultautoselectfamilyattempttimeout
3232+
[`process.loadEnvFile({ override: true })`]: process.md#processloadenvfilepath-options
32153233
[`process.setUncaughtExceptionCaptureCallback()`]: process.md#processsetuncaughtexceptioncapturecallbackfn
32163234
[`process.setuid()`]: process.md#processsetuidid
32173235
[`setuid(2)`]: https://man7.org/linux/man-pages/man2/setuid.2.html

doc/api/process.md

Lines changed: 36 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/52531
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,32 @@ import { loadEnvFile } from 'node:process';
23052313
loadEnvFile();
23062314
```
23072315
2316+
Override local environment variables using override option
2317+
2318+
```cjs
2319+
const { loadEnvFile } = require('node:process');
2320+
loadEnvFile('.env', { override: true });
2321+
```
2322+
2323+
```mjs
2324+
import { loadEnvFile } from 'node:process';
2325+
loadEnvFile('.env', { override: true });
2326+
```
2327+
2328+
This API is available through the [`--env-file-override-local`][] flag.
2329+
2330+
Load environment variables with options only
2331+
2332+
```cjs
2333+
const { loadEnvFile } = require('node:process');
2334+
loadEnvFile({ override: true });
2335+
```
2336+
2337+
```mjs
2338+
import { loadEnvFile } from 'node:process';
2339+
loadEnvFile({ override: true });
2340+
```
2341+
23082342
## `process.mainModule`
23092343
23102344
<!-- YAML
@@ -4003,6 +4037,7 @@ cases:
40034037
[`'exit'`]: #event-exit
40044038
[`'message'`]: child_process.md#event-message
40054039
[`'uncaughtException'`]: #event-uncaughtexception
4040+
[`--env-file-override-local`]: cli.md#--env-file-override-local
40064041
[`--experimental-permission`]: cli.md#--experimental-permission
40074042
[`--no-deprecation`]: cli.md#--no-deprecation
40084043
[`--unhandled-rejections`]: cli.md#--unhandled-rejectionsmode

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(
364+
env, env->options()->env_file_override_local);
364365
}
365366

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

src/node_dotenv.cc

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ std::vector<std::string> Dotenv::GetPathFromArgs(
4141
return paths;
4242
}
4343

44-
void Dotenv::SetEnvironment(node::Environment* env) {
44+
void Dotenv::SetEnvironment(node::Environment* env, bool should_override) {
4545
if (store_.empty()) {
4646
return;
4747
}
@@ -52,9 +52,7 @@ void Dotenv::SetEnvironment(node::Environment* env) {
5252
auto key = entry.first;
5353
auto value = entry.second;
5454

55-
auto existing = env->env_vars()->Get(key.data());
56-
57-
if (existing.IsNothing()) {
55+
if (!env->env_vars()->Get(key.data()).IsJust() || should_override) {
5856
env->env_vars()->Set(
5957
isolate,
6058
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 should_override);
2828
v8::Local<v8::Object> ToObject(Environment* env);
2929

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

src/node_options.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
629629
"set environment variables from supplied file",
630630
&EnvironmentOptions::env_file);
631631
Implies("--env-file", "[has_env_file_string]");
632+
AddOption(
633+
"--env-file-override-local",
634+
"override environment variables set on machine with variables from "
635+
"supplied files via --env-file flag or loadEnvFile() process function",
636+
&EnvironmentOptions::env_file_override_local);
637+
Implies("--env-file-override-local", "[has_env_file_string]");
632638
AddOption("--test",
633639
"launch test runner on startup",
634640
&EnvironmentOptions::test_runner);

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ class EnvironmentOptions : public Options {
166166
std::string diagnostic_dir;
167167
std::string env_file;
168168
bool has_env_file_string = false;
169+
bool env_file_override_local = false;
169170
bool test_runner = false;
170171
uint64_t test_runner_concurrency = 0;
171172
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 env_file_override_local = 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, env_file_override_local);
487491
break;
488492
}
489493
case dotenv.ParseResult::InvalidContent: {

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,55 @@ 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+
it('supports passing options only', async () => {
126+
process.env.BASIC = 'Original value';
127+
const code = `
128+
process.loadEnvFile({override: true});
129+
const assert = require('assert');
130+
assert.strictEqual(process.env.BASIC, 'basic');
131+
`.trim();
132+
const child = await common.spawnPromisified(
133+
process.execPath,
134+
[ '--eval', code ],
135+
{ cwd: fixtures.path('dotenv/') },
136+
);
137+
assert.strictEqual(child.stderr, '');
138+
assert.strictEqual(child.code, 0);
139+
});
140+
141+
});

0 commit comments

Comments
 (0)