Skip to content

Commit

Permalink
src: add support to override local env variables option
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyasShabiCS committed Apr 14, 2024
1 parent f8e325e commit 689209b
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 15 deletions.
11 changes: 11 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,17 @@ Export keyword before a key is ignored:
export USERNAME="nodejs" # will result in `nodejs` as the value.
```

### `--override-env-var`

> Stability: 1.1 - Active development
<!-- YAML
added: REPLACEME
-->

Override existing environment variables on your machine with values specified in
your environment files.

### `-e`, `--eval "script"`

<!-- YAML
Expand Down
20 changes: 19 additions & 1 deletion doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -2280,17 +2280,25 @@ process.kill(process.pid, 'SIGHUP');
When `SIGUSR1` is received by a Node.js process, Node.js will start the
debugger. See [Signal Events][].
## `process.loadEnvFile(path)`
## `process.loadEnvFile(path, options)`
<!-- YAML
added:
- v21.7.0
- v20.12.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/54543

Check warning on line 2291 in doc/api/process.md

View workflow job for this annotation

GitHub Actions / lint-pr-url

pr-url doesn't match the URL of the current PR.
description: Add support to override local environment variables option.
-->
> Stability: 1.1 - Active development
* `path` {string | URL | Buffer | undefined}. **Default:** `'./.env'`
* `options` {Object} Used to provide arguments for parsing environment variables
files. `options` supports the following properties:
* `override` {boolean} to override local environment variables of your
machine. **Default:** `false`.
Loads the `.env` file into `process.env`. Usage of `NODE_OPTIONS`
in the `.env` file will not have any effect on Node.js.
Expand All @@ -2305,6 +2313,16 @@ import { loadEnvFile } from 'node:process';
loadEnvFile();
```
```cjs
const { loadEnvFile } = require('node:process');
loadEnvFile('.env', { override: true });
```
```mjs
import { loadEnvFile } from 'node:process';
loadEnvFile('.env', { override: true });
```
## `process.mainModule`
<!-- YAML
Expand Down
15 changes: 12 additions & 3 deletions lib/internal/process/per_thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,14 +251,23 @@ function wrapProcessMethods(binding) {
/**
* Loads the `.env` file to process.env.
* @param {string | URL | Buffer | undefined} path
* @param {{
* override?: boolean;
* }} [options]
*/
function loadEnvFile(path = undefined) { // Provide optional value so that `loadEnvFile.length` returns 0
function loadEnvFile(path = undefined, options = { override: false }) {
// Provide optional value so that `loadEnvFile.length` returns 0
if (arguments.length === 1 && typeof path === 'object') {
options = path;
path = undefined;
}
validateObject(options, 'options');
if (path != null) {
path = getValidatedPath(path);
_loadEnvFile(toNamespacedPath(path));
} else {
_loadEnvFile();
path = getValidatedPath('.env');
}
_loadEnvFile(toNamespacedPath(path), options.override);
}


Expand Down
3 changes: 2 additions & 1 deletion src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,8 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
#endif

if (env->options()->has_env_file_string) {
per_process::dotenv_file.SetEnvironment(env);
per_process::dotenv_file.SetEnvironment(env,
env->options()->override_env_var);
}

// TODO(joyeecheung): move these conditions into JS land and let the
Expand Down
7 changes: 4 additions & 3 deletions src/node_dotenv.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ std::vector<std::string> Dotenv::GetPathFromArgs(
return paths;
}

void Dotenv::SetEnvironment(node::Environment* env) {
void Dotenv::SetEnvironment(node::Environment* env, bool override_env_var) {
if (store_.empty()) {
return;
}
Expand All @@ -62,9 +62,10 @@ void Dotenv::SetEnvironment(node::Environment* env) {
auto key = entry.first;
auto value = entry.second;

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

if (existing.IsNothing()) {
if (can_set_env_var) {
env->env_vars()->Set(
isolate,
v8::String::NewFromUtf8(
Expand Down
2 changes: 1 addition & 1 deletion src/node_dotenv.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Dotenv {
void ParseContent(const std::string_view content);
ParseResult ParsePath(const std::string_view path);
void AssignNodeOptionsIfAvailable(std::string* node_options);
void SetEnvironment(Environment* env);
void SetEnvironment(Environment* env, bool override_env_var);
v8::Local<v8::Object> ToObject(Environment* env);

static std::vector<std::string> GetPathFromArgs(
Expand Down
5 changes: 5 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,11 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"set environment variables from supplied file",
&EnvironmentOptions::env_file);
Implies("--env-file", "[has_env_file_string]");
AddOption("--override-env-var",
"override environment variables set on machine with variables from "
"supplied file",
&EnvironmentOptions::override_env_var);
Implies("--override-env-var", "[has_env_file_string]");
AddOption("--test",
"launch test runner on startup",
&EnvironmentOptions::test_runner);
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class EnvironmentOptions : public Options {
std::string diagnostic_dir;
std::string env_file;
bool has_env_file_string = false;
bool override_env_var = false;
bool test_runner = false;
uint64_t test_runner_concurrency = 0;
uint64_t test_runner_timeout = 0;
Expand Down
16 changes: 10 additions & 6 deletions src/node_process_methods.cc
Original file line number Diff line number Diff line change
Expand Up @@ -470,11 +470,15 @@ static void ReallyExit(const FunctionCallbackInfo<Value>& args) {

static void LoadEnvFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
Environment* env = Environment::GetCurrent(args);
std::string path = ".env";
if (args.Length() == 1) {
Utf8Value path_value(args.GetIsolate(), args[0]);
path = path_value.ToString();
}

CHECK_EQ(args.Length(), 2);
CHECK(args[0]->IsString());
CHECK(args[1]->IsBoolean());

Utf8Value path_value(args.GetIsolate(), args[0]);
std::string path = path_value.ToString();

bool override_env_var = args[1]->IsTrue();

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

switch (dotenv.ParsePath(path)) {
case dotenv.ParseResult::Valid: {
dotenv.SetEnvironment(env);
dotenv.SetEnvironment(env, override_env_var);
break;
}
case dotenv.ParseResult::InvalidContent: {
Expand Down
36 changes: 36 additions & 0 deletions test/parallel/test-process-load-env-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,39 @@ describe('process.loadEnvFile()', () => {
assert.strictEqual(child.code, 0);
});
});

describe('process.loadEnvFile(path, { override: true })', () => {

it('should not override the original value', async () => {
process.env.BASIC = 'Original value';
const code = `
process.loadEnvFile(${JSON.stringify(validEnvFilePath)});
const assert = require('assert');
assert.strictEqual(process.env.BASIC, 'Original value');
`.trim();
const child = await common.spawnPromisified(
process.execPath,
[ '--eval', code ],
{ cwd: __dirname },
);
assert.strictEqual(child.stderr, '');
assert.strictEqual(child.code, 0);
});

it('should override the original value', async () => {
process.env.BASIC = 'Original value';
const code = `
process.loadEnvFile(${JSON.stringify(validEnvFilePath)}, {override: true});
const assert = require('assert');
assert.strictEqual(process.env.BASIC, 'basic');
`.trim();
const child = await common.spawnPromisified(
process.execPath,
[ '--eval', code ],
{ cwd: __dirname },
);
assert.strictEqual(child.stderr, '');
assert.strictEqual(child.code, 0);
});

});

0 comments on commit 689209b

Please sign in to comment.