@karmaniverous/get-dotenv 5.2.6 → 6.0.0-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 +63 -67
- package/dist/cliHost.cjs +474 -271
- package/dist/cliHost.d.cts +45 -3
- package/dist/cliHost.d.mts +45 -3
- package/dist/cliHost.d.ts +45 -3
- package/dist/cliHost.mjs +474 -271
- package/dist/getdotenv.cli.mjs +713 -499
- package/dist/index.cjs +629 -692
- package/dist/index.d.cts +1 -140
- package/dist/index.d.mts +1 -140
- package/dist/index.d.ts +1 -140
- package/dist/index.mjs +630 -692
- package/dist/plugins-aws.cjs +0 -1
- package/dist/plugins-aws.mjs +0 -1
- package/dist/plugins-batch.cjs +53 -11
- package/dist/plugins-batch.d.cts +2 -1
- package/dist/plugins-batch.d.mts +2 -1
- package/dist/plugins-batch.d.ts +2 -1
- package/dist/plugins-batch.mjs +53 -11
- package/dist/plugins-cmd.cjs +3 -4
- package/dist/plugins-cmd.mjs +3 -4
- package/dist/plugins-demo.cjs +52 -7
- package/dist/plugins-demo.mjs +52 -7
- package/dist/plugins.cjs +66 -22
- package/dist/plugins.d.cts +2 -1
- package/dist/plugins.d.mts +2 -1
- package/dist/plugins.d.ts +2 -1
- package/dist/plugins.mjs +66 -22
- package/package.json +4 -2
package/dist/cliHost.cjs
CHANGED
|
@@ -1,269 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var commander = require('commander');
|
|
4
3
|
var fs = require('fs-extra');
|
|
5
4
|
var packageDirectory = require('package-directory');
|
|
6
5
|
var path = require('path');
|
|
7
6
|
var url = require('url');
|
|
8
7
|
var YAML = require('yaml');
|
|
9
8
|
var zod = require('zod');
|
|
9
|
+
var commander = require('commander');
|
|
10
10
|
var nanoid = require('nanoid');
|
|
11
11
|
var dotenv = require('dotenv');
|
|
12
12
|
var crypto = require('crypto');
|
|
13
13
|
|
|
14
14
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
15
|
-
/**
|
|
16
|
-
* Dotenv expansion utilities.
|
|
17
|
-
*
|
|
18
|
-
* This module implements recursive expansion of environment-variable
|
|
19
|
-
* references in strings and records. It supports both whitespace and
|
|
20
|
-
* bracket syntaxes with optional defaults:
|
|
21
|
-
*
|
|
22
|
-
* - Whitespace: `$VAR[:default]`
|
|
23
|
-
* - Bracketed: `${VAR[:default]}`
|
|
24
|
-
*
|
|
25
|
-
* Escaped dollar signs (`\$`) are preserved.
|
|
26
|
-
* Unknown variables resolve to empty string unless a default is provided.
|
|
27
|
-
*/
|
|
28
|
-
/**
|
|
29
|
-
* Like String.prototype.search but returns the last index.
|
|
30
|
-
* @internal
|
|
31
|
-
*/
|
|
32
|
-
const searchLast = (str, rgx) => {
|
|
33
|
-
const matches = Array.from(str.matchAll(rgx));
|
|
34
|
-
return matches.length > 0 ? (matches.slice(-1)[0]?.index ?? -1) : -1;
|
|
35
|
-
};
|
|
36
|
-
const replaceMatch = (value, match, ref) => {
|
|
37
|
-
/**
|
|
38
|
-
* @internal
|
|
39
|
-
*/
|
|
40
|
-
const group = match[0];
|
|
41
|
-
const key = match[1];
|
|
42
|
-
const defaultValue = match[2];
|
|
43
|
-
if (!key)
|
|
44
|
-
return value;
|
|
45
|
-
const replacement = value.replace(group, ref[key] ?? defaultValue ?? '');
|
|
46
|
-
return interpolate(replacement, ref);
|
|
47
|
-
};
|
|
48
|
-
const interpolate = (value = '', ref = {}) => {
|
|
49
|
-
/**
|
|
50
|
-
* @internal
|
|
51
|
-
*/
|
|
52
|
-
// if value is falsy, return it as is
|
|
53
|
-
if (!value)
|
|
54
|
-
return value;
|
|
55
|
-
// get position of last unescaped dollar sign
|
|
56
|
-
const lastUnescapedDollarSignIndex = searchLast(value, /(?!(?<=\\))\$/g);
|
|
57
|
-
// return value if none found
|
|
58
|
-
if (lastUnescapedDollarSignIndex === -1)
|
|
59
|
-
return value;
|
|
60
|
-
// evaluate the value tail
|
|
61
|
-
const tail = value.slice(lastUnescapedDollarSignIndex);
|
|
62
|
-
// find whitespace pattern: $KEY:DEFAULT
|
|
63
|
-
const whitespacePattern = /^\$([\w]+)(?::([^\s]*))?/;
|
|
64
|
-
const whitespaceMatch = whitespacePattern.exec(tail);
|
|
65
|
-
if (whitespaceMatch != null)
|
|
66
|
-
return replaceMatch(value, whitespaceMatch, ref);
|
|
67
|
-
else {
|
|
68
|
-
// find bracket pattern: ${KEY:DEFAULT}
|
|
69
|
-
const bracketPattern = /^\${([\w]+)(?::([^}]*))?}/;
|
|
70
|
-
const bracketMatch = bracketPattern.exec(tail);
|
|
71
|
-
if (bracketMatch != null)
|
|
72
|
-
return replaceMatch(value, bracketMatch, ref);
|
|
73
|
-
}
|
|
74
|
-
return value;
|
|
75
|
-
};
|
|
76
|
-
/**
|
|
77
|
-
* Recursively expands environment variables in a string. Variables may be
|
|
78
|
-
* presented with optional default as `$VAR[:default]` or `${VAR[:default]}`.
|
|
79
|
-
* Unknown variables will expand to an empty string.
|
|
80
|
-
*
|
|
81
|
-
* @param value - The string to expand.
|
|
82
|
-
* @param ref - The reference object to use for variable expansion.
|
|
83
|
-
* @returns The expanded string.
|
|
84
|
-
*
|
|
85
|
-
* @example
|
|
86
|
-
* ```ts
|
|
87
|
-
* process.env.FOO = 'bar';
|
|
88
|
-
* dotenvExpand('Hello $FOO'); // "Hello bar"
|
|
89
|
-
* dotenvExpand('Hello $BAZ:world'); // "Hello world"
|
|
90
|
-
* ```
|
|
91
|
-
*
|
|
92
|
-
* @remarks
|
|
93
|
-
* The expansion is recursive. If a referenced variable itself contains
|
|
94
|
-
* references, those will also be expanded until a stable value is reached.
|
|
95
|
-
* Escaped references (e.g. `\$FOO`) are preserved as literals.
|
|
96
|
-
*/
|
|
97
|
-
const dotenvExpand = (value, ref = process.env) => {
|
|
98
|
-
const result = interpolate(value, ref);
|
|
99
|
-
return result ? result.replace(/\\\$/g, '$') : undefined;
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
102
|
-
* Recursively expands environment variables in the values of a JSON object.
|
|
103
|
-
* Variables may be presented with optional default as `$VAR[:default]` or
|
|
104
|
-
* `${VAR[:default]}`. Unknown variables will expand to an empty string.
|
|
105
|
-
*
|
|
106
|
-
* @param values - The values object to expand.
|
|
107
|
-
* @param options - Expansion options.
|
|
108
|
-
* @returns The value object with expanded string values.
|
|
109
|
-
*
|
|
110
|
-
* @example
|
|
111
|
-
* ```ts
|
|
112
|
-
* process.env.FOO = 'bar';
|
|
113
|
-
* dotenvExpandAll({ A: '$FOO', B: 'x${FOO}y' });
|
|
114
|
-
* // => { A: "bar", B: "xbary" }
|
|
115
|
-
* ```
|
|
116
|
-
*
|
|
117
|
-
* @remarks
|
|
118
|
-
* Options:
|
|
119
|
-
* - ref: The reference object to use for expansion (defaults to process.env).
|
|
120
|
-
* - progressive: Whether to progressively add expanded values to the set of
|
|
121
|
-
* reference keys.
|
|
122
|
-
*
|
|
123
|
-
* When `progressive` is true, each expanded key becomes available for
|
|
124
|
-
* subsequent expansions in the same object (left-to-right by object key order).
|
|
125
|
-
*/
|
|
126
|
-
const dotenvExpandAll = (values = {}, options = {}) => Object.keys(values).reduce((acc, key) => {
|
|
127
|
-
const { ref = process.env, progressive = false } = options;
|
|
128
|
-
acc[key] = dotenvExpand(values[key], {
|
|
129
|
-
...ref,
|
|
130
|
-
...(progressive ? acc : {}),
|
|
131
|
-
});
|
|
132
|
-
return acc;
|
|
133
|
-
}, {});
|
|
134
|
-
/**
|
|
135
|
-
* Recursively expands environment variables in a string using `process.env` as
|
|
136
|
-
* the expansion reference. Variables may be presented with optional default as
|
|
137
|
-
* `$VAR[:default]` or `${VAR[:default]}`. Unknown variables will expand to an
|
|
138
|
-
* empty string.
|
|
139
|
-
*
|
|
140
|
-
* @param value - The string to expand.
|
|
141
|
-
* @returns The expanded string.
|
|
142
|
-
*
|
|
143
|
-
* @example
|
|
144
|
-
* ```ts
|
|
145
|
-
* process.env.FOO = 'bar';
|
|
146
|
-
* dotenvExpandFromProcessEnv('Hello $FOO'); // "Hello bar"
|
|
147
|
-
* ```
|
|
148
|
-
*/
|
|
149
|
-
const dotenvExpandFromProcessEnv = (value) => dotenvExpand(value, process.env);
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Attach legacy root flags to a Commander program.
|
|
153
|
-
* Uses provided defaults to render help labels without coupling to generators.
|
|
154
|
-
*/
|
|
155
|
-
const attachRootOptions = (program, defaults, opts) => {
|
|
156
|
-
// Install temporary wrappers to tag all options added here as "base".
|
|
157
|
-
const GROUP = 'base';
|
|
158
|
-
const tagLatest = (cmd, group) => {
|
|
159
|
-
const optsArr = cmd.options;
|
|
160
|
-
if (Array.isArray(optsArr) && optsArr.length > 0) {
|
|
161
|
-
const last = optsArr[optsArr.length - 1];
|
|
162
|
-
last.__group = group;
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
const originalAddOption = program.addOption.bind(program);
|
|
166
|
-
const originalOption = program.option.bind(program);
|
|
167
|
-
program.addOption = function patchedAdd(opt) {
|
|
168
|
-
// Tag before adding, in case consumers inspect the Option directly.
|
|
169
|
-
opt.__group = GROUP;
|
|
170
|
-
const ret = originalAddOption(opt);
|
|
171
|
-
return ret;
|
|
172
|
-
};
|
|
173
|
-
program.option = function patchedOption(...args) {
|
|
174
|
-
const ret = originalOption(...args);
|
|
175
|
-
tagLatest(this, GROUP);
|
|
176
|
-
return ret;
|
|
177
|
-
};
|
|
178
|
-
const { defaultEnv, dotenvToken, dynamicPath, env, excludeDynamic, excludeEnv, excludeGlobal, excludePrivate, excludePublic, loadProcess, log, outputPath, paths, pathsDelimiter, pathsDelimiterPattern, privateToken, scripts, shell, varsAssignor, varsAssignorPattern, varsDelimiter, varsDelimiterPattern, } = defaults ?? {};
|
|
179
|
-
const va = typeof defaults?.varsAssignor === 'string' ? defaults.varsAssignor : '=';
|
|
180
|
-
const vd = typeof defaults?.varsDelimiter === 'string' ? defaults.varsDelimiter : ' ';
|
|
181
|
-
// Build initial chain.
|
|
182
|
-
let p = program
|
|
183
|
-
.enablePositionalOptions()
|
|
184
|
-
.passThroughOptions()
|
|
185
|
-
.option('-e, --env <string>', `target environment (dotenv-expanded)`, dotenvExpandFromProcessEnv, env);
|
|
186
|
-
p = p.option('-v, --vars <string>', `extra variables expressed as delimited key-value pairs (dotenv-expanded): ${[
|
|
187
|
-
['KEY1', 'VAL1'],
|
|
188
|
-
['KEY2', 'VAL2'],
|
|
189
|
-
]
|
|
190
|
-
.map((v) => v.join(va))
|
|
191
|
-
.join(vd)}`, dotenvExpandFromProcessEnv);
|
|
192
|
-
// Optional legacy root command flag (kept for generated CLI compatibility).
|
|
193
|
-
// Default is OFF; the generator opts in explicitly.
|
|
194
|
-
if (opts?.includeCommandOption === true) {
|
|
195
|
-
p = p.option('-c, --command <string>', 'command executed according to the --shell option, conflicts with cmd subcommand (dotenv-expanded)', dotenvExpandFromProcessEnv);
|
|
196
|
-
}
|
|
197
|
-
p = p
|
|
198
|
-
.option('-o, --output-path <string>', 'consolidated output file (dotenv-expanded)', dotenvExpandFromProcessEnv, outputPath)
|
|
199
|
-
.addOption(new commander.Option('-s, --shell [string]', (() => {
|
|
200
|
-
let defaultLabel = '';
|
|
201
|
-
if (shell !== undefined) {
|
|
202
|
-
if (typeof shell === 'boolean') {
|
|
203
|
-
defaultLabel = ' (default OS shell)';
|
|
204
|
-
}
|
|
205
|
-
else if (typeof shell === 'string') {
|
|
206
|
-
// Safe string interpolation
|
|
207
|
-
defaultLabel = ` (default ${shell})`;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return `command execution shell, no argument for default OS shell or provide shell string${defaultLabel}`;
|
|
211
|
-
})()).conflicts('shellOff'))
|
|
212
|
-
.addOption(new commander.Option('-S, --shell-off', `command execution shell OFF${!shell ? ' (default)' : ''}`).conflicts('shell'))
|
|
213
|
-
.addOption(new commander.Option('-p, --load-process', `load variables to process.env ON${loadProcess ? ' (default)' : ''}`).conflicts('loadProcessOff'))
|
|
214
|
-
.addOption(new commander.Option('-P, --load-process-off', `load variables to process.env OFF${!loadProcess ? ' (default)' : ''}`).conflicts('loadProcess'))
|
|
215
|
-
.addOption(new commander.Option('-a, --exclude-all', `exclude all dotenv variables from loading ON${excludeDynamic &&
|
|
216
|
-
((excludeEnv && excludeGlobal) || (excludePrivate && excludePublic))
|
|
217
|
-
? ' (default)'
|
|
218
|
-
: ''}`).conflicts('excludeAllOff'))
|
|
219
|
-
.addOption(new commander.Option('-A, --exclude-all-off', `exclude all dotenv variables from loading OFF (default)`).conflicts('excludeAll'))
|
|
220
|
-
.addOption(new commander.Option('-z, --exclude-dynamic', `exclude dynamic dotenv variables from loading ON${excludeDynamic ? ' (default)' : ''}`).conflicts('excludeDynamicOff'))
|
|
221
|
-
.addOption(new commander.Option('-Z, --exclude-dynamic-off', `exclude dynamic dotenv variables from loading OFF${!excludeDynamic ? ' (default)' : ''}`).conflicts('excludeDynamic'))
|
|
222
|
-
.addOption(new commander.Option('-n, --exclude-env', `exclude environment-specific dotenv variables from loading${excludeEnv ? ' (default)' : ''}`).conflicts('excludeEnvOff'))
|
|
223
|
-
.addOption(new commander.Option('-N, --exclude-env-off', `exclude environment-specific dotenv variables from loading OFF${!excludeEnv ? ' (default)' : ''}`).conflicts('excludeEnv'))
|
|
224
|
-
.addOption(new commander.Option('-g, --exclude-global', `exclude global dotenv variables from loading ON${excludeGlobal ? ' (default)' : ''}`).conflicts('excludeGlobalOff'))
|
|
225
|
-
.addOption(new commander.Option('-G, --exclude-global-off', `exclude global dotenv variables from loading OFF${!excludeGlobal ? ' (default)' : ''}`).conflicts('excludeGlobal'))
|
|
226
|
-
.addOption(new commander.Option('-r, --exclude-private', `exclude private dotenv variables from loading ON${excludePrivate ? ' (default)' : ''}`).conflicts('excludePrivateOff'))
|
|
227
|
-
.addOption(new commander.Option('-R, --exclude-private-off', `exclude private dotenv variables from loading OFF${!excludePrivate ? ' (default)' : ''}`).conflicts('excludePrivate'))
|
|
228
|
-
.addOption(new commander.Option('-u, --exclude-public', `exclude public dotenv variables from loading ON${excludePublic ? ' (default)' : ''}`).conflicts('excludePublicOff'))
|
|
229
|
-
.addOption(new commander.Option('-U, --exclude-public-off', `exclude public dotenv variables from loading OFF${!excludePublic ? ' (default)' : ''}`).conflicts('excludePublic'))
|
|
230
|
-
.addOption(new commander.Option('-l, --log', `console log loaded variables ON${log ? ' (default)' : ''}`).conflicts('logOff'))
|
|
231
|
-
.addOption(new commander.Option('-L, --log-off', `console log loaded variables OFF${!log ? ' (default)' : ''}`).conflicts('log'))
|
|
232
|
-
.option('--capture', 'capture child process stdio for commands (tests/CI)')
|
|
233
|
-
.option('--redact', 'mask secret-like values in logs/trace (presentation-only)')
|
|
234
|
-
.option('--default-env <string>', 'default target environment', dotenvExpandFromProcessEnv, defaultEnv)
|
|
235
|
-
.option('--dotenv-token <string>', 'dotenv-expanded token indicating a dotenv file', dotenvExpandFromProcessEnv, dotenvToken)
|
|
236
|
-
.option('--dynamic-path <string>', 'dynamic variables path (.js or .ts; .ts is auto-compiled when esbuild is available, otherwise precompile)', dotenvExpandFromProcessEnv, dynamicPath)
|
|
237
|
-
.option('--paths <string>', 'dotenv-expanded delimited list of paths to dotenv directory', dotenvExpandFromProcessEnv, paths)
|
|
238
|
-
.option('--paths-delimiter <string>', 'paths delimiter string', pathsDelimiter)
|
|
239
|
-
.option('--paths-delimiter-pattern <string>', 'paths delimiter regex pattern', pathsDelimiterPattern)
|
|
240
|
-
.option('--private-token <string>', 'dotenv-expanded token indicating private variables', dotenvExpandFromProcessEnv, privateToken)
|
|
241
|
-
.option('--vars-delimiter <string>', 'vars delimiter string', varsDelimiter)
|
|
242
|
-
.option('--vars-delimiter-pattern <string>', 'vars delimiter regex pattern', varsDelimiterPattern)
|
|
243
|
-
.option('--vars-assignor <string>', 'vars assignment operator string', varsAssignor)
|
|
244
|
-
.option('--vars-assignor-pattern <string>', 'vars assignment operator regex pattern', varsAssignorPattern)
|
|
245
|
-
// Hidden scripts pipe-through (stringified)
|
|
246
|
-
.addOption(new commander.Option('--scripts <string>')
|
|
247
|
-
.default(JSON.stringify(scripts))
|
|
248
|
-
.hideHelp());
|
|
249
|
-
// Diagnostics: opt-in tracing; optional variadic keys after the flag.
|
|
250
|
-
p = p.option('--trace [keys...]', 'emit diagnostics for child env composition (optional keys)');
|
|
251
|
-
// Validation: strict mode fails on env validation issues (warn by default).
|
|
252
|
-
p = p.option('--strict', 'fail on env validation errors (schema/requiredKeys)');
|
|
253
|
-
// Entropy diagnostics (presentation-only)
|
|
254
|
-
p = p
|
|
255
|
-
.addOption(new commander.Option('--entropy-warn', 'enable entropy warnings (default on)').conflicts('entropyWarnOff'))
|
|
256
|
-
.addOption(new commander.Option('--entropy-warn-off', 'disable entropy warnings').conflicts('entropyWarn'))
|
|
257
|
-
.option('--entropy-threshold <number>', 'entropy bits/char threshold (default 3.8)')
|
|
258
|
-
.option('--entropy-min-length <number>', 'min length to examine for entropy (default 16)')
|
|
259
|
-
.option('--entropy-whitelist <pattern...>', 'suppress entropy warnings when key matches any regex pattern')
|
|
260
|
-
.option('--redact-pattern <pattern...>', 'additional key-match regex patterns to trigger redaction');
|
|
261
|
-
// Restore original methods to avoid tagging future additions outside base.
|
|
262
|
-
program.addOption = originalAddOption;
|
|
263
|
-
program.option = originalOption;
|
|
264
|
-
return p;
|
|
265
|
-
};
|
|
266
|
-
|
|
267
15
|
// Base root CLI defaults (shared; kept untyped here to avoid cross-layer deps).
|
|
268
16
|
const baseRootOptionDefaults = {
|
|
269
17
|
dotenvToken: '.env',
|
|
@@ -833,9 +581,21 @@ const resolveGetDotenvOptions = async (customOptions) => {
|
|
|
833
581
|
const localOptionsPath = localPkgDir
|
|
834
582
|
? path.join(localPkgDir, getDotenvOptionsFilename)
|
|
835
583
|
: undefined;
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
584
|
+
// Safely read local CLI-facing defaults (defensive typing to satisfy strict linting).
|
|
585
|
+
let localOptions = {};
|
|
586
|
+
if (localOptionsPath && (await fs.exists(localOptionsPath))) {
|
|
587
|
+
try {
|
|
588
|
+
const txt = await fs.readFile(localOptionsPath, 'utf-8');
|
|
589
|
+
const parsed = JSON.parse(txt);
|
|
590
|
+
if (parsed && typeof parsed === 'object') {
|
|
591
|
+
localOptions = parsed;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
catch {
|
|
595
|
+
// Malformed or unreadable local options are treated as absent.
|
|
596
|
+
localOptions = {};
|
|
597
|
+
}
|
|
598
|
+
}
|
|
839
599
|
// Merge order: base < local < custom (custom has highest precedence)
|
|
840
600
|
const mergedCli = defaultsDeep(baseGetDotenvCliOptions, localOptions);
|
|
841
601
|
const defaultsFromCli = getDotenvCliOptions2Options(mergedCli);
|
|
@@ -846,6 +606,316 @@ const resolveGetDotenvOptions = async (customOptions) => {
|
|
|
846
606
|
};
|
|
847
607
|
};
|
|
848
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Dotenv expansion utilities.
|
|
611
|
+
*
|
|
612
|
+
* This module implements recursive expansion of environment-variable
|
|
613
|
+
* references in strings and records. It supports both whitespace and
|
|
614
|
+
* bracket syntaxes with optional defaults:
|
|
615
|
+
*
|
|
616
|
+
* - Whitespace: `$VAR[:default]`
|
|
617
|
+
* - Bracketed: `${VAR[:default]}`
|
|
618
|
+
*
|
|
619
|
+
* Escaped dollar signs (`\$`) are preserved.
|
|
620
|
+
* Unknown variables resolve to empty string unless a default is provided.
|
|
621
|
+
*/
|
|
622
|
+
/**
|
|
623
|
+
* Like String.prototype.search but returns the last index.
|
|
624
|
+
* @internal
|
|
625
|
+
*/
|
|
626
|
+
const searchLast = (str, rgx) => {
|
|
627
|
+
const matches = Array.from(str.matchAll(rgx));
|
|
628
|
+
return matches.length > 0 ? (matches.slice(-1)[0]?.index ?? -1) : -1;
|
|
629
|
+
};
|
|
630
|
+
const replaceMatch = (value, match, ref) => {
|
|
631
|
+
/**
|
|
632
|
+
* @internal
|
|
633
|
+
*/
|
|
634
|
+
const group = match[0];
|
|
635
|
+
const key = match[1];
|
|
636
|
+
const defaultValue = match[2];
|
|
637
|
+
if (!key)
|
|
638
|
+
return value;
|
|
639
|
+
const replacement = value.replace(group, ref[key] ?? defaultValue ?? '');
|
|
640
|
+
return interpolate(replacement, ref);
|
|
641
|
+
};
|
|
642
|
+
const interpolate = (value = '', ref = {}) => {
|
|
643
|
+
/**
|
|
644
|
+
* @internal
|
|
645
|
+
*/
|
|
646
|
+
// if value is falsy, return it as is
|
|
647
|
+
if (!value)
|
|
648
|
+
return value;
|
|
649
|
+
// get position of last unescaped dollar sign
|
|
650
|
+
const lastUnescapedDollarSignIndex = searchLast(value, /(?!(?<=\\))\$/g);
|
|
651
|
+
// return value if none found
|
|
652
|
+
if (lastUnescapedDollarSignIndex === -1)
|
|
653
|
+
return value;
|
|
654
|
+
// evaluate the value tail
|
|
655
|
+
const tail = value.slice(lastUnescapedDollarSignIndex);
|
|
656
|
+
// find whitespace pattern: $KEY:DEFAULT
|
|
657
|
+
const whitespacePattern = /^\$([\w]+)(?::([^\s]*))?/;
|
|
658
|
+
const whitespaceMatch = whitespacePattern.exec(tail);
|
|
659
|
+
if (whitespaceMatch != null)
|
|
660
|
+
return replaceMatch(value, whitespaceMatch, ref);
|
|
661
|
+
else {
|
|
662
|
+
// find bracket pattern: ${KEY:DEFAULT}
|
|
663
|
+
const bracketPattern = /^\${([\w]+)(?::([^}]*))?}/;
|
|
664
|
+
const bracketMatch = bracketPattern.exec(tail);
|
|
665
|
+
if (bracketMatch != null)
|
|
666
|
+
return replaceMatch(value, bracketMatch, ref);
|
|
667
|
+
}
|
|
668
|
+
return value;
|
|
669
|
+
};
|
|
670
|
+
/**
|
|
671
|
+
* Recursively expands environment variables in a string. Variables may be
|
|
672
|
+
* presented with optional default as `$VAR[:default]` or `${VAR[:default]}`.
|
|
673
|
+
* Unknown variables will expand to an empty string.
|
|
674
|
+
*
|
|
675
|
+
* @param value - The string to expand.
|
|
676
|
+
* @param ref - The reference object to use for variable expansion.
|
|
677
|
+
* @returns The expanded string.
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```ts
|
|
681
|
+
* process.env.FOO = 'bar';
|
|
682
|
+
* dotenvExpand('Hello $FOO'); // "Hello bar"
|
|
683
|
+
* dotenvExpand('Hello $BAZ:world'); // "Hello world"
|
|
684
|
+
* ```
|
|
685
|
+
*
|
|
686
|
+
* @remarks
|
|
687
|
+
* The expansion is recursive. If a referenced variable itself contains
|
|
688
|
+
* references, those will also be expanded until a stable value is reached.
|
|
689
|
+
* Escaped references (e.g. `\$FOO`) are preserved as literals.
|
|
690
|
+
*/
|
|
691
|
+
const dotenvExpand = (value, ref = process.env) => {
|
|
692
|
+
const result = interpolate(value, ref);
|
|
693
|
+
return result ? result.replace(/\\\$/g, '$') : undefined;
|
|
694
|
+
};
|
|
695
|
+
/**
|
|
696
|
+
* Recursively expands environment variables in the values of a JSON object.
|
|
697
|
+
* Variables may be presented with optional default as `$VAR[:default]` or
|
|
698
|
+
* `${VAR[:default]}`. Unknown variables will expand to an empty string.
|
|
699
|
+
*
|
|
700
|
+
* @param values - The values object to expand.
|
|
701
|
+
* @param options - Expansion options.
|
|
702
|
+
* @returns The value object with expanded string values.
|
|
703
|
+
*
|
|
704
|
+
* @example
|
|
705
|
+
* ```ts
|
|
706
|
+
* process.env.FOO = 'bar';
|
|
707
|
+
* dotenvExpandAll({ A: '$FOO', B: 'x${FOO}y' });
|
|
708
|
+
* // => { A: "bar", B: "xbary" }
|
|
709
|
+
* ```
|
|
710
|
+
*
|
|
711
|
+
* @remarks
|
|
712
|
+
* Options:
|
|
713
|
+
* - ref: The reference object to use for expansion (defaults to process.env).
|
|
714
|
+
* - progressive: Whether to progressively add expanded values to the set of
|
|
715
|
+
* reference keys.
|
|
716
|
+
*
|
|
717
|
+
* When `progressive` is true, each expanded key becomes available for
|
|
718
|
+
* subsequent expansions in the same object (left-to-right by object key order).
|
|
719
|
+
*/
|
|
720
|
+
const dotenvExpandAll = (values = {}, options = {}) => Object.keys(values).reduce((acc, key) => {
|
|
721
|
+
const { ref = process.env, progressive = false } = options;
|
|
722
|
+
acc[key] = dotenvExpand(values[key], {
|
|
723
|
+
...ref,
|
|
724
|
+
...(progressive ? acc : {}),
|
|
725
|
+
});
|
|
726
|
+
return acc;
|
|
727
|
+
}, {});
|
|
728
|
+
/**
|
|
729
|
+
* Recursively expands environment variables in a string using `process.env` as
|
|
730
|
+
* the expansion reference. Variables may be presented with optional default as
|
|
731
|
+
* `$VAR[:default]` or `${VAR[:default]}`. Unknown variables will expand to an
|
|
732
|
+
* empty string.
|
|
733
|
+
*
|
|
734
|
+
* @param value - The string to expand.
|
|
735
|
+
* @returns The expanded string.
|
|
736
|
+
*
|
|
737
|
+
* @example
|
|
738
|
+
* ```ts
|
|
739
|
+
* process.env.FOO = 'bar';
|
|
740
|
+
* dotenvExpandFromProcessEnv('Hello $FOO'); // "Hello bar"
|
|
741
|
+
* ```
|
|
742
|
+
*/
|
|
743
|
+
const dotenvExpandFromProcessEnv = (value) => dotenvExpand(value, process.env);
|
|
744
|
+
|
|
745
|
+
/* eslint-disable @typescript-eslint/no-deprecated */
|
|
746
|
+
/**
|
|
747
|
+
* Attach root flags to a GetDotenvCli instance.
|
|
748
|
+
* - Host-only: program is typed as GetDotenvCli and supports dynamicOption/createDynamicOption.
|
|
749
|
+
* - Any flag that displays an effective default in help uses dynamic descriptions.
|
|
750
|
+
*/
|
|
751
|
+
const attachRootOptions = (program, defaults, opts) => {
|
|
752
|
+
// Install temporary wrappers to tag all options added here as "base" for grouped help.
|
|
753
|
+
const GROUP = 'base';
|
|
754
|
+
const tagLatest = (cmd, group) => {
|
|
755
|
+
const optsArr = cmd.options;
|
|
756
|
+
if (Array.isArray(optsArr) && optsArr.length > 0) {
|
|
757
|
+
const last = optsArr[optsArr.length - 1];
|
|
758
|
+
last.__group = group;
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
const originalAddOption = program.addOption.bind(program);
|
|
762
|
+
const originalOption = program.option.bind(program);
|
|
763
|
+
program.addOption = function patchedAdd(opt) {
|
|
764
|
+
opt.__group = GROUP;
|
|
765
|
+
return originalAddOption(opt);
|
|
766
|
+
};
|
|
767
|
+
program.option = function patchedOption(...args) {
|
|
768
|
+
const ret = originalOption(...args);
|
|
769
|
+
tagLatest(this, GROUP);
|
|
770
|
+
return ret;
|
|
771
|
+
};
|
|
772
|
+
const { defaultEnv, dotenvToken, dynamicPath, env, outputPath, paths, pathsDelimiter, pathsDelimiterPattern, privateToken, scripts, varsAssignor, varsAssignorPattern, varsDelimiter, varsDelimiterPattern, } = defaults ?? {};
|
|
773
|
+
const va = typeof defaults?.varsAssignor === 'string' ? defaults.varsAssignor : '=';
|
|
774
|
+
const vd = typeof defaults?.varsDelimiter === 'string' ? defaults.varsDelimiter : ' ';
|
|
775
|
+
// Helper: append (default) tags for ON/OFF toggles
|
|
776
|
+
const onOff = (on, isDefault) => on
|
|
777
|
+
? `ON${isDefault ? ' (default)' : ''}`
|
|
778
|
+
: `OFF${isDefault ? ' (default)' : ''}`;
|
|
779
|
+
let p = program
|
|
780
|
+
.enablePositionalOptions()
|
|
781
|
+
.passThroughOptions()
|
|
782
|
+
.option('-e, --env <string>', `target environment (dotenv-expanded)`, dotenvExpandFromProcessEnv, env);
|
|
783
|
+
p = p.option('-v, --vars <string>', `extra variables expressed as delimited key-value pairs (dotenv-expanded): ${[
|
|
784
|
+
['KEY1', 'VAL1'],
|
|
785
|
+
['KEY2', 'VAL2'],
|
|
786
|
+
]
|
|
787
|
+
.map((v) => v.join(va))
|
|
788
|
+
.join(vd)}`, dotenvExpandFromProcessEnv);
|
|
789
|
+
if (opts?.includeCommandOption === true) {
|
|
790
|
+
p = p.option('-c, --command <string>', 'command executed according to the --shell option, conflicts with cmd subcommand (dotenv-expanded)', dotenvExpandFromProcessEnv);
|
|
791
|
+
}
|
|
792
|
+
// Output path (interpolated later; help can remain static)
|
|
793
|
+
p = p.option('-o, --output-path <string>', 'consolidated output file (dotenv-expanded)', dotenvExpandFromProcessEnv, outputPath);
|
|
794
|
+
// Shell ON (string or boolean true => default shell)
|
|
795
|
+
p = p
|
|
796
|
+
.addOption(program
|
|
797
|
+
.createDynamicOption('-s, --shell [string]', (cfg) => {
|
|
798
|
+
const s = cfg.shell;
|
|
799
|
+
let tag = '';
|
|
800
|
+
if (typeof s === 'boolean' && s)
|
|
801
|
+
tag = ' (default OS shell)';
|
|
802
|
+
else if (typeof s === 'string' && s.length > 0)
|
|
803
|
+
tag = ` (default ${s})`;
|
|
804
|
+
return `command execution shell, no argument for default OS shell or provide shell string${tag}`;
|
|
805
|
+
})
|
|
806
|
+
.conflicts('shellOff'))
|
|
807
|
+
// Shell OFF
|
|
808
|
+
.addOption(program
|
|
809
|
+
.createDynamicOption('-S, --shell-off', (cfg) => {
|
|
810
|
+
const s = cfg.shell;
|
|
811
|
+
return `command execution shell OFF${s === false ? ' (default)' : ''}`;
|
|
812
|
+
})
|
|
813
|
+
.conflicts('shell'));
|
|
814
|
+
// Load process ON/OFF (dynamic defaults)
|
|
815
|
+
p = p
|
|
816
|
+
.addOption(program
|
|
817
|
+
.createDynamicOption('-p, --load-process', (cfg) => `load variables to process.env ${onOff(true, Boolean(cfg.loadProcess))}`)
|
|
818
|
+
.conflicts('loadProcessOff'))
|
|
819
|
+
.addOption(program
|
|
820
|
+
.createDynamicOption('-P, --load-process-off', (cfg) => `load variables to process.env ${onOff(false, !cfg.loadProcess)}`)
|
|
821
|
+
.conflicts('loadProcess'));
|
|
822
|
+
// Exclusion master toggle (dynamic)
|
|
823
|
+
p = p
|
|
824
|
+
.addOption(program
|
|
825
|
+
.createDynamicOption('-a, --exclude-all', (cfg) => {
|
|
826
|
+
const c = cfg;
|
|
827
|
+
const allOn = !!c.excludeDynamic &&
|
|
828
|
+
((!!c.excludeEnv && !!c.excludeGlobal) ||
|
|
829
|
+
(!!c.excludePrivate && !!c.excludePublic));
|
|
830
|
+
const suffix = allOn ? ' (default)' : '';
|
|
831
|
+
return `exclude all dotenv variables from loading ON${suffix}`;
|
|
832
|
+
})
|
|
833
|
+
.conflicts('excludeAllOff'))
|
|
834
|
+
.addOption(new commander.Option('-A, --exclude-all-off', `exclude all dotenv variables from loading OFF (default)`).conflicts('excludeAll'));
|
|
835
|
+
// Per-family exclusions (dynamic defaults)
|
|
836
|
+
p = p
|
|
837
|
+
.addOption(program
|
|
838
|
+
.createDynamicOption('-z, --exclude-dynamic', (cfg) => `exclude dynamic dotenv variables from loading ${onOff(true, Boolean(cfg.excludeDynamic))}`)
|
|
839
|
+
.conflicts('excludeDynamicOff'))
|
|
840
|
+
.addOption(program
|
|
841
|
+
.createDynamicOption('-Z, --exclude-dynamic-off', (cfg) => `exclude dynamic dotenv variables from loading ${onOff(false, !cfg.excludeDynamic)}`)
|
|
842
|
+
.conflicts('excludeDynamic'))
|
|
843
|
+
.addOption(program
|
|
844
|
+
.createDynamicOption('-n, --exclude-env', (cfg) => `exclude environment-specific dotenv variables from loading ${onOff(true, Boolean(cfg.excludeEnv))}`)
|
|
845
|
+
.conflicts('excludeEnvOff'))
|
|
846
|
+
.addOption(program
|
|
847
|
+
.createDynamicOption('-N, --exclude-env-off', (cfg) => `exclude environment-specific dotenv variables from loading ${onOff(false, !cfg.excludeEnv)}`)
|
|
848
|
+
.conflicts('excludeEnv'))
|
|
849
|
+
.addOption(program
|
|
850
|
+
.createDynamicOption('-g, --exclude-global', (cfg) => `exclude global dotenv variables from loading ${onOff(true, Boolean(cfg.excludeGlobal))}`)
|
|
851
|
+
.conflicts('excludeGlobalOff'))
|
|
852
|
+
.addOption(program
|
|
853
|
+
.createDynamicOption('-G, --exclude-global-off', (cfg) => `exclude global dotenv variables from loading ${onOff(false, !cfg.excludeGlobal)}`)
|
|
854
|
+
.conflicts('excludeGlobal'))
|
|
855
|
+
.addOption(program
|
|
856
|
+
.createDynamicOption('-r, --exclude-private', (cfg) => `exclude private dotenv variables from loading ${onOff(true, Boolean(cfg.excludePrivate))}`)
|
|
857
|
+
.conflicts('excludePrivateOff'))
|
|
858
|
+
.addOption(program
|
|
859
|
+
.createDynamicOption('-R, --exclude-private-off', (cfg) => `exclude private dotenv variables from loading ${onOff(false, !cfg.excludePrivate)}`)
|
|
860
|
+
.conflicts('excludePrivate'))
|
|
861
|
+
.addOption(program
|
|
862
|
+
.createDynamicOption('-u, --exclude-public', (cfg) => `exclude public dotenv variables from loading ${onOff(true, Boolean(cfg.excludePublic))}`)
|
|
863
|
+
.conflicts('excludePublicOff'))
|
|
864
|
+
.addOption(program
|
|
865
|
+
.createDynamicOption('-U, --exclude-public-off', (cfg) => `exclude public dotenv variables from loading ${onOff(false, !cfg.excludePublic)}`)
|
|
866
|
+
.conflicts('excludePublic'));
|
|
867
|
+
// Log ON/OFF (dynamic)
|
|
868
|
+
p = p
|
|
869
|
+
.addOption(program
|
|
870
|
+
.createDynamicOption('-l, --log', (cfg) => `console log loaded variables ${onOff(true, Boolean(cfg.log))}`)
|
|
871
|
+
.conflicts('logOff'))
|
|
872
|
+
.addOption(program
|
|
873
|
+
.createDynamicOption('-L, --log-off', (cfg) => `console log loaded variables ${onOff(false, !cfg.log)}`)
|
|
874
|
+
.conflicts('log'));
|
|
875
|
+
// Capture flag (no default display; static)
|
|
876
|
+
p = p.option('--capture', 'capture child process stdio for commands (tests/CI)');
|
|
877
|
+
// Core bootstrap/static flags (kept static in help)
|
|
878
|
+
p = p
|
|
879
|
+
.option('--default-env <string>', 'default target environment', dotenvExpandFromProcessEnv, defaultEnv)
|
|
880
|
+
.option('--dotenv-token <string>', 'dotenv-expanded token indicating a dotenv file', dotenvExpandFromProcessEnv, dotenvToken)
|
|
881
|
+
.option('--dynamic-path <string>', 'dynamic variables path (.js or .ts; .ts is auto-compiled when esbuild is available, otherwise precompile)', dotenvExpandFromProcessEnv, dynamicPath)
|
|
882
|
+
.option('--paths <string>', 'dotenv-expanded delimited list of paths to dotenv directory', dotenvExpandFromProcessEnv, paths)
|
|
883
|
+
.option('--paths-delimiter <string>', 'paths delimiter string', pathsDelimiter)
|
|
884
|
+
.option('--paths-delimiter-pattern <string>', 'paths delimiter regex pattern', pathsDelimiterPattern)
|
|
885
|
+
.option('--private-token <string>', 'dotenv-expanded token indicating private variables', dotenvExpandFromProcessEnv, privateToken)
|
|
886
|
+
.option('--vars-delimiter <string>', 'vars delimiter string', varsDelimiter)
|
|
887
|
+
.option('--vars-delimiter-pattern <string>', 'vars delimiter regex pattern', varsDelimiterPattern)
|
|
888
|
+
.option('--vars-assignor <string>', 'vars assignment operator string', varsAssignor)
|
|
889
|
+
.option('--vars-assignor-pattern <string>', 'vars assignment operator regex pattern', varsAssignorPattern)
|
|
890
|
+
// Hidden scripts pipe-through (stringified)
|
|
891
|
+
.addOption(new commander.Option('--scripts <string>')
|
|
892
|
+
.default(JSON.stringify(scripts))
|
|
893
|
+
.hideHelp());
|
|
894
|
+
// Diagnostics / validation / entropy
|
|
895
|
+
p = p
|
|
896
|
+
.option('--trace [keys...]', 'emit diagnostics for child env composition (optional keys)')
|
|
897
|
+
.option('--strict', 'fail on env validation errors (schema/requiredKeys)');
|
|
898
|
+
p = p
|
|
899
|
+
.addOption(program
|
|
900
|
+
.createDynamicOption('--entropy-warn', (cfg) => {
|
|
901
|
+
const warn = cfg.warnEntropy;
|
|
902
|
+
// Default is effectively ON when warnEntropy is true or undefined.
|
|
903
|
+
return `enable entropy warnings${warn === false ? '' : ' (default on)'}`;
|
|
904
|
+
})
|
|
905
|
+
.conflicts('entropyWarnOff'))
|
|
906
|
+
.addOption(program
|
|
907
|
+
.createDynamicOption('--entropy-warn-off', (cfg) => `disable entropy warnings${cfg.warnEntropy === false ? ' (default)' : ''}`)
|
|
908
|
+
.conflicts('entropyWarn'))
|
|
909
|
+
.option('--entropy-threshold <number>', 'entropy bits/char threshold (default 3.8)')
|
|
910
|
+
.option('--entropy-min-length <number>', 'min length to examine for entropy (default 16)')
|
|
911
|
+
.option('--entropy-whitelist <pattern...>', 'suppress entropy warnings when key matches any regex pattern')
|
|
912
|
+
.option('--redact-pattern <pattern...>', 'additional key-match regex patterns to trigger redaction');
|
|
913
|
+
// Restore original methods
|
|
914
|
+
program.addOption = originalAddOption;
|
|
915
|
+
program.option = originalOption;
|
|
916
|
+
return p;
|
|
917
|
+
};
|
|
918
|
+
|
|
849
919
|
/**
|
|
850
920
|
* Zod schemas for programmatic GetDotenv options.
|
|
851
921
|
*
|
|
@@ -1458,6 +1528,8 @@ const computeContext = async (customOptions, plugins, hostMetaUrl) => {
|
|
|
1458
1528
|
};
|
|
1459
1529
|
};
|
|
1460
1530
|
|
|
1531
|
+
// Dynamic help support: attach a private symbol to Option for description fns.
|
|
1532
|
+
const DYN_DESC_SYM = Symbol('getdotenv.dynamic.description');
|
|
1461
1533
|
const HOST_META_URL = (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cliHost.cjs', document.baseURI).href));
|
|
1462
1534
|
const CTX_SYMBOL = Symbol('GetDotenvCli.ctx');
|
|
1463
1535
|
const OPTS_SYMBOL = Symbol('GetDotenvCli.options');
|
|
@@ -1480,6 +1552,13 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1480
1552
|
_installed = false;
|
|
1481
1553
|
/** Optional header line to prepend in help output */
|
|
1482
1554
|
[HELP_HEADER_SYMBOL];
|
|
1555
|
+
/**
|
|
1556
|
+
* Create a subcommand using the same subclass, preserving helpers like
|
|
1557
|
+
* dynamicOption on children.
|
|
1558
|
+
*/
|
|
1559
|
+
createCommand(name) {
|
|
1560
|
+
return new this.constructor(name);
|
|
1561
|
+
}
|
|
1483
1562
|
constructor(alias = 'getdotenv') {
|
|
1484
1563
|
super(alias);
|
|
1485
1564
|
// Ensure subcommands that use passThroughOptions can be attached safely.
|
|
@@ -1487,15 +1566,18 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1487
1566
|
// child uses passThroughOptions.
|
|
1488
1567
|
this.enablePositionalOptions();
|
|
1489
1568
|
// Configure grouped help: show only base options in default "Options";
|
|
1490
|
-
//
|
|
1569
|
+
// we will insert App/Plugin sections before Commands in helpInformation().
|
|
1491
1570
|
this.configureHelp({
|
|
1492
1571
|
visibleOptions: (cmd) => {
|
|
1493
|
-
const all = cmd.options ??
|
|
1494
|
-
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1572
|
+
const all = cmd.options ?? [];
|
|
1573
|
+
const parent = cmd.parent ?? null;
|
|
1574
|
+
const isRoot = parent === null;
|
|
1575
|
+
const list = isRoot
|
|
1576
|
+
? all.filter((opt) => {
|
|
1577
|
+
const group = opt.__group;
|
|
1578
|
+
return group === 'base';
|
|
1579
|
+
})
|
|
1580
|
+
: all.slice(); // subcommands: show all options (their own "Options:" block)
|
|
1499
1581
|
// Sort: short-aliased options first, then long-only; stable by flags.
|
|
1500
1582
|
const hasShort = (opt) => {
|
|
1501
1583
|
const flags = opt.flags ?? '';
|
|
@@ -1503,19 +1585,18 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1503
1585
|
return /(^|\s|,)-[A-Za-z]/.test(flags);
|
|
1504
1586
|
};
|
|
1505
1587
|
const byFlags = (opt) => opt.flags ?? '';
|
|
1506
|
-
|
|
1588
|
+
list.sort((a, b) => {
|
|
1507
1589
|
const aS = hasShort(a) ? 1 : 0;
|
|
1508
1590
|
const bS = hasShort(b) ? 1 : 0;
|
|
1509
1591
|
return bS - aS || byFlags(a).localeCompare(byFlags(b));
|
|
1510
1592
|
});
|
|
1511
|
-
return
|
|
1593
|
+
return list;
|
|
1512
1594
|
},
|
|
1513
1595
|
});
|
|
1514
1596
|
this.addHelpText('beforeAll', () => {
|
|
1515
1597
|
const header = this[HELP_HEADER_SYMBOL];
|
|
1516
1598
|
return header && header.length > 0 ? `${header}\n\n` : '';
|
|
1517
1599
|
});
|
|
1518
|
-
this.addHelpText('afterAll', (ctx) => this.#renderOptionGroups(ctx.command));
|
|
1519
1600
|
// Skeleton preSubcommand hook: produce a context if absent, without
|
|
1520
1601
|
// mutating process.env. The passOptions hook (when installed) will // compute the final context using merged CLI options; keeping
|
|
1521
1602
|
// loadProcess=false here avoids leaking dotenv values into the parent
|
|
@@ -1527,9 +1608,15 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1527
1608
|
});
|
|
1528
1609
|
}
|
|
1529
1610
|
/**
|
|
1530
|
-
* Resolve options (strict) and compute dotenv context.
|
|
1611
|
+
* Resolve options (strict) and compute dotenv context.
|
|
1612
|
+
* Stores the context on the instance under a symbol.
|
|
1613
|
+
*
|
|
1614
|
+
* Options:
|
|
1615
|
+
* - opts.runAfterResolve (default true): when false, skips running plugin
|
|
1616
|
+
* afterResolve hooks. Useful for top-level help rendering to avoid
|
|
1617
|
+
* long-running side-effects while still evaluating dynamic help text.
|
|
1531
1618
|
*/
|
|
1532
|
-
async resolveAndLoad(customOptions = {}) {
|
|
1619
|
+
async resolveAndLoad(customOptions = {}, opts) {
|
|
1533
1620
|
// Resolve defaults, then validate strictly under the new host.
|
|
1534
1621
|
const optionsResolved = await resolveGetDotenvOptions(customOptions);
|
|
1535
1622
|
getDotenvOptionsSchemaResolved.parse(optionsResolved);
|
|
@@ -1540,9 +1627,64 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1540
1627
|
ctx;
|
|
1541
1628
|
// Ensure plugins are installed exactly once, then run afterResolve.
|
|
1542
1629
|
await this.install();
|
|
1543
|
-
|
|
1630
|
+
if (opts?.runAfterResolve ?? true) {
|
|
1631
|
+
await this._runAfterResolve(ctx);
|
|
1632
|
+
}
|
|
1544
1633
|
return ctx;
|
|
1545
1634
|
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Create a Commander Option that computes its description at help time.
|
|
1637
|
+
* The returned Option may be configured (conflicts, default, parser) and
|
|
1638
|
+
* added via addOption().
|
|
1639
|
+
*/
|
|
1640
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
|
1641
|
+
createDynamicOption(flags, desc, parser, defaultValue) {
|
|
1642
|
+
const opt = new commander.Option(flags, '');
|
|
1643
|
+
// Keep the function on a private symbol so it survives through Commander.
|
|
1644
|
+
opt[DYN_DESC_SYM] = desc;
|
|
1645
|
+
if (parser)
|
|
1646
|
+
opt.argParser(parser);
|
|
1647
|
+
if (defaultValue !== undefined)
|
|
1648
|
+
opt.default(defaultValue);
|
|
1649
|
+
return opt;
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Chainable helper mirroring .option(), but with a dynamic description.
|
|
1653
|
+
* Equivalent to addOption(createDynamicOption(...)).
|
|
1654
|
+
*/
|
|
1655
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
|
|
1656
|
+
dynamicOption(flags, desc, parser, defaultValue) {
|
|
1657
|
+
const opt = this.createDynamicOption(flags, desc, parser, defaultValue);
|
|
1658
|
+
this.addOption(opt);
|
|
1659
|
+
return this;
|
|
1660
|
+
}
|
|
1661
|
+
/**
|
|
1662
|
+
* Evaluate dynamic descriptions for this command and all descendants using
|
|
1663
|
+
* the provided resolved configuration. Mutates the Option.description in
|
|
1664
|
+
* place so Commander help renders updated text.
|
|
1665
|
+
*/
|
|
1666
|
+
evaluateDynamicOptions(resolved) {
|
|
1667
|
+
const visit = (cmd) => {
|
|
1668
|
+
const arr = cmd.options ?? [];
|
|
1669
|
+
for (const o of arr) {
|
|
1670
|
+
const dyn = o[DYN_DESC_SYM];
|
|
1671
|
+
if (typeof dyn === 'function') {
|
|
1672
|
+
try {
|
|
1673
|
+
const txt = dyn(resolved);
|
|
1674
|
+
// Commander Option has a public "description" field used by help.
|
|
1675
|
+
o.description = txt;
|
|
1676
|
+
}
|
|
1677
|
+
catch {
|
|
1678
|
+
// Best-effort: leave description as-is on evaluation failure.
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
const children = cmd.commands ?? [];
|
|
1683
|
+
for (const c of children)
|
|
1684
|
+
visit(c);
|
|
1685
|
+
};
|
|
1686
|
+
visit(this);
|
|
1687
|
+
}
|
|
1546
1688
|
/**
|
|
1547
1689
|
* Retrieve the current invocation context (if any).
|
|
1548
1690
|
*/
|
|
@@ -1572,6 +1714,7 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1572
1714
|
tagAppOptions(fn) {
|
|
1573
1715
|
const root = this;
|
|
1574
1716
|
const originalAddOption = root.addOption.bind(root);
|
|
1717
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1575
1718
|
const originalOption = root.option.bind(root);
|
|
1576
1719
|
const tagLatest = (cmd, group) => {
|
|
1577
1720
|
const optsArr = cmd.options;
|
|
@@ -1584,6 +1727,7 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1584
1727
|
opt.__group = 'app';
|
|
1585
1728
|
return originalAddOption(opt);
|
|
1586
1729
|
};
|
|
1730
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1587
1731
|
root.option = function patchedOption(...args) {
|
|
1588
1732
|
const ret = originalOption(...args);
|
|
1589
1733
|
tagLatest(this, 'app');
|
|
@@ -1594,6 +1738,7 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1594
1738
|
}
|
|
1595
1739
|
finally {
|
|
1596
1740
|
root.addOption = originalAddOption;
|
|
1741
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
1597
1742
|
root.option = originalOption;
|
|
1598
1743
|
}
|
|
1599
1744
|
}
|
|
@@ -1639,6 +1784,40 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1639
1784
|
}
|
|
1640
1785
|
return this;
|
|
1641
1786
|
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Insert grouped plugin/app options between "Options" and "Commands" for
|
|
1789
|
+
* hybrid ordering. Applies to root and any parent command.
|
|
1790
|
+
*/
|
|
1791
|
+
helpInformation() {
|
|
1792
|
+
// Base help text first (includes beforeAll/after hooks).
|
|
1793
|
+
const base = super.helpInformation();
|
|
1794
|
+
const groups = this.#renderOptionGroups(this);
|
|
1795
|
+
const block = typeof groups === 'string' ? groups.trim() : '';
|
|
1796
|
+
let out = base;
|
|
1797
|
+
if (!block) {
|
|
1798
|
+
// Ensure a trailing blank line even when no extra groups render.
|
|
1799
|
+
if (!out.endsWith('\n\n'))
|
|
1800
|
+
out = out.endsWith('\n') ? `${out}\n` : `${out}\n\n`;
|
|
1801
|
+
return out;
|
|
1802
|
+
}
|
|
1803
|
+
// Insert just before "Commands:" when present.
|
|
1804
|
+
const marker = '\nCommands:';
|
|
1805
|
+
const idx = base.indexOf(marker);
|
|
1806
|
+
if (idx >= 0) {
|
|
1807
|
+
const toInsert = groups.startsWith('\n') ? groups : `\n${groups}`;
|
|
1808
|
+
out = `${base.slice(0, idx)}${toInsert}${base.slice(idx)}`;
|
|
1809
|
+
}
|
|
1810
|
+
else {
|
|
1811
|
+
// Otherwise append.
|
|
1812
|
+
const sep = base.endsWith('\n') || groups.startsWith('\n') ? '' : '\n';
|
|
1813
|
+
out = `${base}${sep}${groups}`;
|
|
1814
|
+
}
|
|
1815
|
+
// Ensure a trailing blank line for prompt separation.
|
|
1816
|
+
if (!out.endsWith('\n\n')) {
|
|
1817
|
+
out = out.endsWith('\n') ? `${out}\n` : `${out}\n\n`;
|
|
1818
|
+
}
|
|
1819
|
+
return out;
|
|
1820
|
+
}
|
|
1642
1821
|
/**
|
|
1643
1822
|
* Register a plugin for installation (parent level).
|
|
1644
1823
|
* Installation occurs on first resolveAndLoad() (or explicit install()).
|
|
@@ -1677,7 +1856,7 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1677
1856
|
for (const p of this._plugins)
|
|
1678
1857
|
await run(p);
|
|
1679
1858
|
}
|
|
1680
|
-
// Render App/Plugin grouped options
|
|
1859
|
+
// Render App/Plugin grouped options (used by helpInformation override).
|
|
1681
1860
|
#renderOptionGroups(cmd) {
|
|
1682
1861
|
const all = cmd.options ?? [];
|
|
1683
1862
|
const byGroup = new Map();
|
|
@@ -1719,11 +1898,14 @@ let GetDotenvCli$1 = class GetDotenvCli extends commander.Command {
|
|
|
1719
1898
|
}
|
|
1720
1899
|
// Plugin groups sorted by id
|
|
1721
1900
|
const pluginKeys = Array.from(byGroup.keys()).filter((k) => k.startsWith('plugin:'));
|
|
1901
|
+
const currentName = cmd.name?.() ?? '';
|
|
1722
1902
|
pluginKeys.sort((a, b) => a.localeCompare(b));
|
|
1723
1903
|
for (const k of pluginKeys) {
|
|
1724
1904
|
const id = k.slice('plugin:'.length) || '(unknown)';
|
|
1725
1905
|
const rows = byGroup.get(k) ?? [];
|
|
1726
|
-
|
|
1906
|
+
// Do not show a "Plugin options — <self>" section on the command that owns those options.
|
|
1907
|
+
// Only child-injected plugin groups should render at this level.
|
|
1908
|
+
if (rows.length > 0 && id !== currentName) {
|
|
1727
1909
|
out += renderRows(`Plugin options — ${id}`, rows);
|
|
1728
1910
|
}
|
|
1729
1911
|
}
|
|
@@ -1794,6 +1976,17 @@ class GetDotenvCli extends GetDotenvCli$1 {
|
|
|
1794
1976
|
// Build service options and compute context (always-on loader path).
|
|
1795
1977
|
const serviceOptions = getDotenvCliOptions2Options(merged);
|
|
1796
1978
|
await this.resolveAndLoad(serviceOptions);
|
|
1979
|
+
// Refresh dynamic option descriptions using resolved config + plugin slices
|
|
1980
|
+
try {
|
|
1981
|
+
const ctx = this.getCtx();
|
|
1982
|
+
this.evaluateDynamicOptions({
|
|
1983
|
+
...ctx?.optionsResolved,
|
|
1984
|
+
plugins: ctx?.pluginConfigs ?? {},
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
catch {
|
|
1988
|
+
/* best-effort */
|
|
1989
|
+
}
|
|
1797
1990
|
// Global validation: once after Phase C using config sources.
|
|
1798
1991
|
try {
|
|
1799
1992
|
const ctx = this.getCtx();
|
|
@@ -1828,6 +2021,16 @@ class GetDotenvCli extends GetDotenvCli$1 {
|
|
|
1828
2021
|
if (!this.getCtx()) {
|
|
1829
2022
|
const serviceOptions = getDotenvCliOptions2Options(merged);
|
|
1830
2023
|
await this.resolveAndLoad(serviceOptions);
|
|
2024
|
+
try {
|
|
2025
|
+
const ctx = this.getCtx();
|
|
2026
|
+
this.evaluateDynamicOptions({
|
|
2027
|
+
...ctx?.optionsResolved,
|
|
2028
|
+
plugins: ctx?.pluginConfigs ?? {},
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
catch {
|
|
2032
|
+
/* tolerate */
|
|
2033
|
+
}
|
|
1831
2034
|
try {
|
|
1832
2035
|
const ctx = this.getCtx();
|
|
1833
2036
|
const dotenv = (ctx?.dotenv ?? {});
|