| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 9097de0 commit b8c89a6
7 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -801,6 +801,8 @@ in the file, the value from the environment takes precedence. | |||
| 801 | 801 | You can pass multiple `--env-file` arguments. Subsequent files override | |
| 802 | 802 | pre-existing variables defined in previous files. | |
| 803 | 803 | ||
| 804 | + An error is thrown if the file does not exist. | ||
| 805 | + | ||
| 804 | 806 | ```bash | |
| 805 | 807 | node --env-file=.env --env-file=.development.env index.js | |
| 806 | 808 | ``` | |
@@ -840,6 +842,9 @@ Export keyword before a key is ignored: | |||
| 840 | 842 | export USERNAME="nodejs" # will result in `nodejs` as the value. | |
| 841 | 843 | ``` | |
| 842 | 844 | ||
| 845 | + If you want to load environment variables from a file that may not exist, you | ||
| 846 | + can use the [`--env-file-if-exists`][] flag instead. | ||
| 847 | + | ||
| 843 | 848 | ### `-e`, `--eval "script"` | |
| 844 | 849 | ||
| 845 | 850 | <!-- YAML | |
@@ -1678,6 +1683,15 @@ is being linked to Node.js. Sharing the OpenSSL configuration may have unwanted | |||
| 1678 | 1683 | implications and it is recommended to use a configuration section specific to | |
| 1679 | 1684 | Node.js which is `nodejs_conf` and is default when this option is not used. | |
| 1680 | 1685 | ||
| 1686 | + ### `--env-file-if-exists=config` | ||
| 1687 | + | ||
| 1688 | + <!-- YAML | ||
| 1689 | + added: REPLACEME | ||
| 1690 | + --> | ||
| 1691 | + | ||
| 1692 | + Behavior is the same as [`--env-file`][], but an error is not thrown if the file | ||
| 1693 | + does not exist. | ||
| 1694 | + | ||
| 1681 | 1695 | ### `--pending-deprecation` | |
| 1682 | 1696 | ||
| 1683 | 1697 | <!-- YAML | |
@@ -3354,6 +3368,8 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12 | |||
| 3354 | 3368 | [`--build-snapshot`]: #--build-snapshot | |
| 3355 | 3369 | [`--cpu-prof-dir`]: #--cpu-prof-dir | |
| 3356 | 3370 | [`--diagnostic-dir`]: #--diagnostic-dirdirectory | |
| 3371 | + [`--env-file-if-exists`]: #--env-file-if-existsconfig | ||
| 3372 | + [`--env-file`]: #--env-fileconfig | ||
| 3357 | 3373 | [`--experimental-default-type=module`]: #--experimental-default-typetype | |
| 3358 | 3374 | [`--experimental-sea-config`]: single-executable-applications.md#generating-single-executable-preparation-blobs | |
| 3359 | 3375 | [`--experimental-wasm-modules`]: #--experimental-wasm-modules | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -901,20 +901,26 @@ static ExitCode InitializeNodeWithArgsInternal( | |||
| 901 | 901 | HandleEnvOptions(per_process::cli_options->per_isolate->per_env); | |
| 902 | 902 | ||
| 903 | 903 | std::string node_options; | |
| 904 | - auto file_paths = node::Dotenv::GetPathFromArgs(*argv); | ||
| 904 | + auto env_files = node::Dotenv::GetDataFromArgs(*argv); | ||
| 905 | 905 | ||
| 906 | - if (!file_paths.empty()) { | ||
| 906 | + if (!env_files.empty()) { | ||
| 907 | 907 | CHECK(!per_process::v8_initialized); | |
| 908 | 908 | ||
| 909 | - for (const auto& file_path : file_paths) { | ||
| 910 | - switch (per_process::dotenv_file.ParsePath(file_path)) { | ||
| 909 | + for (const auto& file_data : env_files) { | ||
| 910 | + switch (per_process::dotenv_file.ParsePath(file_data.path)) { | ||
| 911 | 911 | case Dotenv::ParseResult::Valid: | |
| 912 | 912 | break; | |
| 913 | 913 | case Dotenv::ParseResult::InvalidContent: | |
| 914 | - errors->push_back(file_path + ": invalid format"); | ||
| 914 | + errors->push_back(file_data.path + ": invalid format"); | ||
| 915 | 915 | break; | |
| 916 | 916 | case Dotenv::ParseResult::FileError: | |
| 917 | - errors->push_back(file_path + ": not found"); | ||
| 917 | + if (file_data.is_optional) { | ||
| 918 | + fprintf(stderr, | ||
| 919 | + "%s not found. Continuing without it.\n", | ||
| 920 | + file_data.path.c_str()); | ||
| 921 | + continue; | ||
| 922 | + } | ||
| 923 | + errors->push_back(file_data.path + ": not found"); | ||
| 918 | 924 | break; | |
| 919 | 925 | default: | |
| 920 | 926 | UNREACHABLE(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,43 +11,66 @@ using v8::NewStringType; | |||
| 11 | 11 | using v8::Object; | |
| 12 | 12 | using v8::String; | |
| 13 | 13 | ||
| 14 | - std::vector<std::string> Dotenv::GetPathFromArgs( | ||
| 14 | + std::vector<Dotenv::env_file_data> Dotenv::GetDataFromArgs( | ||
| 15 | 15 | const std::vector<std::string>& args) { | |
| 16 | + const std::string_view optional_env_file_flag = "--env-file-if-exists"; | ||
| 17 | + | ||
| 16 | 18 | const auto find_match = [](const std::string& arg) { | |
| 17 | 19 | auto arg_chars = arg.c_str(); | |
| 18 | 20 | auto arg_len = arg.size(); | |
| 19 | 21 | if (arg_chars[0] != '-' || arg_chars[1] != '-') return false; | |
| 20 | 22 | if (arg_len == 2) return true; // arg == "--" | |
| 21 | 23 | const std::string_view flag = "env-file"; | |
| 22 | - const auto len = flag.size(); | ||
| 24 | + auto len = flag.size(); | ||
| 23 | 25 | if (strncmp(arg_chars + 2, flag.data(), len) != 0) return false; | |
| 26 | + if (arg_len == 2 + len) return true; | ||
| 27 | + const std::string_view flag2 = "-if-exists"; | ||
| 28 | + if (strncmp(arg_chars + 2 + len, flag2.data(), flag2.size()) == 0) | ||
| 29 | + len += flag2.size(); | ||
| 24 | 30 | return arg_len == 2 + len || arg_chars[2 + len] == '='; | |
| 25 | 31 | }; | |
| 26 | - std::vector<std::string> paths; | ||
| 27 | - auto path = std::find_if(args.begin(), args.end(), find_match); | ||
| 28 | 32 | ||
| 29 | - while (path != args.end()) { | ||
| 30 | - if (path->size() == 2 && strncmp(path->c_str(), "--", 2) == 0) { | ||
| 31 | - return paths; | ||
| 33 | + std::vector<Dotenv::env_file_data> env_files; | ||
| 34 | + // This will be an iterator, pointing to args.end() if no matches are found | ||
| 35 | + auto matched_arg = std::find_if(args.begin(), args.end(), find_match); | ||
| 36 | + | ||
| 37 | + while (matched_arg != args.end()) { | ||
| 38 | + if (matched_arg->size() == 2 && | ||
| 39 | + strncmp(matched_arg->c_str(), "--", 2) == 0) { | ||
| 40 | + return env_files; | ||
| 32 | 41 | } | |
| 33 | - auto equal_char = path->find('='); | ||
| 34 | 42 | ||
| 35 | - if (equal_char != std::string::npos) { | ||
| 36 | - paths.push_back(path->substr(equal_char + 1)); | ||
| 43 | + auto equal_char_index = matched_arg->find('='); | ||
| 44 | + | ||
| 45 | + if (equal_char_index != std::string::npos) { | ||
| 46 | + // `--env-file=path` | ||
| 47 | + auto flag = matched_arg->substr(0, equal_char_index); | ||
| 48 | + auto file_path = matched_arg->substr(equal_char_index + 1); | ||
| 49 | + | ||
| 50 | + struct env_file_data env_file_data = { | ||
| 51 | + file_path, strncmp(matched_arg->c_str(), | ||
| 52 | + optional_env_file_flag.data(), | ||
| 53 | + optional_env_file_flag.size()) == 0}; | ||
| 54 | + env_files.push_back(env_file_data); | ||
| 37 | 55 | } else { | |
| 38 | - auto next_path = std::next(path); | ||
| 56 | + // `--env-file path` | ||
| 57 | + auto file_path = std::next(matched_arg); | ||
| 39 | 58 | ||
| 40 | - if (next_path == args.end()) { | ||
| 41 | - return paths; | ||
| 59 | + if (file_path == args.end()) { | ||
| 60 | + return env_files; | ||
| 42 | 61 | } | |
| 43 | 62 | ||
| 44 | - paths.push_back(*next_path); | ||
| 63 | + struct env_file_data env_file_data = { | ||
| 64 | + *file_path, strncmp(matched_arg->c_str(), | ||
| 65 | + optional_env_file_flag.data(), | ||
| 66 | + optional_env_file_flag.size()) == 0}; | ||
| 67 | + env_files.push_back(env_file_data); | ||
| 45 | 68 | } | |
| 46 | 69 | ||
| 47 | - path = std::find_if(++path, args.end(), find_match); | ||
| 70 | + matched_arg = std::find_if(++matched_arg, args.end(), find_match); | ||
| 48 | 71 | } | |
| 49 | 72 | ||
| 50 | - return paths; | ||
| 73 | + return env_files; | ||
| 51 | 74 | } | |
| 52 | 75 | ||
| 53 | 76 | void Dotenv::SetEnvironment(node::Environment* env) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,6 +13,10 @@ namespace node { | |||
| 13 | 13 | class Dotenv { | |
| 14 | 14 | public: | |
| 15 | 15 | enum ParseResult { Valid, FileError, InvalidContent }; | |
| 16 | + struct env_file_data { | ||
| 17 | + std::string path; | ||
| 18 | + bool is_optional; | ||
| 19 | + }; | ||
| 16 | 20 | ||
| 17 | 21 | Dotenv() = default; | |
| 18 | 22 | Dotenv(const Dotenv& d) = delete; | |
@@ -27,7 +31,7 @@ class Dotenv { | |||
| 27 | 31 | void SetEnvironment(Environment* env); | |
| 28 | 32 | v8::Local<v8::Object> ToObject(Environment* env) const; | |
| 29 | 33 | ||
| 30 | - static std::vector<std::string> GetPathFromArgs( | ||
| 34 | + static std::vector<env_file_data> GetDataFromArgs( | ||
| 31 | 35 | const std::vector<std::string>& args); | |
| 32 | 36 | ||
| 33 | 37 | private: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -663,6 +663,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | |||
| 663 | 663 | "set environment variables from supplied file", | |
| 664 | 664 | &EnvironmentOptions::env_file); | |
| 665 | 665 | Implies("--env-file", "[has_env_file_string]"); | |
| 666 | + AddOption("--env-file-if-exists", | ||
| 667 | + "set environment variables from supplied file", | ||
| 668 | + &EnvironmentOptions::optional_env_file); | ||
| 669 | + Implies("--env-file-if-exists", "[has_env_file_string]"); | ||
| 666 | 670 | AddOption("--test", | |
| 667 | 671 | "launch test runner on startup", | |
| 668 | 672 | &EnvironmentOptions::test_runner); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -177,6 +177,7 @@ class EnvironmentOptions : public Options { | |||
| 177 | 177 | std::string redirect_warnings; | |
| 178 | 178 | std::string diagnostic_dir; | |
| 179 | 179 | std::string env_file; | |
| 180 | + std::string optional_env_file; | ||
| 180 | 181 | bool has_env_file_string = false; | |
| 181 | 182 | bool test_runner = false; | |
| 182 | 183 | uint64_t test_runner_concurrency = 0; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,30 +10,53 @@ const validEnvFilePath = '../fixtures/dotenv/valid.env'; | |||
| 10 | 10 | const nodeOptionsEnvFilePath = '../fixtures/dotenv/node-options.env'; | |
| 11 | 11 | ||
| 12 | 12 | describe('.env supports edge cases', () => { | |
| 13 | - | ||
| 14 | - it('supports multiple declarations', async () => { | ||
| 15 | - // process.env.BASIC is equal to `basic` because the second .env file overrides it. | ||
| 13 | + it('supports multiple declarations, including optional ones', async () => { | ||
| 16 | 14 | const code = ` | |
| 17 | 15 | const assert = require('assert'); | |
| 18 | 16 | assert.strictEqual(process.env.BASIC, 'basic'); | |
| 19 | 17 | assert.strictEqual(process.env.NODE_NO_WARNINGS, '1'); | |
| 20 | 18 | `.trim(); | |
| 19 | + const children = await Promise.all(Array.from({ length: 4 }, (_, i) => | ||
| 20 | + common.spawnPromisified( | ||
| 21 | + process.execPath, | ||
| 22 | + [ | ||
| 23 | + // Bitwise AND to create all 4 possible combinations: | ||
| 24 | + // i & 0b01 is truthy when i has value 0bx1 (i.e. 0b01 (1) and 0b11 (3)), falsy otherwise. | ||
| 25 | + // i & 0b10 is truthy when i has value 0b1x (i.e. 0b10 (2) and 0b11 (3)), falsy otherwise. | ||
| 26 | + `${i & 0b01 ? '--env-file' : '--env-file-if-exists'}=${nodeOptionsEnvFilePath}`, | ||
| 27 | + `${i & 0b10 ? '--env-file' : '--env-file-if-exists'}=${validEnvFilePath}`, | ||
| 28 | + '--eval', code, | ||
| 29 | + ], | ||
| 30 | + { cwd: __dirname }, | ||
| 31 | + ))); | ||
| 32 | + assert.deepStrictEqual(children, Array.from({ length: 4 }, () => ({ | ||
| 33 | + code: 0, | ||
| 34 | + signal: null, | ||
| 35 | + stdout: '', | ||
| 36 | + stderr: '', | ||
| 37 | + }))); | ||
| 38 | + }); | ||
| 39 | + | ||
| 40 | + it('supports absolute paths', async () => { | ||
| 41 | + const code = ` | ||
| 42 | + require('assert').strictEqual(process.env.BASIC, 'basic'); | ||
| 43 | + `.trim(); | ||
| 21 | 44 | const child = await common.spawnPromisified( | |
| 22 | 45 | process.execPath, | |
| 23 | - [ `--env-file=${nodeOptionsEnvFilePath}`, `--env-file=${validEnvFilePath}`, '--eval', code ], | ||
| 24 | - { cwd: __dirname }, | ||
| 46 | + [ `--env-file=${path.resolve(__dirname, validEnvFilePath)}`, '--eval', code ], | ||
| 25 | 47 | ); | |
| 26 | 48 | assert.strictEqual(child.stderr, ''); | |
| 27 | 49 | assert.strictEqual(child.code, 0); | |
| 28 | 50 | }); | |
| 29 | 51 | ||
| 30 | - it('supports absolute paths', async () => { | ||
| 52 | + it('supports a space instead of \'=\' for the flag ', async () => { | ||
| 31 | 53 | const code = ` | |
| 32 | 54 | require('assert').strictEqual(process.env.BASIC, 'basic'); | |
| 33 | 55 | `.trim(); | |
| 34 | 56 | const child = await common.spawnPromisified( | |
| 35 | 57 | process.execPath, | |
| 36 | - [ `--env-file=${path.resolve(__dirname, validEnvFilePath)}`, '--eval', code ], | ||
| 58 | + [ '--env-file', validEnvFilePath, '--eval', code ], | ||
| 59 | + { cwd: __dirname }, | ||
| 37 | 60 | ); | |
| 38 | 61 | assert.strictEqual(child.stderr, ''); | |
| 39 | 62 | assert.strictEqual(child.code, 0); | |
@@ -48,10 +71,23 @@ describe('.env supports edge cases', () => { | |||
| 48 | 71 | [ '--env-file=.env', '--eval', code ], | |
| 49 | 72 | { cwd: __dirname }, | |
| 50 | 73 | ); | |
| 51 | - assert.notStrictEqual(child.stderr.toString(), ''); | ||
| 74 | + assert.notStrictEqual(child.stderr, ''); | ||
| 52 | 75 | assert.strictEqual(child.code, 9); | |
| 53 | 76 | }); | |
| 54 | 77 | ||
| 78 | + it('should handle non-existent optional .env file', async () => { | ||
| 79 | + const code = ` | ||
| 80 | + require('assert').strictEqual(1,1); | ||
| 81 | + `.trim(); | ||
| 82 | + const child = await common.spawnPromisified( | ||
| 83 | + process.execPath, | ||
| 84 | + ['--env-file-if-exists=.env', '--eval', code], | ||
| 85 | + { cwd: __dirname }, | ||
| 86 | + ); | ||
| 87 | + assert.notStrictEqual(child.stderr, ''); | ||
| 88 | + assert.strictEqual(child.code, 0); | ||
| 89 | + }); | ||
| 90 | + | ||
| 55 | 91 | it('should not override existing environment variables but introduce new vars', async () => { | |
| 56 | 92 | const code = ` | |
| 57 | 93 | require('assert').strictEqual(process.env.BASIC, 'existing'); | |
@@ -106,7 +142,7 @@ describe('.env supports edge cases', () => { | |||
| 106 | 142 | '--eval', 'assert.strictEqual(process.env.BASIC, undefined);', | |
| 107 | 143 | '--', '--env-file', validEnvFilePath, | |
| 108 | 144 | ], | |
| 109 | - { cwd: fixtures.path('dotenv') }, | ||
| 145 | + { cwd: __dirname }, | ||
| 110 | 146 | ); | |
| 111 | 147 | assert.strictEqual(child.stdout, ''); | |
| 112 | 148 | assert.strictEqual(child.stderr, ''); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments