dotenv-extended 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -124,24 +124,48 @@ node -r dotenv-extended/config your_script.js dotenv_config_path=./env/.env dote
124
124
 
125
125
  New in 2.0.0, is a feature inspired by [cross-env](https://www.npmjs.com/package/cross-env) to allow you to load environment variables from your `.env` files and then pass them into a non-NodeJS script such as a shell script. This can simplify the process of maintaining variables used in both your Node app and other scripts. To use this command line executable, you will either need to install globally with the `-g` flag, or install `dotenv-extended` in your project and reference it from your npm scripts.
126
126
 
127
+ The package exposes two equivalent CLI commands:
128
+
129
+ - `dotenv-extended` (original)
130
+ - `dee` (short alias)
131
+
127
132
  Install Globally:
128
133
 
129
134
  ```bash
130
135
  npm install -g dotenv-extended
131
136
  ```
132
137
 
133
- Now call your shell scripts through `dotenv-extended` (this uses the defaults):
138
+ Now call your shell scripts through `dee` (this uses the defaults):
134
139
 
135
140
  ```bash
136
- dotenv-extended ./myshellscript.sh --whatever-flags-my-script-takes
141
+ dee ./myshellscript.sh --whatever-flags-my-script-takes
137
142
  ```
138
143
 
139
- Configure `dotenv-extended` by passing any of the dotenv-extended options before your command. Preceed each option with two dashes `--`:
144
+ Configure `dee` (or `dotenv-extended`) by passing any of the dotenv-extended options before your command. Preceed each option with two dashes `--`:
140
145
 
141
146
  ```bash
142
- dotenv-extended --path=/path/to/.env --defaults=/path/to/.env.defaults --errorOnMissing=true ./myshellscript.sh --whatever-flags-my-script-takes
147
+ dee --path=/path/to/.env --defaults=/path/to/.env.defaults --errorOnMissing=true ./myshellscript.sh --whatever-flags-my-script-takes
143
148
  ```
144
149
 
150
+ `--path` and `--defaults` also support comma-separated layered files:
151
+
152
+ ```bash
153
+ dee \
154
+ --defaults=./env/.env.defaults.base,./env/.env.defaults.local \
155
+ --path=./env/.env.base,./env/.env.development \
156
+ ./myshellscript.sh
157
+ ```
158
+
159
+ You can also print the merged dotenv configuration (without full `process.env`) instead of executing a command:
160
+
161
+ ```bash
162
+ dee --print
163
+ dee --print=dotenv
164
+ ```
165
+
166
+ - `--print` outputs JSON
167
+ - `--print=dotenv` outputs `KEY=value` lines
168
+
145
169
  The following are the flags you can pass to the `dotenv-extended` cli with their default values. these options detailed later in this document:
146
170
 
147
171
  ```bash
@@ -169,10 +193,13 @@ require('dotenv-extended').load({
169
193
  path: '.env',
170
194
  defaults: '.env.defaults',
171
195
  schema: '.env.schema',
196
+ schemaExtends: undefined, // string | string[]
172
197
  errorOnMissing: false,
173
198
  errorOnExtra: false,
174
199
  errorOnRegex: false,
200
+ errorOnMissingFiles: false,
175
201
  includeProcessEnv: false,
202
+ returnSchemaOnly: false,
176
203
  assignToProcessEnv: true,
177
204
  overrideProcessEnv: false,
178
205
  });
@@ -188,14 +215,19 @@ DOTENV_CONFIG_SILENT=true
188
215
  DOTENV_CONFIG_PATH=.env
189
216
  DOTENV_CONFIG_DEFAULTS=.env.defaults
190
217
  DOTENV_CONFIG_SCHEMA=.env.schema
218
+ DOTENV_CONFIG_SCHEMA_EXTENDS=
191
219
  DOTENV_CONFIG_ERROR_ON_MISSING=false
192
220
  DOTENV_CONFIG_ERROR_ON_EXTRA=false
193
221
  DOTENV_CONFIG_ERROR_ON_REGEX=false
222
+ DOTENV_CONFIG_ERROR_ON_MISSING_FILES=false
194
223
  DOTENV_CONFIG_INCLUDE_PROCESS_ENV=false
224
+ DOTENV_CONFIG_RETURN_SCHEMA_ONLY=false
195
225
  DOTENV_CONFIG_ASSIGN_TO_PROCESS_ENV=true
196
226
  DOTENV_CONFIG_OVERRIDE_PROCESS_ENV=false
197
227
  ```
198
228
 
229
+ `DOTENV_CONFIG_PATH`, `DOTENV_CONFIG_DEFAULTS`, and `DOTENV_CONFIG_SCHEMA_EXTENDS` can each be set to comma-separated file paths for layered loading.
230
+
199
231
  The `load()` function always returns an object containing the variables loaded from the `.env` and `.env.defaults` files. By default the returned object does not contain the properties held in `process.env` but rather only the ones that are loaded from the `.env` and `.env.defaults` files.
200
232
 
201
233
  ```javascript
@@ -214,14 +246,41 @@ Sets whether a log message is shown when missing the `.env` or `.env.defaults` f
214
246
 
215
247
  The main `.env` file that contains your variables.
216
248
 
249
+ - Accepts `string` or `string[]` in code.
250
+ - Accepts comma-separated paths via environment variable:
251
+ - `DOTENV_CONFIG_PATH=./.env.base,./.env.dev`
252
+ - Merge order is deterministic:
253
+ - load each `path` entry in order
254
+ - later entries override earlier keys
255
+
217
256
  ### defaults (_default: .env.defaults_)
218
257
 
219
258
  The file that default values are loaded from.
220
259
 
260
+ - Accepts `string` or `string[]` in code.
261
+ - Accepts comma-separated paths via environment variable:
262
+ - `DOTENV_CONFIG_DEFAULTS=./.env.defaults.base,./.env.defaults.shared`
263
+ - Merge order is deterministic:
264
+ - load each `defaults` entry in order
265
+ - later entries override earlier keys
266
+ - `path` layers are applied after all `defaults` layers, so `path` still overrides `defaults`
267
+
221
268
  ### schema (_default: .env.schema_)
222
269
 
223
270
  The file that contains the schema of what values should be available from combining `.env` and `.env.defaults`
224
271
 
272
+ ### schemaExtends (_default: undefined_)
273
+
274
+ Optional schema extension file(s) layered on top of `schema`.
275
+
276
+ - Accepts `string` or `string[]` in code.
277
+ - Accepts comma-separated paths via environment variable:
278
+ - `DOTENV_CONFIG_SCHEMA_EXTENDS=./.env.production.schema,./.env.region.schema`
279
+ - Merge order is deterministic:
280
+ - load base `schema` first
281
+ - then apply `schemaExtends` in order
282
+ - later layers override earlier keys
283
+
225
284
  ### errorOnMissing (_default: false_)
226
285
 
227
286
  Causes the library to throw a `MISSING CONFIG VALUES` error listing all of the variables missing the combined `.env` and `.env.defaults` files.
@@ -234,18 +293,51 @@ Causes the library to throw a `EXTRA CONFIG VALUES` error listing all of the ext
234
293
 
235
294
  Causes the library to throw a `REGEX MISMATCH` error listing all of the invalid variables from the combined `.env` and `.env.defaults` files. Also a `SyntaxError` is thrown in case `.env.schema` contains a syntactically invalid regex.
236
295
 
296
+ ### errorOnMissingFiles (_default: false_)
297
+
298
+ Causes the library to throw a `MISSING CONFIG FILE` error when configured dotenv files cannot be found. This applies to `path`, `defaults`, and `schema` when they are loaded.
299
+
237
300
  ### includeProcessEnv (_default: false_)
238
301
 
239
302
  Causes the library add process.env variables to error checking. The variables in process.env overrides the variables in .env and .env.defaults while checking
240
303
 
304
+ ### returnSchemaOnly (_default: false_)
305
+
306
+ Causes the returned object to include only variables present in `.env.schema`. This is useful when using `includeProcessEnv` for validation but you only want schema-defined keys in the final result.
307
+
241
308
  ### assignToProcessEnv (_default: true_)
242
309
 
243
- Sets whether the loaded values are assigned to the `process.env` object. If this is set, you must capture the return value of the call to `.load()` or you will not be able to use your variables.
310
+ Sets whether the loaded values are assigned to the `process.env` object. If this is `false`, values are only available in the returned object from `.load()`.
244
311
 
245
312
  ### overrideProcessEnv (_default: false_)
246
313
 
247
314
  By defaut, `dotenv-entended` will not overwrite any varibles that are already set in the `process.env` object. If you would like to enable overwriting any already existing values, set this value to `true`.
248
315
 
316
+ ### Layered File Precedence
317
+
318
+ When using multiple files, merge precedence is:
319
+
320
+ 1. `defaults` layers in order (last wins)
321
+ 2. `path` layers in order (last wins)
322
+ 3. `process.env` values, if `includeProcessEnv` is true and `overrideProcessEnv` is false
323
+
324
+ Example:
325
+
326
+ ```javascript
327
+ const config = require('dotenv-extended').load({
328
+ defaults: ['./env/.env.defaults.base', './env/.env.defaults.region'],
329
+ path: ['./env/.env.base', './env/.env.production'],
330
+ });
331
+ ```
332
+
333
+ Equivalent via environment variables:
334
+
335
+ ```bash
336
+ DOTENV_CONFIG_DEFAULTS=./env/.env.defaults.base,./env/.env.defaults.region \
337
+ DOTENV_CONFIG_PATH=./env/.env.base,./env/.env.production \
338
+ node app.js
339
+ ```
340
+
249
341
  ## Examples
250
342
 
251
343
  Consider the following three files:
@@ -27,17 +27,19 @@ export interface IDotenvExtendedOptions {
27
27
 
28
28
  /**
29
29
  * Path to the main .env file that contains your variables.
30
+ * Can be a string path or layered string[] where later entries override earlier ones.
30
31
  *
31
32
  * @default '.env'
32
33
  */
33
- path?: string;
34
+ path?: string | string[];
34
35
 
35
36
  /**
36
37
  * The path to the file that default values are loaded from.
38
+ * Can be a string path or layered string[] where later entries override earlier ones.
37
39
  *
38
40
  * @default '.env.defaults'
39
41
  */
40
- defaults?: string;
42
+ defaults?: string | string[];
41
43
 
42
44
  /**
43
45
  * The path to the file that contains the schema of what values should be available
@@ -47,6 +49,12 @@ export interface IDotenvExtendedOptions {
47
49
  */
48
50
  schema?: string;
49
51
 
52
+ /**
53
+ * Optional schema extension path(s). These are layered on top of `schema` in order.
54
+ * Later entries override earlier keys (including base schema keys).
55
+ */
56
+ schemaExtends?: string | string[];
57
+
50
58
  /**
51
59
  * Causes the library to throw a MISSING CONFIG VALUES error listing all of the variables
52
60
  * missing the combined .env and .env.defaults files.
@@ -71,6 +79,14 @@ export interface IDotenvExtendedOptions {
71
79
  */
72
80
  errorOnRegex?: boolean;
73
81
 
82
+ /**
83
+ * Causes the library to throw when a configured dotenv file path cannot be found.
84
+ * Applies to `path`, `defaults`, and `schema` when they are loaded.
85
+ *
86
+ * @default false
87
+ */
88
+ errorOnMissingFiles?: boolean;
89
+
74
90
  /**
75
91
  * Causes the library add process.env variables to error checking. The variables in process.env overrides the
76
92
  * variables in .env and .env.defaults while checking
@@ -79,10 +95,17 @@ export interface IDotenvExtendedOptions {
79
95
  */
80
96
  includeProcessEnv?: boolean;
81
97
 
98
+ /**
99
+ * Causes the returned object (and any process assignment) to include only variables present in the schema file.
100
+ * This is useful when `includeProcessEnv` is enabled for validation, but you only want schema-defined keys.
101
+ *
102
+ * @default false
103
+ */
104
+ returnSchemaOnly?: boolean;
105
+
82
106
  /**
83
107
  * Sets whether the loaded values are assigned to the process.env object.
84
- * If this is set, you must capture the return value of the call to .load() or you will not be
85
- * able to use your variables.
108
+ * If this is false, values are only available from the returned object.
86
109
  *
87
110
  * @default true
88
111
  */
package/lib/bin/cli.js CHANGED
@@ -93,11 +93,14 @@ var config_from_env_default = getConfigFromEnv;
93
93
  // src/utils/load-environment-file.js
94
94
  var import_fs = __toESM(require("fs"));
95
95
  var import_dotenv = __toESM(require("dotenv"));
96
- var loadEnvironmentFile = (path, encoding, silent) => {
96
+ var loadEnvironmentFile = (path, encoding, silent, errorOnMissingFiles = false) => {
97
97
  try {
98
98
  const data = import_fs.default.readFileSync(path, encoding);
99
99
  return import_dotenv.default.parse(data);
100
100
  } catch (err) {
101
+ if (errorOnMissingFiles && err && err.code === "ENOENT") {
102
+ throw new Error(`MISSING CONFIG FILE: ${path}`);
103
+ }
101
104
  if (!silent) {
102
105
  console.error(err.message);
103
106
  }
@@ -107,6 +110,27 @@ var loadEnvironmentFile = (path, encoding, silent) => {
107
110
  var load_environment_file_default = loadEnvironmentFile;
108
111
 
109
112
  // src/index.js
113
+ var normalizeLayeredFiles = (value) => {
114
+ if (!value) {
115
+ return [];
116
+ }
117
+ if (Array.isArray(value)) {
118
+ return value.filter(Boolean);
119
+ }
120
+ if (typeof value === "string") {
121
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
122
+ }
123
+ return [];
124
+ };
125
+ var loadLayeredFiles = (files, options) => normalizeLayeredFiles(files).reduce((acc, filePath) => {
126
+ const fileData = load_environment_file_default(
127
+ filePath,
128
+ options.encoding,
129
+ options.silent,
130
+ options.errorOnMissingFiles
131
+ );
132
+ return Object.assign(acc, fileData);
133
+ }, {});
110
134
  var parse = import_dotenv2.default.parse.bind(import_dotenv2.default);
111
135
  var config = (options) => {
112
136
  let defaultsData, environmentData, defaultOptions = {
@@ -115,23 +139,44 @@ var config = (options) => {
115
139
  path: ".env",
116
140
  defaults: ".env.defaults",
117
141
  schema: ".env.schema",
142
+ schemaExtends: void 0,
118
143
  errorOnMissing: false,
119
144
  errorOnExtra: false,
120
145
  errorOnRegex: false,
146
+ errorOnMissingFiles: false,
147
+ returnSchemaOnly: false,
121
148
  includeProcessEnv: false,
122
149
  assignToProcessEnv: true,
123
150
  overrideProcessEnv: false
124
151
  }, processEnvOptions = config_from_env_default(process.env);
125
152
  options = Object.assign({}, defaultOptions, processEnvOptions, options);
126
- defaultsData = load_environment_file_default(options.defaults, options.encoding, options.silent);
127
- environmentData = load_environment_file_default(options.path, options.encoding, options.silent);
153
+ defaultsData = loadLayeredFiles(options.defaults, options);
154
+ environmentData = loadLayeredFiles(options.path, options);
128
155
  let configData = Object.assign({}, defaultsData, environmentData);
129
156
  const config2 = options.includeProcessEnv ? Object.assign({}, configData, process.env) : configData;
130
157
  const configOnlyKeys = Object.keys(configData);
131
158
  const configKeys = Object.keys(config2);
132
- if (options.errorOnMissing || options.errorOnExtra || options.errorOnRegex) {
133
- const schema = load_environment_file_default(options.schema, options.encoding, options.silent);
134
- const schemaKeys = Object.keys(schema);
159
+ let schemaKeys = null;
160
+ if (options.errorOnMissing || options.errorOnExtra || options.errorOnRegex || options.returnSchemaOnly) {
161
+ const baseSchema = load_environment_file_default(
162
+ options.schema,
163
+ options.encoding,
164
+ options.silent,
165
+ options.errorOnMissingFiles
166
+ );
167
+ const schema = normalizeLayeredFiles(options.schemaExtends).reduce(
168
+ (acc, schemaPath) => {
169
+ const schemaLayer = load_environment_file_default(
170
+ schemaPath,
171
+ options.encoding,
172
+ options.silent,
173
+ options.errorOnMissingFiles
174
+ );
175
+ return Object.assign(acc, schemaLayer);
176
+ },
177
+ Object.assign({}, baseSchema)
178
+ );
179
+ schemaKeys = Object.keys(schema);
135
180
  let missingKeys = schemaKeys.filter(function(key) {
136
181
  return configKeys.indexOf(key) < 0;
137
182
  });
@@ -163,6 +208,14 @@ var config = (options) => {
163
208
  configData[configKeys[i]] = process.env[configKeys[i]];
164
209
  }
165
210
  }
211
+ if (options.returnSchemaOnly && schemaKeys) {
212
+ configData = schemaKeys.reduce((acc, key) => {
213
+ if (typeof config2[key] !== "undefined") {
214
+ acc[key] = config2[key];
215
+ }
216
+ return acc;
217
+ }, {});
218
+ }
166
219
  if (options.assignToProcessEnv) {
167
220
  if (options.overrideProcessEnv) {
168
221
  Object.assign(process.env, configData);
@@ -176,6 +229,7 @@ var config = (options) => {
176
229
 
177
230
  // src/utils/parse-command.js
178
231
  var dotEnvFlagRegex = /^--(.+)=(.+)/;
232
+ var dotEnvBooleanFlagRegex = /^--(.+)$/;
179
233
  var parseCommand = (args) => {
180
234
  const config2 = {};
181
235
  let command = null;
@@ -184,6 +238,11 @@ var parseCommand = (args) => {
184
238
  const match = dotEnvFlagRegex.exec(args[i]);
185
239
  if (match) {
186
240
  config2[normalize_option_key_default(match[1])] = parse_primitive_default(match[2]);
241
+ continue;
242
+ }
243
+ const booleanFlagMatch = dotEnvBooleanFlagRegex.exec(args[i]);
244
+ if (booleanFlagMatch && normalize_option_key_default(booleanFlagMatch[1]) === "print") {
245
+ config2.print = true;
187
246
  } else {
188
247
  command = args[i];
189
248
  commandArgs = args.slice(i + 1);
@@ -197,15 +256,42 @@ var parse_command_default = parseCommand;
197
256
  // src/bin/index.js
198
257
  var import_node_child_process = require("child_process");
199
258
  var spawnCommand = (command, commandArgs, options) => (0, import_node_child_process.spawn)(command, commandArgs, options);
259
+ var writeStdout = (value) => process.stdout.write(value);
260
+ var writeStderr = (value) => process.stderr.write(value);
261
+ var toDotenvString = (values) => Object.keys(values).map((key) => `${key}=${values[key]}`).join("\n");
200
262
  function loadAndExecute(args, dependencies = {}) {
201
263
  const {
202
264
  spawnCommandFn = spawnCommand,
203
265
  processOn = process.on.bind(process),
204
- processExit = process.exit
266
+ processExit = process.exit,
267
+ writeStdoutFn = writeStdout,
268
+ writeStderrFn = writeStderr
205
269
  } = dependencies;
206
270
  const [dotEnvConfig, command, commandArgs] = parse_command_default(args);
271
+ const { print, ...configOptions } = dotEnvConfig;
272
+ if (print && command) {
273
+ writeStderrFn("dotenv-extended: --print mode cannot be combined with command execution.\n");
274
+ processExit(1);
275
+ return;
276
+ }
277
+ if (print) {
278
+ const mergedConfig = config({
279
+ ...configOptions,
280
+ assignToProcessEnv: false,
281
+ includeProcessEnv: false
282
+ });
283
+ const format = typeof print === "string" ? print.toLowerCase() : "json";
284
+ if (format === "dotenv") {
285
+ writeStdoutFn(`${toDotenvString(mergedConfig)}
286
+ `);
287
+ return mergedConfig;
288
+ }
289
+ writeStdoutFn(`${JSON.stringify(mergedConfig, null, 2)}
290
+ `);
291
+ return mergedConfig;
292
+ }
207
293
  if (command) {
208
- config(dotEnvConfig);
294
+ config(configOptions);
209
295
  const proc = spawnCommandFn(command, commandArgs, {
210
296
  stdio: "inherit",
211
297
  shell: true,
package/lib/bin/index.js CHANGED
@@ -31,7 +31,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var bin_exports = {};
32
32
  __export(bin_exports, {
33
33
  loadAndExecute: () => loadAndExecute,
34
- spawnCommand: () => spawnCommand
34
+ spawnCommand: () => spawnCommand,
35
+ writeStderr: () => writeStderr,
36
+ writeStdout: () => writeStdout
35
37
  });
36
38
  module.exports = __toCommonJS(bin_exports);
37
39
 
@@ -106,11 +108,14 @@ var config_from_env_default = getConfigFromEnv;
106
108
  // src/utils/load-environment-file.js
107
109
  var import_fs = __toESM(require("fs"));
108
110
  var import_dotenv = __toESM(require("dotenv"));
109
- var loadEnvironmentFile = (path, encoding, silent) => {
111
+ var loadEnvironmentFile = (path, encoding, silent, errorOnMissingFiles = false) => {
110
112
  try {
111
113
  const data = import_fs.default.readFileSync(path, encoding);
112
114
  return import_dotenv.default.parse(data);
113
115
  } catch (err) {
116
+ if (errorOnMissingFiles && err && err.code === "ENOENT") {
117
+ throw new Error(`MISSING CONFIG FILE: ${path}`);
118
+ }
114
119
  if (!silent) {
115
120
  console.error(err.message);
116
121
  }
@@ -120,6 +125,27 @@ var loadEnvironmentFile = (path, encoding, silent) => {
120
125
  var load_environment_file_default = loadEnvironmentFile;
121
126
 
122
127
  // src/index.js
128
+ var normalizeLayeredFiles = (value) => {
129
+ if (!value) {
130
+ return [];
131
+ }
132
+ if (Array.isArray(value)) {
133
+ return value.filter(Boolean);
134
+ }
135
+ if (typeof value === "string") {
136
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
137
+ }
138
+ return [];
139
+ };
140
+ var loadLayeredFiles = (files, options) => normalizeLayeredFiles(files).reduce((acc, filePath) => {
141
+ const fileData = load_environment_file_default(
142
+ filePath,
143
+ options.encoding,
144
+ options.silent,
145
+ options.errorOnMissingFiles
146
+ );
147
+ return Object.assign(acc, fileData);
148
+ }, {});
123
149
  var parse = import_dotenv2.default.parse.bind(import_dotenv2.default);
124
150
  var config = (options) => {
125
151
  let defaultsData, environmentData, defaultOptions = {
@@ -128,23 +154,44 @@ var config = (options) => {
128
154
  path: ".env",
129
155
  defaults: ".env.defaults",
130
156
  schema: ".env.schema",
157
+ schemaExtends: void 0,
131
158
  errorOnMissing: false,
132
159
  errorOnExtra: false,
133
160
  errorOnRegex: false,
161
+ errorOnMissingFiles: false,
162
+ returnSchemaOnly: false,
134
163
  includeProcessEnv: false,
135
164
  assignToProcessEnv: true,
136
165
  overrideProcessEnv: false
137
166
  }, processEnvOptions = config_from_env_default(process.env);
138
167
  options = Object.assign({}, defaultOptions, processEnvOptions, options);
139
- defaultsData = load_environment_file_default(options.defaults, options.encoding, options.silent);
140
- environmentData = load_environment_file_default(options.path, options.encoding, options.silent);
168
+ defaultsData = loadLayeredFiles(options.defaults, options);
169
+ environmentData = loadLayeredFiles(options.path, options);
141
170
  let configData = Object.assign({}, defaultsData, environmentData);
142
171
  const config2 = options.includeProcessEnv ? Object.assign({}, configData, process.env) : configData;
143
172
  const configOnlyKeys = Object.keys(configData);
144
173
  const configKeys = Object.keys(config2);
145
- if (options.errorOnMissing || options.errorOnExtra || options.errorOnRegex) {
146
- const schema = load_environment_file_default(options.schema, options.encoding, options.silent);
147
- const schemaKeys = Object.keys(schema);
174
+ let schemaKeys = null;
175
+ if (options.errorOnMissing || options.errorOnExtra || options.errorOnRegex || options.returnSchemaOnly) {
176
+ const baseSchema = load_environment_file_default(
177
+ options.schema,
178
+ options.encoding,
179
+ options.silent,
180
+ options.errorOnMissingFiles
181
+ );
182
+ const schema = normalizeLayeredFiles(options.schemaExtends).reduce(
183
+ (acc, schemaPath) => {
184
+ const schemaLayer = load_environment_file_default(
185
+ schemaPath,
186
+ options.encoding,
187
+ options.silent,
188
+ options.errorOnMissingFiles
189
+ );
190
+ return Object.assign(acc, schemaLayer);
191
+ },
192
+ Object.assign({}, baseSchema)
193
+ );
194
+ schemaKeys = Object.keys(schema);
148
195
  let missingKeys = schemaKeys.filter(function(key) {
149
196
  return configKeys.indexOf(key) < 0;
150
197
  });
@@ -176,6 +223,14 @@ var config = (options) => {
176
223
  configData[configKeys[i]] = process.env[configKeys[i]];
177
224
  }
178
225
  }
226
+ if (options.returnSchemaOnly && schemaKeys) {
227
+ configData = schemaKeys.reduce((acc, key) => {
228
+ if (typeof config2[key] !== "undefined") {
229
+ acc[key] = config2[key];
230
+ }
231
+ return acc;
232
+ }, {});
233
+ }
179
234
  if (options.assignToProcessEnv) {
180
235
  if (options.overrideProcessEnv) {
181
236
  Object.assign(process.env, configData);
@@ -189,6 +244,7 @@ var config = (options) => {
189
244
 
190
245
  // src/utils/parse-command.js
191
246
  var dotEnvFlagRegex = /^--(.+)=(.+)/;
247
+ var dotEnvBooleanFlagRegex = /^--(.+)$/;
192
248
  var parseCommand = (args) => {
193
249
  const config2 = {};
194
250
  let command = null;
@@ -197,6 +253,11 @@ var parseCommand = (args) => {
197
253
  const match = dotEnvFlagRegex.exec(args[i]);
198
254
  if (match) {
199
255
  config2[normalize_option_key_default(match[1])] = parse_primitive_default(match[2]);
256
+ continue;
257
+ }
258
+ const booleanFlagMatch = dotEnvBooleanFlagRegex.exec(args[i]);
259
+ if (booleanFlagMatch && normalize_option_key_default(booleanFlagMatch[1]) === "print") {
260
+ config2.print = true;
200
261
  } else {
201
262
  command = args[i];
202
263
  commandArgs = args.slice(i + 1);
@@ -210,15 +271,42 @@ var parse_command_default = parseCommand;
210
271
  // src/bin/index.js
211
272
  var import_node_child_process = require("child_process");
212
273
  var spawnCommand = (command, commandArgs, options) => (0, import_node_child_process.spawn)(command, commandArgs, options);
274
+ var writeStdout = (value) => process.stdout.write(value);
275
+ var writeStderr = (value) => process.stderr.write(value);
276
+ var toDotenvString = (values) => Object.keys(values).map((key) => `${key}=${values[key]}`).join("\n");
213
277
  function loadAndExecute(args, dependencies = {}) {
214
278
  const {
215
279
  spawnCommandFn = spawnCommand,
216
280
  processOn = process.on.bind(process),
217
- processExit = process.exit
281
+ processExit = process.exit,
282
+ writeStdoutFn = writeStdout,
283
+ writeStderrFn = writeStderr
218
284
  } = dependencies;
219
285
  const [dotEnvConfig, command, commandArgs] = parse_command_default(args);
286
+ const { print, ...configOptions } = dotEnvConfig;
287
+ if (print && command) {
288
+ writeStderrFn("dotenv-extended: --print mode cannot be combined with command execution.\n");
289
+ processExit(1);
290
+ return;
291
+ }
292
+ if (print) {
293
+ const mergedConfig = config({
294
+ ...configOptions,
295
+ assignToProcessEnv: false,
296
+ includeProcessEnv: false
297
+ });
298
+ const format = typeof print === "string" ? print.toLowerCase() : "json";
299
+ if (format === "dotenv") {
300
+ writeStdoutFn(`${toDotenvString(mergedConfig)}
301
+ `);
302
+ return mergedConfig;
303
+ }
304
+ writeStdoutFn(`${JSON.stringify(mergedConfig, null, 2)}
305
+ `);
306
+ return mergedConfig;
307
+ }
220
308
  if (command) {
221
- config(dotEnvConfig);
309
+ config(configOptions);
222
310
  const proc = spawnCommandFn(command, commandArgs, {
223
311
  stdio: "inherit",
224
312
  shell: true,
@@ -235,5 +323,7 @@ function loadAndExecute(args, dependencies = {}) {
235
323
  // Annotate the CommonJS export names for ESM import in node:
236
324
  0 && (module.exports = {
237
325
  loadAndExecute,
238
- spawnCommand
326
+ spawnCommand,
327
+ writeStderr,
328
+ writeStdout
239
329
  });
package/lib/config.js CHANGED
@@ -92,11 +92,14 @@ var config_from_env_default = getConfigFromEnv;
92
92
  // src/utils/load-environment-file.js
93
93
  var import_fs = __toESM(require("fs"));
94
94
  var import_dotenv = __toESM(require("dotenv"));
95
- var loadEnvironmentFile = (path, encoding, silent) => {
95
+ var loadEnvironmentFile = (path, encoding, silent, errorOnMissingFiles = false) => {
96
96
  try {
97
97
  const data = import_fs.default.readFileSync(path, encoding);
98
98
  return import_dotenv.default.parse(data);
99
99
  } catch (err) {
100
+ if (errorOnMissingFiles && err && err.code === "ENOENT") {
101
+ throw new Error(`MISSING CONFIG FILE: ${path}`);
102
+ }
100
103
  if (!silent) {
101
104
  console.error(err.message);
102
105
  }
@@ -106,6 +109,27 @@ var loadEnvironmentFile = (path, encoding, silent) => {
106
109
  var load_environment_file_default = loadEnvironmentFile;
107
110
 
108
111
  // src/index.js
112
+ var normalizeLayeredFiles = (value) => {
113
+ if (!value) {
114
+ return [];
115
+ }
116
+ if (Array.isArray(value)) {
117
+ return value.filter(Boolean);
118
+ }
119
+ if (typeof value === "string") {
120
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
121
+ }
122
+ return [];
123
+ };
124
+ var loadLayeredFiles = (files, options2) => normalizeLayeredFiles(files).reduce((acc, filePath) => {
125
+ const fileData = load_environment_file_default(
126
+ filePath,
127
+ options2.encoding,
128
+ options2.silent,
129
+ options2.errorOnMissingFiles
130
+ );
131
+ return Object.assign(acc, fileData);
132
+ }, {});
109
133
  var parse = import_dotenv2.default.parse.bind(import_dotenv2.default);
110
134
  var config = (options2) => {
111
135
  let defaultsData, environmentData, defaultOptions = {
@@ -114,23 +138,44 @@ var config = (options2) => {
114
138
  path: ".env",
115
139
  defaults: ".env.defaults",
116
140
  schema: ".env.schema",
141
+ schemaExtends: void 0,
117
142
  errorOnMissing: false,
118
143
  errorOnExtra: false,
119
144
  errorOnRegex: false,
145
+ errorOnMissingFiles: false,
146
+ returnSchemaOnly: false,
120
147
  includeProcessEnv: false,
121
148
  assignToProcessEnv: true,
122
149
  overrideProcessEnv: false
123
150
  }, processEnvOptions = config_from_env_default(process.env);
124
151
  options2 = Object.assign({}, defaultOptions, processEnvOptions, options2);
125
- defaultsData = load_environment_file_default(options2.defaults, options2.encoding, options2.silent);
126
- environmentData = load_environment_file_default(options2.path, options2.encoding, options2.silent);
152
+ defaultsData = loadLayeredFiles(options2.defaults, options2);
153
+ environmentData = loadLayeredFiles(options2.path, options2);
127
154
  let configData = Object.assign({}, defaultsData, environmentData);
128
155
  const config2 = options2.includeProcessEnv ? Object.assign({}, configData, process.env) : configData;
129
156
  const configOnlyKeys = Object.keys(configData);
130
157
  const configKeys = Object.keys(config2);
131
- if (options2.errorOnMissing || options2.errorOnExtra || options2.errorOnRegex) {
132
- const schema = load_environment_file_default(options2.schema, options2.encoding, options2.silent);
133
- const schemaKeys = Object.keys(schema);
158
+ let schemaKeys = null;
159
+ if (options2.errorOnMissing || options2.errorOnExtra || options2.errorOnRegex || options2.returnSchemaOnly) {
160
+ const baseSchema = load_environment_file_default(
161
+ options2.schema,
162
+ options2.encoding,
163
+ options2.silent,
164
+ options2.errorOnMissingFiles
165
+ );
166
+ const schema = normalizeLayeredFiles(options2.schemaExtends).reduce(
167
+ (acc, schemaPath) => {
168
+ const schemaLayer = load_environment_file_default(
169
+ schemaPath,
170
+ options2.encoding,
171
+ options2.silent,
172
+ options2.errorOnMissingFiles
173
+ );
174
+ return Object.assign(acc, schemaLayer);
175
+ },
176
+ Object.assign({}, baseSchema)
177
+ );
178
+ schemaKeys = Object.keys(schema);
134
179
  let missingKeys = schemaKeys.filter(function(key) {
135
180
  return configKeys.indexOf(key) < 0;
136
181
  });
@@ -162,6 +207,14 @@ var config = (options2) => {
162
207
  configData[configKeys[i]] = process.env[configKeys[i]];
163
208
  }
164
209
  }
210
+ if (options2.returnSchemaOnly && schemaKeys) {
211
+ configData = schemaKeys.reduce((acc, key) => {
212
+ if (typeof config2[key] !== "undefined") {
213
+ acc[key] = config2[key];
214
+ }
215
+ return acc;
216
+ }, {});
217
+ }
165
218
  if (options2.assignToProcessEnv) {
166
219
  if (options2.overrideProcessEnv) {
167
220
  Object.assign(process.env, configData);
package/lib/index.js CHANGED
@@ -105,11 +105,14 @@ var config_from_env_default = getConfigFromEnv;
105
105
  // src/utils/load-environment-file.js
106
106
  var import_fs = __toESM(require("fs"));
107
107
  var import_dotenv = __toESM(require("dotenv"));
108
- var loadEnvironmentFile = (path, encoding, silent) => {
108
+ var loadEnvironmentFile = (path, encoding, silent, errorOnMissingFiles = false) => {
109
109
  try {
110
110
  const data = import_fs.default.readFileSync(path, encoding);
111
111
  return import_dotenv.default.parse(data);
112
112
  } catch (err) {
113
+ if (errorOnMissingFiles && err && err.code === "ENOENT") {
114
+ throw new Error(`MISSING CONFIG FILE: ${path}`);
115
+ }
113
116
  if (!silent) {
114
117
  console.error(err.message);
115
118
  }
@@ -119,6 +122,27 @@ var loadEnvironmentFile = (path, encoding, silent) => {
119
122
  var load_environment_file_default = loadEnvironmentFile;
120
123
 
121
124
  // src/index.js
125
+ var normalizeLayeredFiles = (value) => {
126
+ if (!value) {
127
+ return [];
128
+ }
129
+ if (Array.isArray(value)) {
130
+ return value.filter(Boolean);
131
+ }
132
+ if (typeof value === "string") {
133
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
134
+ }
135
+ return [];
136
+ };
137
+ var loadLayeredFiles = (files, options) => normalizeLayeredFiles(files).reduce((acc, filePath) => {
138
+ const fileData = load_environment_file_default(
139
+ filePath,
140
+ options.encoding,
141
+ options.silent,
142
+ options.errorOnMissingFiles
143
+ );
144
+ return Object.assign(acc, fileData);
145
+ }, {});
122
146
  var parse = import_dotenv2.default.parse.bind(import_dotenv2.default);
123
147
  var config = (options) => {
124
148
  let defaultsData, environmentData, defaultOptions = {
@@ -127,23 +151,44 @@ var config = (options) => {
127
151
  path: ".env",
128
152
  defaults: ".env.defaults",
129
153
  schema: ".env.schema",
154
+ schemaExtends: void 0,
130
155
  errorOnMissing: false,
131
156
  errorOnExtra: false,
132
157
  errorOnRegex: false,
158
+ errorOnMissingFiles: false,
159
+ returnSchemaOnly: false,
133
160
  includeProcessEnv: false,
134
161
  assignToProcessEnv: true,
135
162
  overrideProcessEnv: false
136
163
  }, processEnvOptions = config_from_env_default(process.env);
137
164
  options = Object.assign({}, defaultOptions, processEnvOptions, options);
138
- defaultsData = load_environment_file_default(options.defaults, options.encoding, options.silent);
139
- environmentData = load_environment_file_default(options.path, options.encoding, options.silent);
165
+ defaultsData = loadLayeredFiles(options.defaults, options);
166
+ environmentData = loadLayeredFiles(options.path, options);
140
167
  let configData = Object.assign({}, defaultsData, environmentData);
141
168
  const config2 = options.includeProcessEnv ? Object.assign({}, configData, process.env) : configData;
142
169
  const configOnlyKeys = Object.keys(configData);
143
170
  const configKeys = Object.keys(config2);
144
- if (options.errorOnMissing || options.errorOnExtra || options.errorOnRegex) {
145
- const schema = load_environment_file_default(options.schema, options.encoding, options.silent);
146
- const schemaKeys = Object.keys(schema);
171
+ let schemaKeys = null;
172
+ if (options.errorOnMissing || options.errorOnExtra || options.errorOnRegex || options.returnSchemaOnly) {
173
+ const baseSchema = load_environment_file_default(
174
+ options.schema,
175
+ options.encoding,
176
+ options.silent,
177
+ options.errorOnMissingFiles
178
+ );
179
+ const schema = normalizeLayeredFiles(options.schemaExtends).reduce(
180
+ (acc, schemaPath) => {
181
+ const schemaLayer = load_environment_file_default(
182
+ schemaPath,
183
+ options.encoding,
184
+ options.silent,
185
+ options.errorOnMissingFiles
186
+ );
187
+ return Object.assign(acc, schemaLayer);
188
+ },
189
+ Object.assign({}, baseSchema)
190
+ );
191
+ schemaKeys = Object.keys(schema);
147
192
  let missingKeys = schemaKeys.filter(function(key) {
148
193
  return configKeys.indexOf(key) < 0;
149
194
  });
@@ -175,6 +220,14 @@ var config = (options) => {
175
220
  configData[configKeys[i]] = process.env[configKeys[i]];
176
221
  }
177
222
  }
223
+ if (options.returnSchemaOnly && schemaKeys) {
224
+ configData = schemaKeys.reduce((acc, key) => {
225
+ if (typeof config2[key] !== "undefined") {
226
+ acc[key] = config2[key];
227
+ }
228
+ return acc;
229
+ }, {});
230
+ }
178
231
  if (options.assignToProcessEnv) {
179
232
  if (options.overrideProcessEnv) {
180
233
  Object.assign(process.env, configData);
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "dotenv-extended",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "A module for loading .env files and optionally loading defaults and a schema for validating all values are present.",
5
5
  "repository": "git@github.com:keithmorris/node-dotenv-extended.git",
6
6
  "main": "lib/index.js",
7
7
  "types": "dotenv-extended.d.ts",
8
- "bin": "lib/bin/cli.js",
8
+ "bin": {
9
+ "dotenv-extended": "lib/bin/cli.js",
10
+ "dee": "lib/bin/cli.js"
11
+ },
9
12
  "exports": {
10
13
  ".": {
11
14
  "require": "./lib/index.js",