Skip to content

Commit f41af8e

Browse files
committed
src: add built-in .env file support
1 parent 6c08b1f commit f41af8e

File tree

7 files changed

+123
-0
lines changed

7 files changed

+123
-0
lines changed

src/node.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

2222
#include "node.h"
23+
#include "node_dotenv.h"
2324

2425
// ========== local headers ==========
2526

@@ -303,6 +304,10 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
303304
}
304305
#endif
305306

307+
if (env->options()->load_dotenv) {
308+
node::dotenv::LoadFromFile(env->isolate(), env->GetCwd(), env->env_vars());
309+
}
310+
306311
// TODO(joyeecheung): move these conditions into JS land and let the
307312
// deserialize main function take precedence. For workers, we need to
308313
// move the pre-execution part into a different file that can be

src/node_dotenv.cc

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include "node_dotenv.h"
2+
#include "uv.h"
3+
4+
namespace node {
5+
6+
using v8::Isolate;
7+
using v8::NewStringType;
8+
9+
namespace dotenv {
10+
11+
void LoadFromFile(Isolate* isolate,
12+
const std::string_view src,
13+
std::shared_ptr<KVStore> store) {
14+
std::string path = std::string(src) + "/.env";
15+
16+
uv_fs_t req;
17+
auto defer_req_cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });
18+
19+
uv_file file = uv_fs_open(nullptr, &req, path.c_str(), 0, 438, nullptr);
20+
if (req.result < 0) {
21+
// req will be cleaned up by scope leave.
22+
return;
23+
}
24+
uv_fs_req_cleanup(&req);
25+
26+
auto defer_close = OnScopeLeave([file]() {
27+
uv_fs_t close_req;
28+
CHECK_EQ(0, uv_fs_close(nullptr, &close_req, file, nullptr));
29+
uv_fs_req_cleanup(&close_req);
30+
});
31+
32+
std::string result{};
33+
char buffer[8192];
34+
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
35+
36+
while (true) {
37+
auto r = uv_fs_read(nullptr, &req, file, &buf, 1, -1, nullptr);
38+
if (req.result < 0) {
39+
// req will be cleaned up by scope leave.
40+
return;
41+
}
42+
uv_fs_req_cleanup(&req);
43+
if (r <= 0) {
44+
break;
45+
}
46+
result.append(buf.base, r);
47+
}
48+
49+
using std::string_view_literals::operator""sv;
50+
51+
for (const auto& line : SplitString(result, "\n"sv)) {
52+
auto equal_index = line.find('=');
53+
54+
if (equal_index == std::string_view::npos) {
55+
continue;
56+
}
57+
58+
std::string_view key = line.substr(0, equal_index);
59+
std::string_view value = line.substr(equal_index + 1);
60+
61+
store->Set(isolate,
62+
v8::String::NewFromUtf8(
63+
isolate, key.data(), NewStringType::kNormal, key.size())
64+
.ToLocalChecked(),
65+
v8::String::NewFromUtf8(
66+
isolate, value.data(), NewStringType::kNormal, value.size())
67+
.ToLocalChecked());
68+
}
69+
}
70+
71+
} // namespace dotenv
72+
73+
} // namespace node

src/node_dotenv.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#ifndef SRC_NODE_DOTENV_H_
2+
#define SRC_NODE_DOTENV_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include "util-inl.h"
7+
8+
namespace node {
9+
10+
namespace dotenv {
11+
12+
void LoadFromFile(v8::Isolate* isolate,
13+
const std::string_view path,
14+
std::shared_ptr<KVStore> store);
15+
16+
} // namespace dotenv
17+
18+
} // namespace node
19+
20+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
21+
22+
#endif // SRC_NODE_DOTENV_H_

src/node_options.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
575575
"write warnings to file instead of stderr",
576576
&EnvironmentOptions::redirect_warnings,
577577
kAllowedInEnvvar);
578+
AddOption("--load-dotenv",
579+
"load .env configuration file on startup",
580+
&EnvironmentOptions::load_dotenv);
578581
AddOption("--test",
579582
"launch test runner on startup",
580583
&EnvironmentOptions::test_runner);

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ class EnvironmentOptions : public Options {
158158
#endif // HAVE_INSPECTOR
159159
std::string redirect_warnings;
160160
std::string diagnostic_dir;
161+
bool load_dotenv = false;
161162
bool test_runner = false;
162163
bool test_runner_coverage = false;
163164
std::vector<std::string> test_name_pattern;

test/fixtures/dotenv/valid/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DATABASE_PASSWORD=nodejs

test/parallel/test-dotenv.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
3+
require('../common');
4+
const assert = require('node:assert');
5+
const { spawnSync } = require('node:child_process');
6+
const path = require('node:path');
7+
8+
{
9+
const child = spawnSync(
10+
process.execPath,
11+
['--load-dotenv', '-e', 'console.log(process.env.DATABASE_PASSWORD)'],
12+
{
13+
cwd: path.join(__dirname, '../fixtures/dotenv/valid')
14+
}
15+
);
16+
17+
assert.strictEqual(child.stdout.toString(), 'nodejs\n');
18+
}

0 commit comments

Comments
 (0)