Skip to content

Commit

Permalink
src: add process.loadEnvFile programmatic API
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Jan 16, 2024
1 parent 477d6d7 commit 76ea37d
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 0 deletions.
23 changes: 23 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -2260,6 +2260,29 @@ 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)`
<!-- YAML
added: REPLACEME
-->
> Stability: 1.1 - Active development
* `path` {string | undefined}
Loads the `.env` file into `process.env`. By default, the path is resolved to
`path.resolve(process.cwd(), '.env')`. If file is not found, no error is thrown.
```cjs
const { loadEnvFile } = require('node:process');
loadEnvFile();
```
```mjs
import { loadEnvFile } from 'node:process';
loadEnvFile();
```
## `process.mainModule`
<!-- YAML
Expand Down
1 change: 1 addition & 0 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ const rawMethods = internalBinding('process_methods');
process._kill = rawMethods._kill;

const wrapped = perThreadSetup.wrapProcessMethods(rawMethods);
process.loadEnvFile = wrapped.loadEnvFile;
process._rawDebug = wrapped._rawDebug;
process.cpuUsage = wrapped.cpuUsage;
process.resourceUsage = wrapped.resourceUsage;
Expand Down
12 changes: 12 additions & 0 deletions lib/internal/process/per_thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const {
validateArray,
validateNumber,
validateObject,
validateString,
} = require('internal/validators');
const constants = internalBinding('constants').os.signals;

Expand Down Expand Up @@ -108,6 +109,7 @@ function wrapProcessMethods(binding) {
memoryUsage: _memoryUsage,
rss,
resourceUsage: _resourceUsage,
loadEnvFile: _loadEnvFile,
} = binding;

function _rawDebug(...args) {
Expand Down Expand Up @@ -258,6 +260,15 @@ function wrapProcessMethods(binding) {
};
}

function loadEnvFile(path) {
if (path != null) {
validateString(path, 'path');
_loadEnvFile(path);
} else {
_loadEnvFile();
}
}


return {
_rawDebug,
Expand All @@ -266,6 +277,7 @@ function wrapProcessMethods(binding) {
memoryUsage,
kill,
exit,
loadEnvFile,
};
}

Expand Down
2 changes: 2 additions & 0 deletions src/node_process.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class BindingData : public SnapshotableObject {

static void SlowBigInt(const v8::FunctionCallbackInfo<v8::Value>& args);

static void LoadEnvFile(const v8::FunctionCallbackInfo<v8::Value>& args);

private:
// Buffer length in uint32.
static constexpr size_t kHrTimeBufferLength = 3;
Expand Down
22 changes: 22 additions & 0 deletions src/node_process_methods.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
#include "node_dotenv.h"
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_internals.h"
Expand Down Expand Up @@ -463,6 +464,23 @@ static void ReallyExit(const FunctionCallbackInfo<Value>& args) {
env->Exit(code);
}

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

THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, path);

Dotenv dotenv{};
dotenv.ParsePath(path);
dotenv.SetEnvironment(env);
}

namespace process {

BindingData::BindingData(Realm* realm,
Expand Down Expand Up @@ -616,6 +634,8 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
SetMethod(isolate, target, "reallyExit", ReallyExit);
SetMethodNoSideEffect(isolate, target, "uptime", Uptime);
SetMethod(isolate, target, "patchProcessObject", PatchProcessObject);

SetMethod(isolate, target, "loadEnvFile", LoadEnvFile);
}

static void CreatePerContextProperties(Local<Object> target,
Expand Down Expand Up @@ -653,6 +673,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(ReallyExit);
registry->Register(Uptime);
registry->Register(PatchProcessObject);

registry->Register(LoadEnvFile);
}

} // namespace process
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/dotenv/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BASIC=basic
72 changes: 72 additions & 0 deletions test/parallel/test-process-load-env-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
'use strict';

const common = require('../common');
const fixtures = require('../../test/common/fixtures');
const path = require('node:path');
const assert = require('node:assert');
const { describe, it } = require('node:test');

const validEnvFilePath = fixtures.path('dotenv/valid.env');
const missingEnvFile = fixtures.path('dotenv/non-existent-file.env');

describe('process.loadEnvFile()', () => {

it('supports passing path', async () => {
const code = `
process.loadEnvFile(${JSON.stringify(validEnvFilePath)});
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);
});

it('supports not-passing a path', async () => {
// Uses `../fixtures/dotenv/.env` file.
const code = `
process.loadEnvFile();
const assert = require('assert');
assert.strictEqual(process.env.BASIC, 'basic');
`.trim();
const child = await common.spawnPromisified(
process.execPath,
[ '--eval', code ],
{ cwd: path.join(__dirname, '../fixtures/dotenv') },
);
assert.strictEqual(child.stderr, '');
assert.strictEqual(child.code, 0);
});

it('should handle non-existent files', async () => {
const code = `
process.loadEnvFile(${JSON.stringify(missingEnvFile)});
`.trim();
const child = await common.spawnPromisified(
process.execPath,
[ '--eval', code ],
{ cwd: __dirname },
);
assert.strictEqual(child.stderr, '');
assert.strictEqual(child.code, 0);
});

it('should check for permissions', async () => {
const code = `
process.loadEnvFile(${JSON.stringify(missingEnvFile)});
`.trim();
const child = await common.spawnPromisified(
process.execPath,
[ '--eval', code, '--experimental-permission' ],
{ cwd: __dirname },
);
assert.match(child.stderr, /Error: Access to this API has been restricted/);
assert.match(child.stderr, /code: 'ERR_ACCESS_DENIED'/);
assert.match(child.stderr, /permission: 'FileSystemRead'/);
assert.strictEqual(child.code, 1);
});
});

0 comments on commit 76ea37d

Please sign in to comment.