Skip to content

Commit d6e8866

Browse files
src: extend dot-env to also handle sections
Make sure that `--env-file` can accept sections, also extend the `parseEnv` and `process.loadEnvFile` APIs to accept sections as well
1 parent e679e38 commit d6e8866

File tree

14 files changed

+452
-43
lines changed

14 files changed

+452
-43
lines changed

doc/api/cli.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,29 @@ Export keyword before a key is ignored:
905905
export USERNAME="nodejs" # will result in `nodejs` as the value.
906906
```
907907

908+
Additionally sections can be used to have a more granular control of
909+
the environment variables within a single file.
910+
911+
Sections can be defined in the file and then targeted by including a hash character
912+
followed by their name as the flag's argument. Multiple sections can be specified and
913+
if a variable is defined in multiple sections the latest instance of the variable in
914+
the file overrides the others.
915+
916+
For example given the following file:
917+
918+
```text
919+
MY_VAR = 'my top-level variable'
920+
921+
[dev]
922+
MY_VAR = 'my variable for development'
923+
924+
[prod]
925+
MY_VAR = 'my variable for production'
926+
```
927+
928+
`--env-file=config#dev` will make it so that the variable's value being used is
929+
taken from the `dev` section.
930+
908931
If you want to load environment variables from a file that may not exist, you
909932
can use the [`--env-file-if-exists`][] flag instead.
910933

doc/api/process.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2791,6 +2791,10 @@ added:
27912791
27922792
* `path` {string | URL | Buffer | undefined}. **Default:** `'./.env'`
27932793
2794+
* `options`
2795+
2796+
* `sections` {string\[]} (optional) The sections to use.
2797+
27942798
Loads the `.env` file into `process.env`. Usage of `NODE_OPTIONS`
27952799
in the `.env` file will not have any effect on Node.js.
27962800

doc/api/util.md

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,7 +2133,7 @@ $ node negate.js --no-logfile --logfile=test.log --color --no-color
21332133
{ logfile: 'test.log', color: false }
21342134
```
21352135
2136-
## `util.parseEnv(content)`
2136+
## `util.parseEnv(content, sections)`
21372137
21382138
<!-- YAML
21392139
added:
@@ -2143,28 +2143,72 @@ added:
21432143
21442144
> Stability: 1.1 - Active development
21452145
2146-
* `content` {string}
2146+
* `content` {string} The con
2147+
The raw contents of a `.env` file.
21472148
2148-
The raw contents of a `.env` file.
2149+
* `options`
2150+
2151+
* `sections` {string\[]} (optional)
2152+
The sections to take from the content
21492153
21502154
* Returns: {Object}
21512155
2152-
Given an example `.env` file:
2156+
Examples:
21532157
21542158
```cjs
21552159
const { parseEnv } = require('node:util');
21562160

2157-
parseEnv('HELLO=world\nHELLO=oh my\n');
2161+
parseEnv(`
2162+
HELLO=world
2163+
HELLO=oh my
2164+
`);
21582165
// Returns: { HELLO: 'oh my' }
21592166
```
21602167
21612168
```mjs
21622169
import { parseEnv } from 'node:util';
21632170

2164-
parseEnv('HELLO=world\nHELLO=oh my\n');
2171+
parseEnv(`
2172+
HELLO=world
2173+
HELLO=oh my
2174+
`);
21652175
// Returns: { HELLO: 'oh my' }
21662176
```
21672177
2178+
```cjs
2179+
const { parseEnv } = require('node:util');
2180+
2181+
parseEnv(`
2182+
X = x
2183+
Y = y
2184+
2185+
[section-a]
2186+
X = x_a
2187+
2188+
[section-b]
2189+
Y = y_b
2190+
Z = z_b
2191+
`, { sections: ['section-b'] });
2192+
// Returns: { X: 'x', Y: 'y_b', Z: 'z_b' }
2193+
```
2194+
2195+
```mjs
2196+
import { parseEnv } from 'node:util';
2197+
2198+
parseEnv(`
2199+
X = x
2200+
Y = y
2201+
2202+
[section-a]
2203+
X = x_a
2204+
2205+
[section-b]
2206+
Y = y_b
2207+
Z = z_b
2208+
`, { sections: ['section-b'] });
2209+
// Returns: { X: 'x', Y: 'y_b', Z: 'z_b' }
2210+
```
2211+
21682212
## `util.promisify(original)`
21692213
21702214
<!-- YAML

lib/internal/process/per_thread.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ const {
5252
validateNumber,
5353
validateObject,
5454
validateString,
55+
validateStringArray,
5556
} = require('internal/validators');
5657

5758
const dc = require('diagnostics_channel');
@@ -354,15 +355,13 @@ function wrapProcessMethods(binding) {
354355
/**
355356
* Loads the `.env` file to process.env.
356357
* @param {string | URL | Buffer | undefined} path
358+
* @param {{ sections?: [] } | undefined} options
357359
*/
358-
function loadEnvFile(path = undefined) { // Provide optional value so that `loadEnvFile.length` returns 0
359-
if (path != null) {
360-
getValidatedPath ??= require('internal/fs/utils').getValidatedPath;
361-
path = getValidatedPath(path);
362-
_loadEnvFile(path);
363-
} else {
364-
_loadEnvFile();
365-
}
360+
function loadEnvFile(path = '.env', options = {}) {
361+
getValidatedPath ??= require('internal/fs/utils').getValidatedPath;
362+
path = getValidatedPath(path);
363+
validateStringArray(options.sections ?? [], 'options.section');
364+
_loadEnvFile(path, options.sections ?? []);
366365
}
367366

368367
return {

lib/util.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const {
6161
validateFunction,
6262
validateNumber,
6363
validateString,
64+
validateStringArray,
6465
validateOneOf,
6566
validateObject,
6667
} = require('internal/validators');
@@ -316,11 +317,13 @@ function _exceptionWithHostPort(...args) {
316317
/**
317318
* Parses the content of a `.env` file.
318319
* @param {string} content
320+
* @param {{ sections?: [] } | undefined} options
319321
* @returns {Record<string, string>}
320322
*/
321-
function parseEnv(content) {
323+
function parseEnv(content, options = {}) {
322324
validateString(content, 'content');
323-
return binding.parseEnv(content);
325+
validateStringArray(options.sections ?? [], 'options.section');
326+
return binding.parseEnv(content, options.sections ?? []);
324327
}
325328

326329
const lazySourceMap = getLazy(() => require('internal/source_map/source_map_cache'));

src/node.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,8 @@ static ExitCode InitializeNodeWithArgsInternal(
873873
CHECK(!per_process::v8_initialized);
874874

875875
for (const auto& file_data : env_files) {
876-
switch (per_process::dotenv_file.ParsePath(file_data.path)) {
876+
switch (per_process::dotenv_file.ParsePath(file_data.path,
877+
file_data.sections)) {
877878
case Dotenv::ParseResult::Valid:
878879
break;
879880
case Dotenv::ParseResult::InvalidContent:

0 commit comments

Comments
 (0)