@karmaniverous/get-dotenv 6.2.3 → 6.2.4

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.
Files changed (89) hide show
  1. package/dist/chunks/AwsRestJsonProtocol-Bq1HE-Ln.mjs +932 -0
  2. package/dist/chunks/createCli-BY6_cfZr.mjs +439 -0
  3. package/dist/chunks/externalDataInterceptor-CbsdEYa-.mjs +19 -0
  4. package/dist/chunks/getSSOTokenFromFile-hUSpR7Wf.mjs +22 -0
  5. package/dist/chunks/helpConfig-CGejgwWW.mjs +12 -0
  6. package/dist/chunks/index-B5JKTBOL.mjs +443 -0
  7. package/dist/chunks/index-BEJFiHMX.mjs +522 -0
  8. package/dist/chunks/index-BPYF6K_G.mjs +82 -0
  9. package/dist/chunks/index-Bc3h0a95.mjs +374 -0
  10. package/dist/chunks/index-BpCF5UKx.mjs +272 -0
  11. package/dist/chunks/index-C_wqbTwI.mjs +187 -0
  12. package/dist/chunks/index-CeCufHlm.mjs +9374 -0
  13. package/dist/chunks/index-Cu7rdyqN.mjs +102 -0
  14. package/dist/chunks/index-DWAtHEA-.mjs +379 -0
  15. package/dist/chunks/index-Dp1Ip6Ra.mjs +354 -0
  16. package/dist/chunks/index-DyU5pKKi.mjs +24 -0
  17. package/dist/chunks/index-c7zKtEuy.mjs +578 -0
  18. package/dist/chunks/index-cIunyiUQ.mjs +702 -0
  19. package/dist/chunks/invoke-DuRPU1oC.mjs +60 -0
  20. package/dist/chunks/loadModuleDefault-Dj8B3Stt.mjs +123 -0
  21. package/dist/chunks/loadSso-w1eTVg0O.mjs +412 -0
  22. package/dist/chunks/loader-DnhPeGfq.mjs +346 -0
  23. package/dist/chunks/overlayEnv-Bs2kVayG.mjs +234 -0
  24. package/dist/chunks/package-boo9EyYs.mjs +5 -0
  25. package/dist/chunks/parseKnownFiles-B9cDK21V.mjs +23 -0
  26. package/dist/chunks/readMergedOptions-Nt0TR7dX.mjs +1626 -0
  27. package/dist/chunks/resolveCliOptions-TFRzhB2c.mjs +138 -0
  28. package/dist/chunks/sdk-stream-mixin-BZoJ5jy9.mjs +167 -0
  29. package/dist/chunks/spawnEnv-CN8a7cNR.mjs +306 -0
  30. package/dist/chunks/types-DJ-BGABd.mjs +59 -0
  31. package/dist/chunks/validate-CDl0rE6k.mjs +61 -0
  32. package/dist/cli.mjs +39 -19307
  33. package/dist/cliHost.mjs +20 -2800
  34. package/dist/config.mjs +10 -509
  35. package/dist/env-overlay.mjs +6 -337
  36. package/dist/getdotenv.cli.mjs +39 -19305
  37. package/dist/index.mjs +39 -19396
  38. package/dist/plugins-aws.d.ts +1 -4
  39. package/dist/plugins-aws.mjs +65 -2568
  40. package/dist/plugins-batch.mjs +16 -2573
  41. package/dist/plugins-cmd.mjs +19 -3094
  42. package/dist/plugins-init.d.ts +8 -0
  43. package/dist/plugins-init.mjs +85 -2297
  44. package/dist/plugins.mjs +36 -18817
  45. package/package.json +1 -2
  46. package/dist/templates/cli/index.ts +0 -25
  47. package/dist/templates/cli/plugins/hello/defaultAction.ts +0 -27
  48. package/dist/templates/cli/plugins/hello/index.ts +0 -26
  49. package/dist/templates/cli/plugins/hello/options.ts +0 -31
  50. package/dist/templates/cli/plugins/hello/strangerAction.ts +0 -20
  51. package/dist/templates/cli/plugins/hello/types.ts +0 -13
  52. package/dist/templates/config/js/getdotenv.config.js +0 -20
  53. package/dist/templates/config/json/local/getdotenv.config.local.json +0 -7
  54. package/dist/templates/config/json/public/getdotenv.config.json +0 -9
  55. package/dist/templates/config/public/getdotenv.config.json +0 -8
  56. package/dist/templates/config/ts/getdotenv.config.ts +0 -28
  57. package/dist/templates/config/yaml/local/getdotenv.config.local.yaml +0 -7
  58. package/dist/templates/config/yaml/public/getdotenv.config.yaml +0 -7
  59. package/dist/templates/defaultAction.ts +0 -27
  60. package/dist/templates/getdotenv.config.js +0 -20
  61. package/dist/templates/getdotenv.config.json +0 -9
  62. package/dist/templates/getdotenv.config.local.json +0 -7
  63. package/dist/templates/getdotenv.config.local.yaml +0 -7
  64. package/dist/templates/getdotenv.config.ts +0 -28
  65. package/dist/templates/getdotenv.config.yaml +0 -7
  66. package/dist/templates/hello/defaultAction.ts +0 -27
  67. package/dist/templates/hello/index.ts +0 -26
  68. package/dist/templates/hello/options.ts +0 -31
  69. package/dist/templates/hello/strangerAction.ts +0 -20
  70. package/dist/templates/hello/types.ts +0 -13
  71. package/dist/templates/index.ts +0 -26
  72. package/dist/templates/js/getdotenv.config.js +0 -20
  73. package/dist/templates/json/local/getdotenv.config.local.json +0 -7
  74. package/dist/templates/json/public/getdotenv.config.json +0 -9
  75. package/dist/templates/local/getdotenv.config.local.json +0 -7
  76. package/dist/templates/local/getdotenv.config.local.yaml +0 -7
  77. package/dist/templates/options.ts +0 -31
  78. package/dist/templates/plugins/hello/defaultAction.ts +0 -27
  79. package/dist/templates/plugins/hello/index.ts +0 -26
  80. package/dist/templates/plugins/hello/options.ts +0 -31
  81. package/dist/templates/plugins/hello/strangerAction.ts +0 -20
  82. package/dist/templates/plugins/hello/types.ts +0 -13
  83. package/dist/templates/public/getdotenv.config.json +0 -9
  84. package/dist/templates/public/getdotenv.config.yaml +0 -7
  85. package/dist/templates/strangerAction.ts +0 -20
  86. package/dist/templates/ts/getdotenv.config.ts +0 -28
  87. package/dist/templates/types.ts +0 -13
  88. package/dist/templates/yaml/local/getdotenv.config.local.yaml +0 -7
  89. package/dist/templates/yaml/public/getdotenv.config.yaml +0 -7
@@ -0,0 +1,1626 @@
1
+ import { z } from 'zod';
2
+ import { Option, Command } from '@commander-js/extra-typings';
3
+ import { d as dotenvExpand, a as dotenvExpandAll, l as loadAndApplyDynamic, c as applyDynamicMap, o as overlayEnv, b as dotenvExpandFromProcessEnv } from './overlayEnv-Bs2kVayG.mjs';
4
+ import { nanoid } from 'nanoid';
5
+ import path from 'path';
6
+ import fs from 'fs-extra';
7
+ import 'crypto';
8
+ import { fileURLToPath } from 'url';
9
+ import { parse } from 'dotenv';
10
+ import { g as getDotenvOptionsSchemaResolved, r as resolveGetDotenvConfigSources } from './loader-DnhPeGfq.mjs';
11
+ import { packageDirectory } from 'package-directory';
12
+
13
+ /** @internal */
14
+ const isPlainObject$1 = (value) => value !== null &&
15
+ typeof value === 'object' &&
16
+ Object.getPrototypeOf(value) === Object.prototype;
17
+ const mergeInto = (target, source) => {
18
+ for (const [key, sVal] of Object.entries(source)) {
19
+ if (sVal === undefined)
20
+ continue; // do not overwrite with undefined
21
+ const tVal = target[key];
22
+ if (isPlainObject$1(tVal) && isPlainObject$1(sVal)) {
23
+ target[key] = mergeInto({ ...tVal }, sVal);
24
+ }
25
+ else if (isPlainObject$1(sVal)) {
26
+ target[key] = mergeInto({}, sVal);
27
+ }
28
+ else {
29
+ target[key] = sVal;
30
+ }
31
+ }
32
+ return target;
33
+ };
34
+ function defaultsDeep(...layers) {
35
+ const result = layers
36
+ .filter(Boolean)
37
+ .reduce((acc, layer) => mergeInto(acc, layer), {});
38
+ return result;
39
+ }
40
+
41
+ /**
42
+ * Serialize a dotenv record to a file with minimal quoting (multiline values are quoted).
43
+ * Future-proofs for ordering/sorting changes (currently insertion order).
44
+ *
45
+ * @param filename - Destination dotenv file path.
46
+ * @param data - Env-like map of values to write (values may be `undefined`).
47
+ * @returns A `Promise\<void\>` which resolves when the file has been written.
48
+ */
49
+ async function writeDotenvFile(filename, data) {
50
+ // Serialize: key=value with quotes only for multiline values.
51
+ const body = Object.keys(data).reduce((acc, key) => {
52
+ const v = data[key] ?? '';
53
+ const val = v.includes('\n') ? `"${v}"` : v;
54
+ return `${acc}${key}=${val}\n`;
55
+ }, '');
56
+ await fs.writeFile(filename, body, { encoding: 'utf-8' });
57
+ }
58
+
59
+ /** @internal */
60
+ const isPlainObject = (v) => v !== null &&
61
+ typeof v === 'object' &&
62
+ !Array.isArray(v) &&
63
+ Object.getPrototypeOf(v) === Object.prototype;
64
+ /**
65
+ * Deeply interpolate string leaves against envRef.
66
+ * Arrays are not recursed into; they are returned unchanged.
67
+ *
68
+ * @typeParam T - Shape of the input value.
69
+ * @param value - Input value (object/array/primitive).
70
+ * @param envRef - Reference environment for interpolation.
71
+ * @returns A new value with string leaves interpolated.
72
+ */
73
+ const interpolateDeep = (value, envRef) => {
74
+ // Strings: expand and return
75
+ if (typeof value === 'string') {
76
+ const out = dotenvExpand(value, envRef);
77
+ // dotenvExpand returns string | undefined; preserve original on undefined
78
+ return (out ?? value);
79
+ }
80
+ // Arrays: return as-is (no recursion)
81
+ if (Array.isArray(value)) {
82
+ return value;
83
+ }
84
+ // Plain objects: shallow clone and recurse into values
85
+ if (isPlainObject(value)) {
86
+ const src = value;
87
+ const out = {};
88
+ for (const [k, v] of Object.entries(src)) {
89
+ // Recurse for strings/objects; keep arrays as-is; preserve other scalars
90
+ if (typeof v === 'string')
91
+ out[k] = dotenvExpand(v, envRef) ?? v;
92
+ else if (Array.isArray(v))
93
+ out[k] = v;
94
+ else if (isPlainObject(v))
95
+ out[k] = interpolateDeep(v, envRef);
96
+ else
97
+ out[k] = v;
98
+ }
99
+ return out;
100
+ }
101
+ // Other primitives/types: return as-is
102
+ return value;
103
+ };
104
+
105
+ /** src/util/omitUndefined.ts
106
+ * Helpers to drop undefined-valued properties in a typed-friendly way.
107
+ */
108
+ /**
109
+ * Omit keys whose runtime value is undefined from a shallow object.
110
+ * Returns a Partial with non-undefined value types preserved.
111
+ *
112
+ * @typeParam T - Input object shape.
113
+ * @param obj - Object to filter.
114
+ * @returns A shallow copy of `obj` without keys whose value is `undefined`.
115
+ */
116
+ function omitUndefined(obj) {
117
+ const out = {};
118
+ for (const [k, v] of Object.entries(obj)) {
119
+ if (v !== undefined)
120
+ out[k] = v;
121
+ }
122
+ return out;
123
+ }
124
+ /**
125
+ * Specialized helper for env-like maps: drop undefined and return string-only.
126
+ *
127
+ * @typeParam V - Value type for present entries (must extend `string`).
128
+ * @param obj - Env-like record containing `string | undefined` values.
129
+ * @returns A new record containing only the keys with defined values.
130
+ */
131
+ function omitUndefinedRecord(obj) {
132
+ const out = {};
133
+ for (const [k, v] of Object.entries(obj)) {
134
+ if (v !== undefined)
135
+ out[k] = v;
136
+ }
137
+ return out;
138
+ }
139
+
140
+ /** src/diagnostics/entropy.ts
141
+ * Entropy diagnostics (presentation-only).
142
+ * - Gated by min length and printable ASCII.
143
+ * - Warn once per key per run when bits/char \>= threshold.
144
+ * - Supports whitelist patterns to suppress known-noise keys.
145
+ */
146
+ const warned = new Set();
147
+ const isPrintableAscii = (s) => /^[\x20-\x7E]+$/.test(s);
148
+ const compile$1 = (patterns) => (patterns ?? []).map((p) => (typeof p === 'string' ? new RegExp(p, 'i') : p));
149
+ const whitelisted = (key, regs) => regs.some((re) => re.test(key));
150
+ const shannonBitsPerChar = (s) => {
151
+ const freq = new Map();
152
+ for (const ch of s)
153
+ freq.set(ch, (freq.get(ch) ?? 0) + 1);
154
+ const n = s.length;
155
+ let h = 0;
156
+ for (const c of freq.values()) {
157
+ const p = c / n;
158
+ h -= p * Math.log2(p);
159
+ }
160
+ return h;
161
+ };
162
+ /**
163
+ * Maybe emit a one-line entropy warning for a key.
164
+ * Caller supplies an `emit(line)` function; the helper ensures once-per-key.
165
+ */
166
+ const maybeWarnEntropy = (key, value, origin, opts, emit) => {
167
+ if (!opts || opts.warnEntropy === false)
168
+ return;
169
+ if (warned.has(key))
170
+ return;
171
+ const v = value ?? '';
172
+ const minLen = Math.max(0, opts.entropyMinLength ?? 16);
173
+ const threshold = opts.entropyThreshold ?? 3.8;
174
+ if (v.length < minLen)
175
+ return;
176
+ if (!isPrintableAscii(v))
177
+ return;
178
+ const wl = compile$1(opts.entropyWhitelist);
179
+ if (whitelisted(key, wl))
180
+ return;
181
+ const bpc = shannonBitsPerChar(v);
182
+ if (bpc >= threshold) {
183
+ warned.add(key);
184
+ emit(`[entropy] key=${key} score=${bpc.toFixed(2)} len=${String(v.length)} origin=${origin}`);
185
+ }
186
+ };
187
+
188
+ const DEFAULT_PATTERNS = [
189
+ '\\bsecret\\b',
190
+ '\\btoken\\b',
191
+ '\\bpass(word)?\\b',
192
+ '\\bapi[_-]?key\\b',
193
+ '\\bkey\\b',
194
+ ];
195
+ const compile = (patterns) => (patterns && patterns.length > 0 ? patterns : DEFAULT_PATTERNS).map((p) => typeof p === 'string' ? new RegExp(p, 'i') : p);
196
+ const shouldRedactKey = (key, regs) => regs.some((re) => re.test(key));
197
+ const MASK = '[redacted]';
198
+ /**
199
+ * Redact a single displayed value according to key/patterns.
200
+ * Returns the original value when redaction is disabled or key is not matched.
201
+ */
202
+ const redactDisplay = (key, value, opts) => {
203
+ if (!value)
204
+ return value;
205
+ if (!opts?.redact)
206
+ return value;
207
+ const regs = compile(opts.redactPatterns);
208
+ return shouldRedactKey(key, regs) ? MASK : value;
209
+ };
210
+ /**
211
+ * Produce a shallow redacted copy of an env-like object for display.
212
+ */
213
+ const redactObject = (obj, opts) => {
214
+ if (!opts?.redact)
215
+ return { ...obj };
216
+ const regs = compile(opts.redactPatterns);
217
+ const out = {};
218
+ for (const [k, v] of Object.entries(obj)) {
219
+ out[k] = v && shouldRedactKey(k, regs) ? MASK : v;
220
+ }
221
+ return out;
222
+ };
223
+ /**
224
+ * Utility to redact three related displayed values (parent/dotenv/final)
225
+ * consistently for trace lines.
226
+ */
227
+ const redactTriple = (key, triple, opts) => {
228
+ if (!opts?.redact)
229
+ return triple;
230
+ const regs = compile(opts.redactPatterns);
231
+ const maskIf = (v) => (v && shouldRedactKey(key, regs) ? MASK : v);
232
+ const out = {};
233
+ const p = maskIf(triple.parent);
234
+ const d = maskIf(triple.dotenv);
235
+ const f = maskIf(triple.final);
236
+ if (p !== undefined)
237
+ out.parent = p;
238
+ if (d !== undefined)
239
+ out.dotenv = d;
240
+ if (f !== undefined)
241
+ out.final = f;
242
+ return out;
243
+ };
244
+
245
+ /**
246
+ * Base root CLI defaults (shared; kept untyped here to avoid cross-layer deps).
247
+ * Used as the bottom layer for CLI option resolution.
248
+ */
249
+ const baseScripts = {
250
+ 'git-status': {
251
+ cmd: 'git branch --show-current && git status -s -u',
252
+ shell: true,
253
+ },
254
+ };
255
+ /**
256
+ * Default values for root CLI options used by the host and helpers as the baseline layer during option resolution.
257
+ *
258
+ * These defaults correspond to the “stringly” root surface (see `RootOptionsShape`) and are merged by precedence with:
259
+ * - create-time overrides
260
+ * - any discovered configuration `rootOptionDefaults`
261
+ * - and finally CLI flags at runtime
262
+ *
263
+ * @public
264
+ */
265
+ const baseRootOptionDefaults = {
266
+ dotenvToken: '.env',
267
+ loadProcess: true,
268
+ logger: console,
269
+ // Diagnostics defaults
270
+ warnEntropy: true,
271
+ entropyThreshold: 3.8,
272
+ entropyMinLength: 16,
273
+ entropyWhitelist: ['^GIT_', '^npm_', '^CI$', 'SHLVL'],
274
+ paths: './',
275
+ pathsDelimiter: ' ',
276
+ privateToken: 'local',
277
+ scripts: baseScripts,
278
+ shell: true,
279
+ vars: '',
280
+ varsAssignor: '=',
281
+ varsDelimiter: ' ',
282
+ // tri-state flags default to unset unless explicitly provided
283
+ // (debug/log/exclude* resolved via flag utils)
284
+ };
285
+
286
+ function defineDynamic(d) {
287
+ return d;
288
+ }
289
+ /**
290
+ * Define a strongly‑typed get‑dotenv configuration document for JS/TS authoring.
291
+ *
292
+ * This helper is compile‑time only: it returns the input unchanged at runtime,
293
+ * but enables rich TypeScript inference for `vars`, `envVars`, and `dynamic`,
294
+ * and validates property names and value shapes as you author the config.
295
+ *
296
+ * @typeParam Vars - The string‑valued env map your project uses (for example,
297
+ * `{ APP_SETTING?: string }`). Keys propagate to `dynamic` function arguments.
298
+ * @typeParam Env - Allowed environment names used for `envVars` (defaults to `string`).
299
+ * @typeParam T - The full config type being produced (defaults to `GetDotenvConfig<Vars, Env>`).
300
+ * This type parameter is rarely supplied explicitly.
301
+ * @param cfg - The configuration object literal.
302
+ * @returns The same `cfg` value, with its type preserved for inference.
303
+ */
304
+ function defineGetDotenvConfig(cfg) {
305
+ return cfg;
306
+ }
307
+ /**
308
+ * Converts programmatic CLI options to `getDotenv` options.
309
+ *
310
+ * Accepts "stringly" CLI inputs for vars/paths and normalizes them into
311
+ * the programmatic shape. Preserves exactOptionalPropertyTypes semantics by
312
+ * omitting keys when undefined.
313
+ */
314
+ const getDotenvCliOptions2Options = ({ paths, pathsDelimiter, pathsDelimiterPattern, vars, varsAssignor, varsAssignorPattern, varsDelimiter, varsDelimiterPattern,
315
+ // drop CLI-only keys from the pass-through bag
316
+ debug: _debug, scripts: _scripts, ...rest }) => {
317
+ // Split helper for delimited strings or regex patterns
318
+ const splitBy = (value, delim, pattern) => {
319
+ if (!value)
320
+ return [];
321
+ if (pattern)
322
+ return value.split(RegExp(pattern));
323
+ if (typeof delim === 'string')
324
+ return value.split(delim);
325
+ return value.split(' ');
326
+ };
327
+ // Tolerate vars as either a CLI string ("A=1 B=2") or an object map.
328
+ let parsedVars;
329
+ if (typeof vars === 'string') {
330
+ const kvPairs = splitBy(vars, varsDelimiter, varsDelimiterPattern)
331
+ .map((v) => v.split(varsAssignorPattern
332
+ ? RegExp(varsAssignorPattern)
333
+ : (varsAssignor ?? '=')))
334
+ .filter(([k]) => typeof k === 'string' && k.length > 0);
335
+ parsedVars = Object.fromEntries(kvPairs);
336
+ }
337
+ else if (vars && typeof vars === 'object' && !Array.isArray(vars)) {
338
+ // Accept provided object map of string | undefined; drop undefined values
339
+ // in the normalization step below to produce a ProcessEnv-compatible bag.
340
+ parsedVars = Object.fromEntries(Object.entries(vars));
341
+ }
342
+ // Drop undefined-valued entries at the converter stage to match ProcessEnv
343
+ // expectations and the compat test assertions.
344
+ if (parsedVars) {
345
+ parsedVars = omitUndefinedRecord(parsedVars);
346
+ }
347
+ // Tolerate paths as either a delimited string or string[]
348
+ const pathsOut = Array.isArray(paths)
349
+ ? paths.filter((p) => typeof p === 'string')
350
+ : splitBy(paths, pathsDelimiter, pathsDelimiterPattern);
351
+ // Preserve exactOptionalPropertyTypes: only include keys when defined.
352
+ return {
353
+ // Ensure the required logger property is present. The base CLI defaults
354
+ // specify console as the logger; callers can override upstream if desired.
355
+ logger: console,
356
+ ...rest,
357
+ ...(pathsOut.length > 0 ? { paths: pathsOut } : {}),
358
+ ...(parsedVars !== undefined ? { vars: parsedVars } : {}),
359
+ };
360
+ };
361
+ /**
362
+ * Resolve {@link GetDotenvOptions} by layering defaults in ascending precedence:
363
+ *
364
+ * 1. Base defaults derived from the CLI generator defaults
365
+ * ({@link baseGetDotenvCliOptions}).
366
+ * 2. Local project overrides from a `getdotenv.config.json` in the nearest
367
+ * package root (if present).
368
+ * 3. The provided customOptions.
369
+ *
370
+ * The result preserves explicit empty values and drops only `undefined`.
371
+ */
372
+ const resolveGetDotenvOptions = (customOptions) => {
373
+ // Programmatic callers use neutral defaults only. Do not read local packaged
374
+ // getdotenv.config.json here; the host path applies packaged/project configs
375
+ // via the dedicated loader/overlay pipeline.
376
+ const mergedDefaults = baseRootOptionDefaults;
377
+ const defaultsFromCli = getDotenvCliOptions2Options(mergedDefaults);
378
+ const result = defaultsDeep(defaultsFromCli, customOptions);
379
+ return Promise.resolve({
380
+ ...result, // Keep explicit empty strings/zeros; drop only undefined
381
+ vars: omitUndefinedRecord(result.vars ?? {}),
382
+ });
383
+ };
384
+
385
+ /**
386
+ * Asynchronously read a dotenv file & parse it into an object.
387
+ *
388
+ * @param path - Path to dotenv file.
389
+ * @returns The parsed dotenv object.
390
+ */
391
+ const readDotenv = async (path) => {
392
+ try {
393
+ return (await fs.exists(path)) ? parse(await fs.readFile(path)) : {};
394
+ }
395
+ catch {
396
+ return {};
397
+ }
398
+ };
399
+
400
+ async function getDotenv(options = {}) {
401
+ // Apply defaults.
402
+ const { defaultEnv, dotenvToken = '.env', dynamicPath, env, excludeDynamic = false, excludeEnv = false, excludeGlobal = false, excludePrivate = false, excludePublic = false, loadProcess = false, log = false, logger = console, outputPath, paths = [], privateToken = 'local', vars = {}, } = await resolveGetDotenvOptions(options);
403
+ // Read .env files.
404
+ const loaded = paths.length
405
+ ? await paths.reduce(async (e, p) => {
406
+ const publicGlobal = excludePublic || excludeGlobal
407
+ ? Promise.resolve({})
408
+ : readDotenv(path.resolve(p, dotenvToken));
409
+ const publicEnv = excludePublic || excludeEnv || (!env && !defaultEnv)
410
+ ? Promise.resolve({})
411
+ : readDotenv(path.resolve(p, `${dotenvToken}.${env ?? defaultEnv ?? ''}`));
412
+ const privateGlobal = excludePrivate || excludeGlobal
413
+ ? Promise.resolve({})
414
+ : readDotenv(path.resolve(p, `${dotenvToken}.${privateToken}`));
415
+ const privateEnv = excludePrivate || excludeEnv || (!env && !defaultEnv)
416
+ ? Promise.resolve({})
417
+ : readDotenv(path.resolve(p, `${dotenvToken}.${env ?? defaultEnv ?? ''}.${privateToken}`));
418
+ const [eResolved, publicGlobalResolved, publicEnvResolved, privateGlobalResolved, privateEnvResolved,] = await Promise.all([
419
+ e,
420
+ publicGlobal,
421
+ publicEnv,
422
+ privateGlobal,
423
+ privateEnv,
424
+ ]);
425
+ return {
426
+ ...eResolved,
427
+ ...publicGlobalResolved,
428
+ ...publicEnvResolved,
429
+ ...privateGlobalResolved,
430
+ ...privateEnvResolved,
431
+ };
432
+ }, Promise.resolve({}))
433
+ : {};
434
+ const outputKey = nanoid();
435
+ const dotenv = dotenvExpandAll({
436
+ ...loaded,
437
+ ...vars,
438
+ ...(outputPath ? { [outputKey]: outputPath } : {}),
439
+ }, { progressive: true });
440
+ // Process dynamic variables. Programmatic option takes precedence over path.
441
+ if (!excludeDynamic) {
442
+ let dynamic = undefined;
443
+ if (options.dynamic && Object.keys(options.dynamic).length > 0) {
444
+ dynamic = options.dynamic;
445
+ }
446
+ else if (dynamicPath) {
447
+ const absDynamicPath = path.resolve(dynamicPath);
448
+ await loadAndApplyDynamic(dotenv, absDynamicPath, env ?? defaultEnv, 'getdotenv-dynamic');
449
+ }
450
+ if (dynamic) {
451
+ try {
452
+ applyDynamicMap(dotenv, dynamic, env ?? defaultEnv);
453
+ }
454
+ catch {
455
+ throw new Error(`Unable to evaluate dynamic variables.`);
456
+ }
457
+ }
458
+ }
459
+ // Write output file.
460
+ let resultDotenv = dotenv;
461
+ if (outputPath) {
462
+ const outputPathResolved = dotenv[outputKey];
463
+ if (!outputPathResolved)
464
+ throw new Error('Output path not found.');
465
+ const { [outputKey]: _omitted, ...dotenvForOutput } = dotenv;
466
+ await writeDotenvFile(outputPathResolved, dotenvForOutput);
467
+ resultDotenv = dotenvForOutput;
468
+ }
469
+ // Log result.
470
+ if (log) {
471
+ const redactFlag = options.redact ?? false;
472
+ const redactPatterns = options.redactPatterns ?? undefined;
473
+ const redOpts = {};
474
+ if (redactFlag)
475
+ redOpts.redact = true;
476
+ if (redactFlag && Array.isArray(redactPatterns))
477
+ redOpts.redactPatterns = redactPatterns;
478
+ const bag = redactFlag
479
+ ? redactObject(resultDotenv, redOpts)
480
+ : { ...resultDotenv };
481
+ logger.log(bag);
482
+ // Entropy warnings: once-per-key-per-run (presentation only)
483
+ const warnEntropyVal = options.warnEntropy ?? true;
484
+ const entropyThresholdVal = options
485
+ .entropyThreshold;
486
+ const entropyMinLengthVal = options
487
+ .entropyMinLength;
488
+ const entropyWhitelistVal = options.entropyWhitelist;
489
+ const entOpts = {};
490
+ if (typeof warnEntropyVal === 'boolean')
491
+ entOpts.warnEntropy = warnEntropyVal;
492
+ if (typeof entropyThresholdVal === 'number')
493
+ entOpts.entropyThreshold = entropyThresholdVal;
494
+ if (typeof entropyMinLengthVal === 'number')
495
+ entOpts.entropyMinLength = entropyMinLengthVal;
496
+ if (Array.isArray(entropyWhitelistVal))
497
+ entOpts.entropyWhitelist = entropyWhitelistVal;
498
+ for (const [k, v] of Object.entries(resultDotenv)) {
499
+ maybeWarnEntropy(k, v, v !== undefined ? 'dotenv' : 'unset', entOpts, (line) => {
500
+ logger.log(line);
501
+ });
502
+ }
503
+ }
504
+ // Load process.env.
505
+ if (loadProcess)
506
+ Object.assign(process.env, resultDotenv);
507
+ return resultDotenv;
508
+ }
509
+
510
+ /**
511
+ * Compute the realized path for a command mount (leaf-up to root).
512
+ * Excludes the root application alias.
513
+ *
514
+ * @param cli - The mounted command instance.
515
+ */
516
+ /**
517
+ * Flatten a plugin tree into a list of `{ plugin, path }` entries.
518
+ * Traverses the namespace chain in pre-order.
519
+ */
520
+ function flattenPluginTreeByPath(plugins, prefix) {
521
+ const out = [];
522
+ for (const p of plugins) {
523
+ const here = prefix && prefix.length > 0 ? `${prefix}/${p.ns}` : p.ns;
524
+ out.push({ plugin: p, path: here });
525
+ if (Array.isArray(p.children) && p.children.length > 0) {
526
+ out.push(...flattenPluginTreeByPath(p.children.map((c) => c.plugin), here));
527
+ }
528
+ }
529
+ return out;
530
+ }
531
+
532
+ /**
533
+ * Instance-bound plugin config store.
534
+ * Host stores the validated/interpolated slice per plugin instance.
535
+ * The store is intentionally private to this module; definePlugin()
536
+ * provides a typed accessor that reads from this store for the calling
537
+ * plugin instance.
538
+ */
539
+ const PLUGIN_CONFIG_STORE = new WeakMap();
540
+ /**
541
+ * Store a validated, interpolated config slice for a specific plugin instance.
542
+ * Generic on both the host options type and the plugin config type to avoid
543
+ * defaulting to GetDotenvOptions under exactOptionalPropertyTypes.
544
+ */
545
+ const setPluginConfig = (plugin, cfg) => {
546
+ PLUGIN_CONFIG_STORE.set(plugin, cfg);
547
+ };
548
+ /**
549
+ * Retrieve the validated/interpolated config slice for a plugin instance.
550
+ */
551
+ const getPluginConfig = (plugin) => {
552
+ return PLUGIN_CONFIG_STORE.get(plugin);
553
+ };
554
+ /**
555
+ * Compute the dotenv context for the host (uses the config loader/overlay path).
556
+ * - Resolves and validates options strictly (host-only).
557
+ * - Applies file cascade, overlays, dynamics, and optional effects.
558
+ * - Merges and validates per-plugin config slices (when provided), keyed by
559
+ * realized mount path (ns chain).
560
+ *
561
+ * @param customOptions - Partial options from the current invocation.
562
+ * @param plugins - Installed plugins (for config validation).
563
+ * @param hostMetaUrl - import.meta.url of the host module (for packaged root discovery).
564
+ */
565
+ const computeContext = async (customOptions, plugins, hostMetaUrl) => {
566
+ const optionsResolved = await resolveGetDotenvOptions(customOptions);
567
+ // Zod boundary: parse returns the schema-derived shape; we adopt our public
568
+ // GetDotenvOptions overlay (logger/dynamic typing) for internal processing.
569
+ const validated = getDotenvOptionsSchemaResolved.parse(optionsResolved);
570
+ // Build a pure base without side effects or logging (no dynamics, no programmatic vars).
571
+ const cleanedValidated = omitUndefined(validated);
572
+ const base = await getDotenv({
573
+ ...cleanedValidated,
574
+ excludeDynamic: true,
575
+ vars: {},
576
+ log: false,
577
+ loadProcess: false,
578
+ });
579
+ // Discover config sources and overlay with progressive expansion per slice.
580
+ const sources = await resolveGetDotenvConfigSources(hostMetaUrl);
581
+ const dotenvOverlaid = overlayEnv({
582
+ base,
583
+ env: validated.env ?? validated.defaultEnv,
584
+ configs: sources,
585
+ ...(validated.vars ? { programmaticVars: validated.vars } : {}),
586
+ });
587
+ const dotenv = { ...dotenvOverlaid };
588
+ // Programmatic dynamic variables (when provided)
589
+ applyDynamicMap(dotenv, validated.dynamic, validated.env ?? validated.defaultEnv);
590
+ // Packaged/project dynamics
591
+ const packagedDyn = (sources.packaged?.dynamic ?? undefined);
592
+ const publicDyn = (sources.project?.public?.dynamic ?? undefined);
593
+ const localDyn = (sources.project?.local?.dynamic ?? undefined);
594
+ applyDynamicMap(dotenv, packagedDyn, validated.env ?? validated.defaultEnv);
595
+ applyDynamicMap(dotenv, publicDyn, validated.env ?? validated.defaultEnv);
596
+ applyDynamicMap(dotenv, localDyn, validated.env ?? validated.defaultEnv);
597
+ // file dynamicPath (lowest)
598
+ if (validated.dynamicPath) {
599
+ const absDynamicPath = path.resolve(validated.dynamicPath);
600
+ await loadAndApplyDynamic(dotenv, absDynamicPath, validated.env ?? validated.defaultEnv, 'getdotenv-dynamic-host');
601
+ }
602
+ // Effects:
603
+ if (validated.outputPath) {
604
+ await writeDotenvFile(validated.outputPath, dotenv);
605
+ }
606
+ const logger = validated.logger;
607
+ if (validated.log)
608
+ logger.log(dotenv);
609
+ if (validated.loadProcess)
610
+ Object.assign(process.env, dotenv);
611
+ // Merge and validate per-plugin config keyed by realized path (ns chain).
612
+ const packagedPlugins = (sources.packaged &&
613
+ sources.packaged.plugins) ??
614
+ {};
615
+ const publicPlugins = (sources.project?.public &&
616
+ sources.project.public.plugins) ??
617
+ {};
618
+ const localPlugins = (sources.project?.local &&
619
+ sources.project.local.plugins) ??
620
+ {};
621
+ const entries = flattenPluginTreeByPath(plugins);
622
+ const mergedPluginConfigsByPath = {};
623
+ const envRef = {
624
+ ...dotenv,
625
+ ...process.env,
626
+ };
627
+ for (const e of entries) {
628
+ const pathKey = e.path;
629
+ const mergedRaw = defaultsDeep({}, packagedPlugins[pathKey] ?? {}, publicPlugins[pathKey] ?? {}, localPlugins[pathKey] ?? {});
630
+ const interpolated = mergedRaw && typeof mergedRaw === 'object'
631
+ ? interpolateDeep(mergedRaw, envRef)
632
+ : {};
633
+ const schema = e.plugin.configSchema;
634
+ if (schema) {
635
+ const parsed = schema.safeParse(interpolated);
636
+ if (!parsed.success) {
637
+ const err = parsed.error;
638
+ const msgs = err.issues
639
+ .map((i) => {
640
+ const pth = Array.isArray(i.path) ? i.path.join('.') : '';
641
+ const msg = typeof i.message === 'string' ? i.message : 'Invalid value';
642
+ return pth ? `${pth}: ${msg}` : msg;
643
+ })
644
+ .join('\n');
645
+ throw new Error(`Invalid config for plugin at '${pathKey}':\n${msgs}`);
646
+ }
647
+ const frozen = Object.freeze(parsed.data);
648
+ setPluginConfig(e.plugin, frozen);
649
+ mergedPluginConfigsByPath[pathKey] = frozen;
650
+ }
651
+ else {
652
+ const frozen = Object.freeze(interpolated);
653
+ setPluginConfig(e.plugin, frozen);
654
+ mergedPluginConfigsByPath[pathKey] = frozen;
655
+ }
656
+ }
657
+ return {
658
+ optionsResolved: validated,
659
+ dotenv,
660
+ plugins: {},
661
+ pluginConfigs: mergedPluginConfigsByPath,
662
+ };
663
+ };
664
+
665
+ // Implementation
666
+ function definePlugin(spec) {
667
+ const { ...rest } = spec;
668
+ const effectiveSchema = spec.configSchema ?? z.object({}).strict();
669
+ const base = {
670
+ ...rest,
671
+ configSchema: effectiveSchema,
672
+ children: [],
673
+ use(child, override) {
674
+ // Enforce sibling uniqueness at composition time.
675
+ const desired = (override && typeof override.ns === 'string' && override.ns.length > 0
676
+ ? override.ns
677
+ : child.ns).trim();
678
+ const collision = this.children.some((c) => {
679
+ const ns = (c.override &&
680
+ typeof c.override.ns === 'string' &&
681
+ c.override.ns.length > 0
682
+ ? c.override.ns
683
+ : c.plugin.ns).trim();
684
+ return ns === desired;
685
+ });
686
+ if (collision) {
687
+ const under = this.ns && this.ns.length > 0 ? this.ns : 'root';
688
+ throw new Error(`Duplicate namespace '${desired}' under '${under}'. ` +
689
+ `Override via .use(plugin, { ns: '...' }).`);
690
+ }
691
+ this.children.push({ plugin: child, override });
692
+ return this;
693
+ },
694
+ };
695
+ const extended = base;
696
+ extended.readConfig = function (_cli) {
697
+ const value = getPluginConfig(extended);
698
+ if (value === undefined) {
699
+ throw new Error('Plugin config not available. Ensure resolveAndLoad() has been called before readConfig().');
700
+ }
701
+ return value;
702
+ };
703
+ extended.createPluginDynamicOption = function (cli, flags, desc, parser, defaultValue) {
704
+ // Derive realized path strictly from the provided mount (leaf-up).
705
+ const realizedPath = (() => {
706
+ const parts = [];
707
+ let node = cli;
708
+ while (node.parent) {
709
+ parts.push(node.name());
710
+ node = node.parent;
711
+ }
712
+ return parts.reverse().join('/');
713
+ })();
714
+ return cli.createDynamicOption(flags, (c) => {
715
+ const fromStore = getPluginConfig(extended);
716
+ let cfgVal = fromStore ?? {};
717
+ // Strict fallback only by realized path for help-time synthetic usage.
718
+ if (!fromStore && realizedPath.length > 0) {
719
+ const bag = c.plugins;
720
+ const maybe = bag[realizedPath];
721
+ if (maybe && typeof maybe === 'object') {
722
+ cfgVal = maybe;
723
+ }
724
+ }
725
+ // c is strictly typed as ResolvedHelpConfig from cli.createDynamicOption
726
+ return desc(c, cfgVal);
727
+ }, parser, defaultValue);
728
+ };
729
+ return extended;
730
+ }
731
+
732
+ /**
733
+ * Attach root flags to a {@link GetDotenvCli} instance.
734
+ *
735
+ * Program is typed as {@link GetDotenvCli} and supports {@link GetDotenvCli.createDynamicOption | createDynamicOption}.
736
+ */
737
+ const attachRootOptions = (program, defaults) => {
738
+ const GROUP = 'base';
739
+ const { defaultEnv, dotenvToken, dynamicPath, env, outputPath, paths, pathsDelimiter, pathsDelimiterPattern, privateToken, varsAssignor, varsAssignorPattern, varsDelimiter, varsDelimiterPattern, } = defaults ?? {};
740
+ const va = typeof defaults?.varsAssignor === 'string' ? defaults.varsAssignor : '=';
741
+ const vd = typeof defaults?.varsDelimiter === 'string' ? defaults.varsDelimiter : ' ';
742
+ // Helper: append (default) tags for ON/OFF toggles
743
+ const onOff = (on, isDefault) => on
744
+ ? `ON${isDefault ? ' (default)' : ''}`
745
+ : `OFF${isDefault ? ' (default)' : ''}`;
746
+ program.enablePositionalOptions().passThroughOptions();
747
+ // -e, --env <string>
748
+ {
749
+ const opt = new Option('-e, --env <string>', 'target environment (dotenv-expanded)');
750
+ opt.argParser(dotenvExpandFromProcessEnv);
751
+ if (env !== undefined)
752
+ opt.default(env);
753
+ program.addOption(opt);
754
+ program.setOptionGroup(opt, GROUP);
755
+ }
756
+ // -v, --vars <string>
757
+ {
758
+ const examples = [
759
+ ['KEY1', 'VAL1'],
760
+ ['KEY2', 'VAL2'],
761
+ ]
762
+ .map((v) => v.join(va))
763
+ .join(vd);
764
+ const opt = new Option('-v, --vars <string>', `extra variables expressed as delimited key-value pairs (dotenv-expanded): ${examples}`);
765
+ opt.argParser(dotenvExpandFromProcessEnv);
766
+ program.addOption(opt);
767
+ program.setOptionGroup(opt, GROUP);
768
+ }
769
+ // Output path (interpolated later; help can remain static)
770
+ {
771
+ const opt = new Option('-o, --output-path <string>', 'consolidated output file (dotenv-expanded)');
772
+ opt.argParser(dotenvExpandFromProcessEnv);
773
+ if (outputPath !== undefined)
774
+ opt.default(outputPath);
775
+ program.addOption(opt);
776
+ program.setOptionGroup(opt, GROUP);
777
+ }
778
+ // Shell ON (string or boolean true => default shell)
779
+ {
780
+ const opt = program
781
+ .createDynamicOption('-s, --shell [string]', (cfg) => {
782
+ const s = cfg.shell;
783
+ let tag = '';
784
+ if (typeof s === 'boolean' && s)
785
+ tag = ' (default OS shell)';
786
+ else if (typeof s === 'string' && s.length > 0)
787
+ tag = ` (default ${s})`;
788
+ return `command execution shell, no argument for default OS shell or provide shell string${tag}`;
789
+ })
790
+ .conflicts('shellOff');
791
+ program.addOption(opt);
792
+ program.setOptionGroup(opt, GROUP);
793
+ }
794
+ // Shell OFF
795
+ {
796
+ const opt = program
797
+ .createDynamicOption('-S, --shell-off', (cfg) => {
798
+ const s = cfg.shell;
799
+ return `command execution shell OFF${s === false ? ' (default)' : ''}`;
800
+ })
801
+ .conflicts('shell');
802
+ program.addOption(opt);
803
+ program.setOptionGroup(opt, GROUP);
804
+ }
805
+ // Load process ON/OFF (dynamic defaults)
806
+ {
807
+ const optOn = program
808
+ .createDynamicOption('-p, --load-process', (cfg) => `load variables to process.env ${onOff(true, Boolean(cfg.loadProcess))}`)
809
+ .conflicts('loadProcessOff');
810
+ program.addOption(optOn);
811
+ program.setOptionGroup(optOn, GROUP);
812
+ const optOff = program
813
+ .createDynamicOption('-P, --load-process-off', (cfg) => `load variables to process.env ${onOff(false, !cfg.loadProcess)}`)
814
+ .conflicts('loadProcess');
815
+ program.addOption(optOff);
816
+ program.setOptionGroup(optOff, GROUP);
817
+ }
818
+ // Exclusion master toggle (dynamic)
819
+ {
820
+ const optAll = program
821
+ .createDynamicOption('-a, --exclude-all', (cfg) => {
822
+ const allOn = !!cfg.excludeDynamic &&
823
+ ((!!cfg.excludeEnv && !!cfg.excludeGlobal) ||
824
+ (!!cfg.excludePrivate && !!cfg.excludePublic));
825
+ const suffix = allOn ? ' (default)' : '';
826
+ return `exclude all dotenv variables from loading ON${suffix}`;
827
+ })
828
+ .conflicts('excludeAllOff');
829
+ program.addOption(optAll);
830
+ program.setOptionGroup(optAll, GROUP);
831
+ const optAllOff = new Option('-A, --exclude-all-off', 'exclude all dotenv variables from loading OFF (default)').conflicts('excludeAll');
832
+ program.addOption(optAllOff);
833
+ program.setOptionGroup(optAllOff, GROUP);
834
+ }
835
+ // Per-family exclusions (dynamic defaults)
836
+ {
837
+ const o1 = program
838
+ .createDynamicOption('-z, --exclude-dynamic', (cfg) => `exclude dynamic dotenv variables from loading ${onOff(true, Boolean(cfg.excludeDynamic))}`)
839
+ .conflicts('excludeDynamicOff');
840
+ program.addOption(o1);
841
+ program.setOptionGroup(o1, GROUP);
842
+ const o2 = program
843
+ .createDynamicOption('-Z, --exclude-dynamic-off', (cfg) => `exclude dynamic dotenv variables from loading ${onOff(false, !cfg.excludeDynamic)}`)
844
+ .conflicts('excludeDynamic');
845
+ program.addOption(o2);
846
+ program.setOptionGroup(o2, GROUP);
847
+ }
848
+ {
849
+ const o1 = program
850
+ .createDynamicOption('-n, --exclude-env', (cfg) => `exclude environment-specific dotenv variables from loading ${onOff(true, Boolean(cfg.excludeEnv))}`)
851
+ .conflicts('excludeEnvOff');
852
+ program.addOption(o1);
853
+ program.setOptionGroup(o1, GROUP);
854
+ const o2 = program
855
+ .createDynamicOption('-N, --exclude-env-off', (cfg) => `exclude environment-specific dotenv variables from loading ${onOff(false, !cfg.excludeEnv)}`)
856
+ .conflicts('excludeEnv');
857
+ program.addOption(o2);
858
+ program.setOptionGroup(o2, GROUP);
859
+ }
860
+ {
861
+ const o1 = program
862
+ .createDynamicOption('-g, --exclude-global', (cfg) => `exclude global dotenv variables from loading ${onOff(true, Boolean(cfg.excludeGlobal))}`)
863
+ .conflicts('excludeGlobalOff');
864
+ program.addOption(o1);
865
+ program.setOptionGroup(o1, GROUP);
866
+ const o2 = program
867
+ .createDynamicOption('-G, --exclude-global-off', (cfg) => `exclude global dotenv variables from loading ${onOff(false, !cfg.excludeGlobal)}`)
868
+ .conflicts('excludeGlobal');
869
+ program.addOption(o2);
870
+ program.setOptionGroup(o2, GROUP);
871
+ }
872
+ {
873
+ const p1 = program
874
+ .createDynamicOption('-r, --exclude-private', (cfg) => `exclude private dotenv variables from loading ${onOff(true, Boolean(cfg.excludePrivate))}`)
875
+ .conflicts('excludePrivateOff');
876
+ program.addOption(p1);
877
+ program.setOptionGroup(p1, GROUP);
878
+ const p2 = program
879
+ .createDynamicOption('-R, --exclude-private-off', (cfg) => `exclude private dotenv variables from loading ${onOff(false, !cfg.excludePrivate)}`)
880
+ .conflicts('excludePrivate');
881
+ program.addOption(p2);
882
+ program.setOptionGroup(p2, GROUP);
883
+ const pu1 = program
884
+ .createDynamicOption('-u, --exclude-public', (cfg) => `exclude public dotenv variables from loading ${onOff(true, Boolean(cfg.excludePublic))}`)
885
+ .conflicts('excludePublicOff');
886
+ program.addOption(pu1);
887
+ program.setOptionGroup(pu1, GROUP);
888
+ const pu2 = program
889
+ .createDynamicOption('-U, --exclude-public-off', (cfg) => `exclude public dotenv variables from loading ${onOff(false, !cfg.excludePublic)}`)
890
+ .conflicts('excludePublic');
891
+ program.addOption(pu2);
892
+ program.setOptionGroup(pu2, GROUP);
893
+ }
894
+ // Log ON/OFF (dynamic)
895
+ {
896
+ const lo = program
897
+ .createDynamicOption('-l, --log', (cfg) => `console log loaded variables ${onOff(true, Boolean(cfg.log))}`)
898
+ .conflicts('logOff');
899
+ program.addOption(lo);
900
+ program.setOptionGroup(lo, GROUP);
901
+ const lf = program
902
+ .createDynamicOption('-L, --log-off', (cfg) => `console log loaded variables ${onOff(false, !cfg.log)}`)
903
+ .conflicts('log');
904
+ program.addOption(lf);
905
+ program.setOptionGroup(lf, GROUP);
906
+ }
907
+ // Capture flag (no default display; static)
908
+ {
909
+ const opt = new Option('--capture', 'capture child process stdio for commands (tests/CI)');
910
+ program.addOption(opt);
911
+ program.setOptionGroup(opt, GROUP);
912
+ }
913
+ // Core bootstrap/static flags (kept static in help)
914
+ {
915
+ const o1 = new Option('--default-env <string>', 'default target environment');
916
+ o1.argParser(dotenvExpandFromProcessEnv);
917
+ if (defaultEnv !== undefined)
918
+ o1.default(defaultEnv);
919
+ program.addOption(o1);
920
+ program.setOptionGroup(o1, GROUP);
921
+ const o2 = new Option('--dotenv-token <string>', 'dotenv-expanded token indicating a dotenv file');
922
+ o2.argParser(dotenvExpandFromProcessEnv);
923
+ if (dotenvToken !== undefined)
924
+ o2.default(dotenvToken);
925
+ program.addOption(o2);
926
+ program.setOptionGroup(o2, GROUP);
927
+ const o3 = new Option('--dynamic-path <string>', 'dynamic variables path (.js or .ts; .ts is auto-compiled when esbuild is available, otherwise precompile)');
928
+ o3.argParser(dotenvExpandFromProcessEnv);
929
+ if (dynamicPath !== undefined)
930
+ o3.default(dynamicPath);
931
+ program.addOption(o3);
932
+ program.setOptionGroup(o3, GROUP);
933
+ const o4 = new Option('--paths <string>', 'dotenv-expanded delimited list of paths to dotenv directory');
934
+ o4.argParser(dotenvExpandFromProcessEnv);
935
+ if (paths !== undefined)
936
+ o4.default(paths);
937
+ program.addOption(o4);
938
+ program.setOptionGroup(o4, GROUP);
939
+ const o5 = new Option('--paths-delimiter <string>', 'paths delimiter string');
940
+ if (pathsDelimiter !== undefined)
941
+ o5.default(pathsDelimiter);
942
+ program.addOption(o5);
943
+ program.setOptionGroup(o5, GROUP);
944
+ const o6 = new Option('--paths-delimiter-pattern <string>', 'paths delimiter regex pattern');
945
+ if (pathsDelimiterPattern !== undefined)
946
+ o6.default(pathsDelimiterPattern);
947
+ program.addOption(o6);
948
+ program.setOptionGroup(o6, GROUP);
949
+ const o7 = new Option('--private-token <string>', 'dotenv-expanded token indicating private variables');
950
+ o7.argParser(dotenvExpandFromProcessEnv);
951
+ if (privateToken !== undefined)
952
+ o7.default(privateToken);
953
+ program.addOption(o7);
954
+ program.setOptionGroup(o7, GROUP);
955
+ const o8 = new Option('--vars-delimiter <string>', 'vars delimiter string');
956
+ if (varsDelimiter !== undefined)
957
+ o8.default(varsDelimiter);
958
+ program.addOption(o8);
959
+ program.setOptionGroup(o8, GROUP);
960
+ const o9 = new Option('--vars-delimiter-pattern <string>', 'vars delimiter regex pattern');
961
+ if (varsDelimiterPattern !== undefined)
962
+ o9.default(varsDelimiterPattern);
963
+ program.addOption(o9);
964
+ program.setOptionGroup(o9, GROUP);
965
+ const o10 = new Option('--vars-assignor <string>', 'vars assignment operator string');
966
+ if (varsAssignor !== undefined)
967
+ o10.default(varsAssignor);
968
+ program.addOption(o10);
969
+ program.setOptionGroup(o10, GROUP);
970
+ const o11 = new Option('--vars-assignor-pattern <string>', 'vars assignment operator regex pattern');
971
+ if (varsAssignorPattern !== undefined)
972
+ o11.default(varsAssignorPattern);
973
+ program.addOption(o11);
974
+ program.setOptionGroup(o11, GROUP);
975
+ }
976
+ // Diagnostics / validation / entropy
977
+ {
978
+ const tr = new Option('--trace [keys...]', 'emit diagnostics for child env composition (optional keys)');
979
+ program.addOption(tr);
980
+ program.setOptionGroup(tr, GROUP);
981
+ const st = new Option('--strict', 'fail on env validation errors (schema/requiredKeys)');
982
+ program.addOption(st);
983
+ program.setOptionGroup(st, GROUP);
984
+ }
985
+ {
986
+ const w = program
987
+ .createDynamicOption('--entropy-warn', (cfg) => {
988
+ const warn = cfg.warnEntropy;
989
+ // Default is effectively ON when warnEntropy is true or undefined.
990
+ return `enable entropy warnings${warn === false ? '' : ' (default on)'}`;
991
+ })
992
+ .conflicts('entropyWarnOff');
993
+ program.addOption(w);
994
+ program.setOptionGroup(w, GROUP);
995
+ const woff = program
996
+ .createDynamicOption('--entropy-warn-off', (cfg) => `disable entropy warnings${cfg.warnEntropy === false ? ' (default)' : ''}`)
997
+ .conflicts('entropyWarn');
998
+ program.addOption(woff);
999
+ program.setOptionGroup(woff, GROUP);
1000
+ const th = new Option('--entropy-threshold <number>', 'entropy bits/char threshold (default 3.8)');
1001
+ program.addOption(th);
1002
+ program.setOptionGroup(th, GROUP);
1003
+ const ml = new Option('--entropy-min-length <number>', 'min length to examine for entropy (default 16)');
1004
+ program.addOption(ml);
1005
+ program.setOptionGroup(ml, GROUP);
1006
+ const wl = new Option('--entropy-whitelist <pattern...>', 'suppress entropy warnings when key matches any regex pattern');
1007
+ program.addOption(wl);
1008
+ program.setOptionGroup(wl, GROUP);
1009
+ const rp = new Option('--redact-pattern <pattern...>', 'additional key-match regex patterns to trigger redaction');
1010
+ program.addOption(rp);
1011
+ program.setOptionGroup(rp, GROUP);
1012
+ // Redact ON/OFF (dynamic)
1013
+ {
1014
+ const rOn = program
1015
+ .createDynamicOption('--redact', (cfg) => `presentation-time redaction for secret-like keys ON${cfg.redact ? ' (default)' : ''}`)
1016
+ .conflicts('redactOff');
1017
+ program.addOption(rOn);
1018
+ program.setOptionGroup(rOn, GROUP);
1019
+ const rOff = program
1020
+ .createDynamicOption('--redact-off', (cfg) => `presentation-time redaction for secret-like keys OFF${cfg.redact === false ? ' (default)' : ''}`)
1021
+ .conflicts('redact');
1022
+ program.addOption(rOff);
1023
+ program.setOptionGroup(rOff, GROUP);
1024
+ }
1025
+ }
1026
+ return program;
1027
+ };
1028
+
1029
+ /**
1030
+ * Registry for option grouping.
1031
+ * Root help renders these groups between "Options" and "Commands".
1032
+ */
1033
+ const GROUP_TAG = new WeakMap();
1034
+ /**
1035
+ * Render help option groups (App/Plugins) for a given command.
1036
+ * Groups are injected between Options and Commands in the help output.
1037
+ */
1038
+ function renderOptionGroups(cmd) {
1039
+ const all = cmd.options;
1040
+ const byGroup = new Map();
1041
+ for (const o of all) {
1042
+ const opt = o;
1043
+ const g = GROUP_TAG.get(opt);
1044
+ if (!g || g === 'base')
1045
+ continue; // base handled by default help
1046
+ const rows = byGroup.get(g) ?? [];
1047
+ rows.push({
1048
+ flags: opt.flags,
1049
+ description: opt.description ?? '',
1050
+ });
1051
+ byGroup.set(g, rows);
1052
+ }
1053
+ if (byGroup.size === 0)
1054
+ return '';
1055
+ const renderRows = (title, rows) => {
1056
+ const width = Math.min(40, rows.reduce((m, r) => Math.max(m, r.flags.length), 0));
1057
+ // Sort within group: short-aliased flags first
1058
+ rows.sort((a, b) => {
1059
+ const aS = /(^|\s|,)-[A-Za-z]/.test(a.flags) ? 1 : 0;
1060
+ const bS = /(^|\s|,)-[A-Za-z]/.test(b.flags) ? 1 : 0;
1061
+ return bS - aS || a.flags.localeCompare(b.flags);
1062
+ });
1063
+ const lines = rows
1064
+ .map((r) => {
1065
+ const pad = ' '.repeat(Math.max(2, width - r.flags.length + 2));
1066
+ return ` ${r.flags}${pad}${r.description}`.trimEnd();
1067
+ })
1068
+ .join('\n');
1069
+ return `\n${title}:\n${lines}\n`;
1070
+ };
1071
+ let out = '';
1072
+ // App options (if any)
1073
+ const app = byGroup.get('app');
1074
+ if (app && app.length > 0) {
1075
+ out += renderRows('App options', app);
1076
+ }
1077
+ // Plugin groups sorted by id; suppress self group on the owning command name.
1078
+ const pluginKeys = Array.from(byGroup.keys()).filter((k) => k.startsWith('plugin:'));
1079
+ const currentName = cmd.name();
1080
+ pluginKeys.sort((a, b) => a.localeCompare(b));
1081
+ for (const k of pluginKeys) {
1082
+ const id = k.slice('plugin:'.length) || '(unknown)';
1083
+ const rows = byGroup.get(k) ?? [];
1084
+ if (rows.length > 0 && id !== currentName) {
1085
+ out += renderRows(`Plugin options — ${id}`, rows);
1086
+ }
1087
+ }
1088
+ return out;
1089
+ }
1090
+
1091
+ /**
1092
+ * Compose root/parent help output by inserting grouped sections between
1093
+ * Options and Commands, ensuring a trailing blank line.
1094
+ *
1095
+ * @param base - Base help text produced by Commander.
1096
+ * @param cmd - Command instance whose grouped options should be rendered.
1097
+ * @returns The modified help text with grouped blocks inserted.
1098
+ */
1099
+ function buildHelpInformation(base, cmd) {
1100
+ const groups = renderOptionGroups(cmd);
1101
+ const block = typeof groups === 'string' ? groups.trim() : '';
1102
+ if (!block) {
1103
+ return base.endsWith('\n\n')
1104
+ ? base
1105
+ : base.endsWith('\n')
1106
+ ? `${base}\n`
1107
+ : `${base}\n\n`;
1108
+ }
1109
+ const marker = '\nCommands:';
1110
+ const idx = base.indexOf(marker);
1111
+ let out = base;
1112
+ if (idx >= 0) {
1113
+ const toInsert = groups.startsWith('\n') ? groups : `\n${groups}`;
1114
+ out = `${base.slice(0, idx)}${toInsert}${base.slice(idx)}`;
1115
+ }
1116
+ else {
1117
+ const sep = base.endsWith('\n') || groups.startsWith('\n') ? '' : '\n';
1118
+ out = `${base}${sep}${groups}`;
1119
+ }
1120
+ return out.endsWith('\n\n')
1121
+ ? out
1122
+ : out.endsWith('\n')
1123
+ ? `${out}\n`
1124
+ : `${out}\n\n`;
1125
+ }
1126
+
1127
+ /** src/cliHost/GetDotenvCli/dynamicOptions.ts
1128
+ * Helpers for dynamic option descriptions and evaluation.
1129
+ */
1130
+ /**
1131
+ * Registry for dynamic descriptions keyed by Option (WeakMap for GC safety).
1132
+ */
1133
+ const DYN_DESC = new WeakMap();
1134
+ /**
1135
+ * Create an Option with a dynamic description callback stored in DYN_DESC.
1136
+ */
1137
+ function makeDynamicOption(flags, desc, parser, defaultValue) {
1138
+ const opt = new Option(flags, '');
1139
+ DYN_DESC.set(opt, desc);
1140
+ if (parser) {
1141
+ opt.argParser((value, previous) => parser(value, previous));
1142
+ }
1143
+ if (defaultValue !== undefined)
1144
+ opt.default(defaultValue);
1145
+ // Commander.Option is structurally compatible; help-time wiring is stored in DYN_DESC.
1146
+ return opt;
1147
+ }
1148
+ /**
1149
+ * Evaluate dynamic descriptions across a command tree using the resolved config.
1150
+ */
1151
+ function evaluateDynamicOptions(root, resolved) {
1152
+ const visit = (cmd) => {
1153
+ const arr = cmd.options;
1154
+ for (const o of arr) {
1155
+ const dyn = DYN_DESC.get(o);
1156
+ if (typeof dyn === 'function') {
1157
+ try {
1158
+ const txt = dyn(resolved);
1159
+ // Commander uses Option.description during help rendering.
1160
+ o.description = txt;
1161
+ }
1162
+ catch {
1163
+ /* best-effort; leave as-is */
1164
+ }
1165
+ }
1166
+ }
1167
+ for (const c of cmd.commands)
1168
+ visit(c);
1169
+ };
1170
+ visit(root);
1171
+ }
1172
+
1173
+ /**
1174
+ * Initialize a {@link GetDotenvCli} instance with help configuration and safe defaults.
1175
+ *
1176
+ * @remarks
1177
+ * This is a low-level initializer used by the host constructor to keep `GetDotenvCli.ts`
1178
+ * small and to centralize help/output behavior.
1179
+ *
1180
+ * @param cli - The CLI instance to initialize.
1181
+ * @param headerGetter - Callback returning an optional help header string.
1182
+ */
1183
+ function initializeInstance(cli, headerGetter) {
1184
+ // Configure grouped help: show only base options in default "Options";
1185
+ // subcommands show all of their own options.
1186
+ cli.configureHelp({
1187
+ visibleOptions: (cmd) => {
1188
+ const all = cmd.options;
1189
+ const isRoot = cmd.parent === null;
1190
+ const list = isRoot
1191
+ ? all.filter((opt) => {
1192
+ const group = GROUP_TAG.get(opt);
1193
+ return group === 'base';
1194
+ })
1195
+ : all.slice();
1196
+ // Sort: short-aliased options first, then long-only; stable by flags.
1197
+ const hasShort = (opt) => {
1198
+ const flags = opt.flags;
1199
+ return /(^|\s|,)-[A-Za-z]/.test(flags);
1200
+ };
1201
+ const byFlags = (opt) => opt.flags;
1202
+ list.sort((a, b) => {
1203
+ const aS = hasShort(a) ? 1 : 0;
1204
+ const bS = hasShort(b) ? 1 : 0;
1205
+ return bS - aS || byFlags(a).localeCompare(byFlags(b));
1206
+ });
1207
+ return list;
1208
+ },
1209
+ });
1210
+ // Optional branded header before help text (kept minimal and deterministic).
1211
+ cli.addHelpText('beforeAll', () => {
1212
+ const header = headerGetter();
1213
+ return header && header.length > 0 ? `${header}\n\n` : '';
1214
+ });
1215
+ // Tests-only: suppress process.exit during help/version flows under Vitest.
1216
+ // Unit tests often construct GetDotenvCli directly (bypassing createCli),
1217
+ // so install a local exitOverride when a test environment is detected.
1218
+ const underTests = process.env.GETDOTENV_TEST === '1' ||
1219
+ typeof process.env.VITEST_WORKER_ID === 'string';
1220
+ if (underTests) {
1221
+ cli.exitOverride((err) => {
1222
+ const code = err?.code;
1223
+ if (code === 'commander.helpDisplayed' ||
1224
+ code === 'commander.version' ||
1225
+ code === 'commander.help')
1226
+ return;
1227
+ throw err;
1228
+ });
1229
+ }
1230
+ // Ensure the root has a no-op action so preAction hooks installed by
1231
+ // passOptions() fire for root-only invocations (no subcommand).
1232
+ // Subcommands still take precedence and will not hit this action.
1233
+ // This keeps root-side effects (e.g., --log) working in direct hosts/tests.
1234
+ cli.action(() => {
1235
+ /* no-op */
1236
+ });
1237
+ // PreSubcommand hook: compute a context if absent, without mutating process.env.
1238
+ // The passOptions() helper, when installed, resolves the final context.
1239
+ cli.hook('preSubcommand', async () => {
1240
+ if (cli.hasCtx())
1241
+ return;
1242
+ await cli.resolveAndLoad({ loadProcess: false });
1243
+ });
1244
+ }
1245
+
1246
+ /**
1247
+ * Determine the effective namespace for a child plugin (override \> default).
1248
+ */
1249
+ const effectiveNs = (child) => {
1250
+ const o = child.override;
1251
+ return (o && typeof o.ns === 'string' && o.ns.length > 0 ? o.ns : child.plugin.ns).trim();
1252
+ };
1253
+ const isPromise = (v) => !!v && typeof v.then === 'function';
1254
+ function runInstall(parentCli, plugin) {
1255
+ // Create mount and run setup
1256
+ const mount = parentCli.ns(plugin.ns);
1257
+ const setupRet = plugin.setup(mount);
1258
+ const pending = [];
1259
+ if (isPromise(setupRet))
1260
+ pending.push(setupRet.then(() => undefined));
1261
+ // Enforce sibling uniqueness before creating children
1262
+ const names = new Set();
1263
+ for (const entry of plugin.children) {
1264
+ const ns = effectiveNs(entry);
1265
+ if (names.has(ns)) {
1266
+ const under = mount.name();
1267
+ throw new Error(`Duplicate namespace '${ns}' under '${under || 'root'}'. Override via .use(plugin, { ns: '...' }).`);
1268
+ }
1269
+ names.add(ns);
1270
+ }
1271
+ // Install children (pre-order), synchronously when possible
1272
+ for (const entry of plugin.children) {
1273
+ const childRet = runInstall(mount, entry.plugin);
1274
+ if (isPromise(childRet))
1275
+ pending.push(childRet);
1276
+ }
1277
+ if (pending.length > 0)
1278
+ return Promise.all(pending).then(() => undefined);
1279
+ return;
1280
+ }
1281
+ /**
1282
+ * Install a plugin and its children (pre-order setup phase).
1283
+ * Enforces sibling namespace uniqueness.
1284
+ */
1285
+ function setupPluginTree(cli, plugin) {
1286
+ const ret = runInstall(cli, plugin);
1287
+ return isPromise(ret) ? ret : Promise.resolve();
1288
+ }
1289
+
1290
+ /**
1291
+ * Resolve options strictly and compute the dotenv context via the loader/overlay path.
1292
+ *
1293
+ * @param customOptions - Partial options overlay.
1294
+ * @param plugins - Plugins list for config validation.
1295
+ * @param hostMetaUrl - Import URL for resolving the packaged root.
1296
+ */
1297
+ async function resolveAndComputeContext(customOptions, plugins, hostMetaUrl) {
1298
+ const optionsResolved = await resolveGetDotenvOptions(customOptions);
1299
+ // Strict schema validation
1300
+ getDotenvOptionsSchemaResolved.parse(optionsResolved);
1301
+ const ctx = await computeContext(optionsResolved, plugins, hostMetaUrl);
1302
+ return ctx;
1303
+ }
1304
+
1305
+ /**
1306
+ * Run afterResolve hooks for a plugin tree (parent → children).
1307
+ */
1308
+ async function runAfterResolveTree(cli, plugins, ctx) {
1309
+ const run = async (p) => {
1310
+ if (p.afterResolve)
1311
+ await p.afterResolve(cli, ctx);
1312
+ for (const child of p.children)
1313
+ await run(child.plugin);
1314
+ };
1315
+ for (const p of plugins)
1316
+ await run(p);
1317
+ }
1318
+
1319
+ /**
1320
+ * Temporarily tag options added during a callback as 'app' for grouped help.
1321
+ * Wraps `addOption` on the command instance.
1322
+ */
1323
+ function tagAppOptionsAround(root, setOptionGroup, fn) {
1324
+ const originalAddOption = root.addOption.bind(root);
1325
+ root.addOption = ((opt) => {
1326
+ setOptionGroup(opt, 'app');
1327
+ return originalAddOption(opt);
1328
+ });
1329
+ try {
1330
+ return fn(root);
1331
+ }
1332
+ finally {
1333
+ root.addOption = originalAddOption;
1334
+ }
1335
+ }
1336
+
1337
+ /**
1338
+ * Read the version from the nearest `package.json` relative to the provided import URL.
1339
+ *
1340
+ * @param importMetaUrl - The `import.meta.url` of the calling module.
1341
+ * @returns The version string or undefined if not found.
1342
+ */
1343
+ async function readPkgVersion(importMetaUrl) {
1344
+ if (!importMetaUrl)
1345
+ return undefined;
1346
+ try {
1347
+ const fromUrl = fileURLToPath(importMetaUrl);
1348
+ const pkgDir = await packageDirectory({ cwd: fromUrl });
1349
+ if (!pkgDir)
1350
+ return undefined;
1351
+ const txt = await fs.readFile(`${pkgDir}/package.json`, 'utf-8');
1352
+ const pkg = JSON.parse(txt);
1353
+ return pkg.version ?? undefined;
1354
+ }
1355
+ catch {
1356
+ // best-effort only
1357
+ return undefined;
1358
+ }
1359
+ }
1360
+
1361
+ /** src/cliHost/GetDotenvCli.ts
1362
+ * Plugin-first CLI host for get-dotenv with Commander generics preserved.
1363
+ * Public surface implements GetDotenvCliPublic and provides:
1364
+ * - attachRootOptions (builder-only; no public override wiring)
1365
+ * - resolveAndLoad (strict resolve + context compute)
1366
+ * - getCtx/hasCtx accessors
1367
+ * - ns() for typed subcommand creation with duplicate-name guard
1368
+ * - grouped help rendering with dynamic option descriptions
1369
+ */
1370
+ const HOST_META_URL = import.meta.url;
1371
+ const CTX_SYMBOL = Symbol('GetDotenvCli.ctx');
1372
+ const OPTS_SYMBOL = Symbol('GetDotenvCli.options');
1373
+ const HELP_HEADER_SYMBOL = Symbol('GetDotenvCli.helpHeader');
1374
+ /**
1375
+ * Plugin-first CLI host for get-dotenv. Extends Commander.Command.
1376
+ *
1377
+ * Responsibilities:
1378
+ * - Resolve options strictly and compute dotenv context (resolveAndLoad).
1379
+ * - Expose a stable accessor for the current context (getCtx).
1380
+ * - Provide a namespacing helper (ns).
1381
+ * - Support composable plugins with parent → children install and afterResolve.
1382
+ */
1383
+ class GetDotenvCli extends Command {
1384
+ /** Registered top-level plugins (composition happens via .use()) */
1385
+ _plugins = [];
1386
+ /** One-time installation guard */
1387
+ _installed = false;
1388
+ /** In-flight installation promise to guard against concurrent installs */
1389
+ _installing;
1390
+ /** Optional header line to prepend in help output */
1391
+ [HELP_HEADER_SYMBOL];
1392
+ /** Context/options stored under symbols (typed) */
1393
+ [CTX_SYMBOL];
1394
+ [OPTS_SYMBOL];
1395
+ /**
1396
+ * Create a subcommand using the same subclass, preserving helpers like
1397
+ * dynamicOption on children.
1398
+ */
1399
+ createCommand(name) {
1400
+ // Explicitly construct a GetDotenvCli for children to preserve helpers.
1401
+ return new GetDotenvCli(name);
1402
+ }
1403
+ constructor(alias = 'getdotenv') {
1404
+ super(alias);
1405
+ this.enablePositionalOptions();
1406
+ // Delegate the heavy setup to a helper to keep the constructor lean.
1407
+ initializeInstance(this, () => this[HELP_HEADER_SYMBOL]);
1408
+ }
1409
+ /**
1410
+ * Attach legacy/base root flags to this CLI instance.
1411
+ * Delegates to the pure builder in attachRootOptions.ts.
1412
+ */
1413
+ attachRootOptions(defaults) {
1414
+ const d = (defaults ?? baseRootOptionDefaults);
1415
+ attachRootOptions(this, d);
1416
+ return this;
1417
+ }
1418
+ /**
1419
+ * Resolve options (strict) and compute dotenv context.
1420
+ * Stores the context on the instance under a symbol.
1421
+ *
1422
+ * Options:
1423
+ * - opts.runAfterResolve (default true): when false, skips running plugin
1424
+ * afterResolve hooks. Useful for top-level help rendering to avoid
1425
+ * long-running side-effects while still evaluating dynamic help text.
1426
+ */
1427
+ async resolveAndLoad(customOptions = {}, opts) {
1428
+ const ctx = await resolveAndComputeContext(customOptions,
1429
+ // Pass only plugin instances to the resolver (not entries with overrides)
1430
+ this._plugins.map((e) => e.plugin), HOST_META_URL);
1431
+ // Persist context on the instance for later access.
1432
+ this[CTX_SYMBOL] = ctx;
1433
+ // Ensure plugins are installed exactly once, then run afterResolve.
1434
+ await this.install();
1435
+ if (opts?.runAfterResolve ?? true) {
1436
+ await this._runAfterResolve(ctx);
1437
+ }
1438
+ return ctx;
1439
+ }
1440
+ // Implementation
1441
+ createDynamicOption(flags, desc, parser, defaultValue) {
1442
+ return makeDynamicOption(flags, (c) => desc(c), parser, defaultValue);
1443
+ }
1444
+ /**
1445
+ * Evaluate dynamic descriptions for this command and all descendants using
1446
+ * the provided resolved configuration. Mutates the Option.description in
1447
+ * place so Commander help renders updated text.
1448
+ */
1449
+ evaluateDynamicOptions(resolved) {
1450
+ evaluateDynamicOptions(this, resolved);
1451
+ }
1452
+ /** Internal: climb to the true root (host) command. */
1453
+ _root() {
1454
+ let node = this;
1455
+ while (node.parent) {
1456
+ node = node.parent;
1457
+ }
1458
+ return node;
1459
+ }
1460
+ /**
1461
+ * Retrieve the current invocation context (if any).
1462
+ */
1463
+ getCtx() {
1464
+ let ctx = this[CTX_SYMBOL];
1465
+ if (!ctx) {
1466
+ const root = this._root();
1467
+ ctx = root[CTX_SYMBOL];
1468
+ }
1469
+ if (!ctx) {
1470
+ throw new Error('Dotenv context unavailable. Ensure resolveAndLoad() has been called or the host is wired with passOptions() before invoking commands.');
1471
+ }
1472
+ return ctx;
1473
+ }
1474
+ /**
1475
+ * Check whether a context has been resolved (non-throwing guard).
1476
+ */
1477
+ hasCtx() {
1478
+ if (this[CTX_SYMBOL] !== undefined)
1479
+ return true;
1480
+ const root = this._root();
1481
+ return root[CTX_SYMBOL] !== undefined;
1482
+ }
1483
+ /**
1484
+ * Retrieve the merged root CLI options bag (if set by passOptions()).
1485
+ * Downstream-safe: no generics required.
1486
+ */
1487
+ getOptions() {
1488
+ if (this[OPTS_SYMBOL])
1489
+ return this[OPTS_SYMBOL];
1490
+ const root = this._root();
1491
+ const bag = root[OPTS_SYMBOL];
1492
+ if (bag)
1493
+ return bag;
1494
+ return undefined;
1495
+ }
1496
+ /** Internal: set the merged root options bag for this run. */
1497
+ _setOptionsBag(bag) {
1498
+ this[OPTS_SYMBOL] = bag;
1499
+ }
1500
+ /**
1501
+ * Convenience helper to create a namespaced subcommand with argument inference.
1502
+ * This mirrors Commander generics so downstream chaining stays fully typed.
1503
+ */
1504
+ ns(name) {
1505
+ // Guard against same-level duplicate command names for clearer diagnostics.
1506
+ const exists = this.commands.some((c) => c.name() === name);
1507
+ if (exists) {
1508
+ throw new Error(`Duplicate command name: ${name}`);
1509
+ }
1510
+ return this.command(name);
1511
+ }
1512
+ /**
1513
+ * Tag options added during the provided callback as 'app' for grouped help.
1514
+ * Allows downstream apps to demarcate their root-level options.
1515
+ */
1516
+ tagAppOptions(fn) {
1517
+ return tagAppOptionsAround(this, this.setOptionGroup.bind(this), fn);
1518
+ }
1519
+ /**
1520
+ * Branding helper: set CLI name/description/version and optional help header.
1521
+ * If version is omitted and importMetaUrl is provided, attempts to read the
1522
+ * nearest package.json version (best-effort; non-fatal on failure).
1523
+ */
1524
+ async brand(args) {
1525
+ const { name, description, version, importMetaUrl, helpHeader } = args;
1526
+ if (typeof name === 'string' && name.length > 0)
1527
+ this.name(name);
1528
+ if (typeof description === 'string')
1529
+ this.description(description);
1530
+ const v = version ?? (await readPkgVersion(importMetaUrl));
1531
+ if (v)
1532
+ this.version(v);
1533
+ // Help header:
1534
+ // - If caller provides helpHeader, use it.
1535
+ // - Otherwise, when a version is known, default to "<name> v<version>".
1536
+ if (typeof helpHeader === 'string') {
1537
+ this[HELP_HEADER_SYMBOL] = helpHeader;
1538
+ }
1539
+ else if (v) {
1540
+ const header = `${this.name()} v${v}`;
1541
+ this[HELP_HEADER_SYMBOL] = header;
1542
+ }
1543
+ return this;
1544
+ }
1545
+ /**
1546
+ * Insert grouped plugin/app options between "Options" and "Commands" for
1547
+ * hybrid ordering. Applies to root and any parent command.
1548
+ */
1549
+ helpInformation() {
1550
+ return buildHelpInformation(super.helpInformation(), this);
1551
+ }
1552
+ /**
1553
+ * Public: tag an Option with a display group for help (root/app/plugin:<id>).
1554
+ */
1555
+ setOptionGroup(opt, group) {
1556
+ GROUP_TAG.set(opt, group);
1557
+ }
1558
+ /**
1559
+ * Register a plugin for installation (parent level).
1560
+ * Installation occurs on first resolveAndLoad() (or explicit install()).
1561
+ */
1562
+ use(plugin, override) {
1563
+ this._plugins.push({ plugin, override });
1564
+ return this;
1565
+ }
1566
+ /**
1567
+ * Install all registered plugins in parent → children (pre-order).
1568
+ * Runs only once per CLI instance.
1569
+ */
1570
+ async install() {
1571
+ if (this._installed)
1572
+ return;
1573
+ if (this._installing) {
1574
+ await this._installing;
1575
+ return;
1576
+ }
1577
+ this._installing = (async () => {
1578
+ // Install parent → children with host-created mounts (async-aware).
1579
+ for (const entry of this._plugins) {
1580
+ const p = entry.plugin;
1581
+ await setupPluginTree(this, p);
1582
+ }
1583
+ this._installed = true;
1584
+ })();
1585
+ try {
1586
+ await this._installing;
1587
+ }
1588
+ finally {
1589
+ // leave _installing as resolved; subsequent calls return early via _installed
1590
+ }
1591
+ }
1592
+ /**
1593
+ * Run afterResolve hooks for all plugins (parent → children).
1594
+ */
1595
+ async _runAfterResolve(ctx) {
1596
+ await runAfterResolveTree(this, this._plugins.map((e) => e.plugin), ctx);
1597
+ }
1598
+ }
1599
+
1600
+ /**
1601
+ * Retrieve the merged root options bag from the current command context.
1602
+ * Climbs to the root `GetDotenvCli` instance to access the persisted options.
1603
+ *
1604
+ * @param cmd - The current command instance (thisCommand).
1605
+ * @throws Error if the root is not a GetDotenvCli or options are missing.
1606
+ */
1607
+ const readMergedOptions = (cmd) => {
1608
+ // Climb to the true root
1609
+ let root = cmd;
1610
+ while (root.parent)
1611
+ root = root.parent;
1612
+ // Assert we ended at our host
1613
+ if (!(root instanceof GetDotenvCli)) {
1614
+ throw new Error('readMergedOptions: root command is not a GetDotenvCli.' +
1615
+ 'Ensure your CLI is constructed with GetDotenvCli.');
1616
+ }
1617
+ // Require passOptions() to have persisted the bag
1618
+ const bag = root.getOptions();
1619
+ if (!bag || typeof bag !== 'object') {
1620
+ throw new Error('readMergedOptions: merged options are unavailable. ' +
1621
+ 'Call .passOptions() on the host before parsing.');
1622
+ }
1623
+ return bag;
1624
+ };
1625
+
1626
+ export { GetDotenvCli as G, defineDynamic as a, defineGetDotenvConfig as b, getDotenvCliOptions2Options as c, definePlugin as d, baseRootOptionDefaults as e, redactDisplay as f, getDotenv as g, redactObject as h, interpolateDeep as i, defaultsDeep as j, attachRootOptions as k, redactTriple as l, maybeWarnEntropy as m, readMergedOptions as r };