Skip to content

Commit

Permalink
src: add built-in .env file support
Browse files Browse the repository at this point in the history
  • Loading branch information
anonrig committed Jul 23, 2023
1 parent 6c08b1f commit f41af8e
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "node.h"
#include "node_dotenv.h"

// ========== local headers ==========

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

if (env->options()->load_dotenv) {
node::dotenv::LoadFromFile(env->isolate(), env->GetCwd(), env->env_vars());
}

// TODO(joyeecheung): move these conditions into JS land and let the
// deserialize main function take precedence. For workers, we need to
// move the pre-execution part into a different file that can be
Expand Down
73 changes: 73 additions & 0 deletions src/node_dotenv.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "node_dotenv.h"
#include "uv.h"

namespace node {

using v8::Isolate;
using v8::NewStringType;

namespace dotenv {

void LoadFromFile(Isolate* isolate,
const std::string_view src,
std::shared_ptr<KVStore> store) {
std::string path = std::string(src) + "/.env";

uv_fs_t req;
auto defer_req_cleanup = OnScopeLeave([&req]() { uv_fs_req_cleanup(&req); });

uv_file file = uv_fs_open(nullptr, &req, path.c_str(), 0, 438, nullptr);
if (req.result < 0) {
// req will be cleaned up by scope leave.
return;
}
uv_fs_req_cleanup(&req);

auto defer_close = OnScopeLeave([file]() {
uv_fs_t close_req;
CHECK_EQ(0, uv_fs_close(nullptr, &close_req, file, nullptr));
uv_fs_req_cleanup(&close_req);
});

std::string result{};
char buffer[8192];
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));

while (true) {
auto r = uv_fs_read(nullptr, &req, file, &buf, 1, -1, nullptr);
if (req.result < 0) {
// req will be cleaned up by scope leave.
return;
}
uv_fs_req_cleanup(&req);
if (r <= 0) {
break;
}
result.append(buf.base, r);
}

using std::string_view_literals::operator""sv;

for (const auto& line : SplitString(result, "\n"sv)) {
auto equal_index = line.find('=');

if (equal_index == std::string_view::npos) {
continue;
}

std::string_view key = line.substr(0, equal_index);
std::string_view value = line.substr(equal_index + 1);

store->Set(isolate,
v8::String::NewFromUtf8(
isolate, key.data(), NewStringType::kNormal, key.size())
.ToLocalChecked(),
v8::String::NewFromUtf8(
isolate, value.data(), NewStringType::kNormal, value.size())
.ToLocalChecked());
}
}

} // namespace dotenv

} // namespace node
22 changes: 22 additions & 0 deletions src/node_dotenv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#ifndef SRC_NODE_DOTENV_H_
#define SRC_NODE_DOTENV_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "util-inl.h"

namespace node {

namespace dotenv {

void LoadFromFile(v8::Isolate* isolate,
const std::string_view path,
std::shared_ptr<KVStore> store);

} // namespace dotenv

} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif // SRC_NODE_DOTENV_H_
3 changes: 3 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"write warnings to file instead of stderr",
&EnvironmentOptions::redirect_warnings,
kAllowedInEnvvar);
AddOption("--load-dotenv",
"load .env configuration file on startup",
&EnvironmentOptions::load_dotenv);
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 @@ -158,6 +158,7 @@ class EnvironmentOptions : public Options {
#endif // HAVE_INSPECTOR
std::string redirect_warnings;
std::string diagnostic_dir;
bool load_dotenv = false;
bool test_runner = false;
bool test_runner_coverage = false;
std::vector<std::string> test_name_pattern;
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/dotenv/valid/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DATABASE_PASSWORD=nodejs
18 changes: 18 additions & 0 deletions test/parallel/test-dotenv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

require('../common');
const assert = require('node:assert');
const { spawnSync } = require('node:child_process');
const path = require('node:path');

{
const child = spawnSync(
process.execPath,
['--load-dotenv', '-e', 'console.log(process.env.DATABASE_PASSWORD)'],
{
cwd: path.join(__dirname, '../fixtures/dotenv/valid')
}
);

assert.strictEqual(child.stdout.toString(), 'nodejs\n');
}

0 comments on commit f41af8e

Please sign in to comment.