Skip to content

Commit 76ea37d

Browse files
committed
src: add process.loadEnvFile programmatic API
1 parent 477d6d7 commit 76ea37d

File tree

7 files changed

+133
-0
lines changed

7 files changed

+133
-0
lines changed

doc/api/process.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,6 +2260,29 @@ process.kill(process.pid, 'SIGHUP');
22602260
When `SIGUSR1` is received by a Node.js process, Node.js will start the
22612261
debugger. See [Signal Events][].
22622262
2263+
## `process.loadEnvFile(path)`
2264+
2265+
<!-- YAML
2266+
added: REPLACEME
2267+
-->
2268+
2269+
> Stability: 1.1 - Active development
2270+
2271+
* `path` {string | undefined}
2272+
2273+
Loads the `.env` file into `process.env`. By default, the path is resolved to
2274+
`path.resolve(process.cwd(), '.env')`. If file is not found, no error is thrown.
2275+
2276+
```cjs
2277+
const { loadEnvFile } = require('node:process');
2278+
loadEnvFile();
2279+
```
2280+
2281+
```mjs
2282+
import { loadEnvFile } from 'node:process';
2283+
loadEnvFile();
2284+
```
2285+
22632286
## `process.mainModule`
22642287
22652288
<!-- YAML

lib/internal/bootstrap/node.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ const rawMethods = internalBinding('process_methods');
172172
process._kill = rawMethods._kill;
173173

174174
const wrapped = perThreadSetup.wrapProcessMethods(rawMethods);
175+
process.loadEnvFile = wrapped.loadEnvFile;
175176
process._rawDebug = wrapped._rawDebug;
176177
process.cpuUsage = wrapped.cpuUsage;
177178
process.resourceUsage = wrapped.resourceUsage;

lib/internal/process/per_thread.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const {
4545
validateArray,
4646
validateNumber,
4747
validateObject,
48+
validateString,
4849
} = require('internal/validators');
4950
const constants = internalBinding('constants').os.signals;
5051

@@ -108,6 +109,7 @@ function wrapProcessMethods(binding) {
108109
memoryUsage: _memoryUsage,
109110
rss,
110111
resourceUsage: _resourceUsage,
112+
loadEnvFile: _loadEnvFile,
111113
} = binding;
112114

113115
function _rawDebug(...args) {
@@ -258,6 +260,15 @@ function wrapProcessMethods(binding) {
258260
};
259261
}
260262

263+
function loadEnvFile(path) {
264+
if (path != null) {
265+
validateString(path, 'path');
266+
_loadEnvFile(path);
267+
} else {
268+
_loadEnvFile();
269+
}
270+
}
271+
261272

262273
return {
263274
_rawDebug,
@@ -266,6 +277,7 @@ function wrapProcessMethods(binding) {
266277
memoryUsage,
267278
kill,
268279
exit,
280+
loadEnvFile,
269281
};
270282
}
271283

src/node_process.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class BindingData : public SnapshotableObject {
8484

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

87+
static void LoadEnvFile(const v8::FunctionCallbackInfo<v8::Value>& args);
88+
8789
private:
8890
// Buffer length in uint32.
8991
static constexpr size_t kHrTimeBufferLength = 3;

src/node_process_methods.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "env-inl.h"
55
#include "memory_tracker-inl.h"
66
#include "node.h"
7+
#include "node_dotenv.h"
78
#include "node_errors.h"
89
#include "node_external_reference.h"
910
#include "node_internals.h"
@@ -463,6 +464,23 @@ static void ReallyExit(const FunctionCallbackInfo<Value>& args) {
463464
env->Exit(code);
464465
}
465466

467+
static void LoadEnvFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
468+
Environment* env = Environment::GetCurrent(args);
469+
std::string path = ".env";
470+
if (args.Length() == 1) {
471+
CHECK(args[0]->IsString());
472+
Utf8Value path_value(args.GetIsolate(), args[0]);
473+
path = path_value.ToString();
474+
}
475+
476+
THROW_IF_INSUFFICIENT_PERMISSIONS(
477+
env, permission::PermissionScope::kFileSystemRead, path);
478+
479+
Dotenv dotenv{};
480+
dotenv.ParsePath(path);
481+
dotenv.SetEnvironment(env);
482+
}
483+
466484
namespace process {
467485

468486
BindingData::BindingData(Realm* realm,
@@ -616,6 +634,8 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
616634
SetMethod(isolate, target, "reallyExit", ReallyExit);
617635
SetMethodNoSideEffect(isolate, target, "uptime", Uptime);
618636
SetMethod(isolate, target, "patchProcessObject", PatchProcessObject);
637+
638+
SetMethod(isolate, target, "loadEnvFile", LoadEnvFile);
619639
}
620640

621641
static void CreatePerContextProperties(Local<Object> target,
@@ -653,6 +673,8 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
653673
registry->Register(ReallyExit);
654674
registry->Register(Uptime);
655675
registry->Register(PatchProcessObject);
676+
677+
registry->Register(LoadEnvFile);
656678
}
657679

658680
} // namespace process

test/fixtures/dotenv/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
BASIC=basic
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const fixtures = require('../../test/common/fixtures');
5+
const path = require('node:path');
6+
const assert = require('node:assert');
7+
const { describe, it } = require('node:test');
8+
9+
const validEnvFilePath = fixtures.path('dotenv/valid.env');
10+
const missingEnvFile = fixtures.path('dotenv/non-existent-file.env');
11+
12+
describe('process.loadEnvFile()', () => {
13+
14+
it('supports passing path', async () => {
15+
const code = `
16+
process.loadEnvFile(${JSON.stringify(validEnvFilePath)});
17+
const assert = require('assert');
18+
assert.strictEqual(process.env.BASIC, 'basic');
19+
`.trim();
20+
const child = await common.spawnPromisified(
21+
process.execPath,
22+
[ '--eval', code ],
23+
{ cwd: __dirname },
24+
);
25+
assert.strictEqual(child.stderr, '');
26+
assert.strictEqual(child.code, 0);
27+
});
28+
29+
it('supports not-passing a path', async () => {
30+
// Uses `../fixtures/dotenv/.env` file.
31+
const code = `
32+
process.loadEnvFile();
33+
const assert = require('assert');
34+
assert.strictEqual(process.env.BASIC, 'basic');
35+
`.trim();
36+
const child = await common.spawnPromisified(
37+
process.execPath,
38+
[ '--eval', code ],
39+
{ cwd: path.join(__dirname, '../fixtures/dotenv') },
40+
);
41+
assert.strictEqual(child.stderr, '');
42+
assert.strictEqual(child.code, 0);
43+
});
44+
45+
it('should handle non-existent files', async () => {
46+
const code = `
47+
process.loadEnvFile(${JSON.stringify(missingEnvFile)});
48+
`.trim();
49+
const child = await common.spawnPromisified(
50+
process.execPath,
51+
[ '--eval', code ],
52+
{ cwd: __dirname },
53+
);
54+
assert.strictEqual(child.stderr, '');
55+
assert.strictEqual(child.code, 0);
56+
});
57+
58+
it('should check for permissions', async () => {
59+
const code = `
60+
process.loadEnvFile(${JSON.stringify(missingEnvFile)});
61+
`.trim();
62+
const child = await common.spawnPromisified(
63+
process.execPath,
64+
[ '--eval', code, '--experimental-permission' ],
65+
{ cwd: __dirname },
66+
);
67+
assert.match(child.stderr, /Error: Access to this API has been restricted/);
68+
assert.match(child.stderr, /code: 'ERR_ACCESS_DENIED'/);
69+
assert.match(child.stderr, /permission: 'FileSystemRead'/);
70+
assert.strictEqual(child.code, 1);
71+
});
72+
});

0 commit comments

Comments
 (0)