@karmaniverous/get-dotenv 5.2.3 → 5.2.5

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 (43) hide show
  1. package/README.md +5 -5
  2. package/dist/cliHost.cjs +422 -21
  3. package/dist/cliHost.d.cts +95 -8
  4. package/dist/cliHost.d.mts +95 -8
  5. package/dist/cliHost.d.ts +95 -8
  6. package/dist/cliHost.mjs +423 -22
  7. package/dist/getdotenv.cli.mjs +10 -6
  8. package/dist/index.cjs +11 -6
  9. package/dist/index.d.cts +19 -1
  10. package/dist/index.d.mts +19 -1
  11. package/dist/index.d.ts +19 -1
  12. package/dist/index.mjs +11 -7
  13. package/dist/plugins-aws.cjs +7 -0
  14. package/dist/plugins-aws.d.cts +99 -162
  15. package/dist/plugins-aws.d.mts +99 -162
  16. package/dist/plugins-aws.d.ts +99 -162
  17. package/dist/plugins-aws.mjs +7 -0
  18. package/dist/plugins-batch.cjs +7 -0
  19. package/dist/plugins-batch.d.cts +99 -162
  20. package/dist/plugins-batch.d.mts +99 -162
  21. package/dist/plugins-batch.d.ts +99 -162
  22. package/dist/plugins-batch.mjs +7 -0
  23. package/dist/plugins-cmd.cjs +9 -5
  24. package/dist/plugins-cmd.d.cts +99 -162
  25. package/dist/plugins-cmd.d.mts +99 -162
  26. package/dist/plugins-cmd.d.ts +99 -162
  27. package/dist/plugins-cmd.mjs +9 -5
  28. package/dist/plugins-demo.cjs +7 -0
  29. package/dist/plugins-demo.d.cts +99 -162
  30. package/dist/plugins-demo.d.mts +99 -162
  31. package/dist/plugins-demo.d.ts +99 -162
  32. package/dist/plugins-demo.mjs +7 -0
  33. package/dist/plugins-init.cjs +7 -0
  34. package/dist/plugins-init.d.cts +99 -162
  35. package/dist/plugins-init.d.mts +99 -162
  36. package/dist/plugins-init.d.ts +99 -162
  37. package/dist/plugins-init.mjs +7 -0
  38. package/dist/plugins.cjs +9 -5
  39. package/dist/plugins.d.cts +99 -162
  40. package/dist/plugins.d.mts +99 -162
  41. package/dist/plugins.d.ts +99 -162
  42. package/dist/plugins.mjs +9 -5
  43. package/package.json +1 -1
package/README.md CHANGED
@@ -12,11 +12,11 @@ Load environment variables with a cascade of environment-aware dotenv files. You
12
12
 
13
13
  ✅ Asynchronously load environment variables from multiple dotenv files.
14
14
 
15
- ✅ Segregate variables info distinct files:
15
+ ✅ Segregate variables into distinct files:
16
16
 
17
- - Public files (e.g. `.env`, `env.dev`, `env.test`) are synced with your git repository.
18
- - Private files (e.g. `.env.local`, `env.dev.local`, `env.test.local`) are protected by `.gitignore`.
19
- - Global files (e.g. `.env`, `env.local`) apply to all environments.
17
+ - Public files (e.g. `.env`, `.env.dev`, `.env.test`) are synced with your git repository.
18
+ - Private files (e.g. `.env.local`, `.env.dev.local`, `.env.test.local`) are protected by `.gitignore`.
19
+ - Global files (e.g. `.env`, `.env.local`) apply to all environments.
20
20
  - Env files (e.g. `.env.dev`, `.env.dev.local`, `.env.test`, `.env.test.local`) apply to a specific environment.
21
21
  - [Dynamic files](#dynamic-processing) (`.env.js`) export logic that dynamically & progressively generates new variables or overrides current ones.
22
22
 
@@ -278,7 +278,7 @@ You can also use `getdotenv` from the command line:
278
278
  #
279
279
  # Commands:
280
280
  # batch [options] Batch shell commands across multiple working directories.
281
- # cmd Batch execute command according to the --shell option, conflicts with --command option (default command)
281
+ # cmd Execute command according to the --shell option, conflicts with --command option (default command)
282
282
  # help [command] display help for command
283
283
  ```
284
284
 
package/dist/cliHost.cjs CHANGED
@@ -12,27 +12,6 @@ var dotenv = require('dotenv');
12
12
  var crypto = require('crypto');
13
13
 
14
14
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
15
- /**
16
- * Define a GetDotenv CLI plugin with compositional helpers.
17
- *
18
- * @example
19
- * const parent = definePlugin(\{ id: 'p', setup(cli) \{ /* ... *\/ \} \})
20
- * .use(childA)
21
- * .use(childB);
22
- */
23
- const definePlugin = (spec) => {
24
- const { children = [], ...rest } = spec;
25
- const plugin = {
26
- ...rest,
27
- children: [...children],
28
- use(child) {
29
- this.children.push(child);
30
- return this;
31
- },
32
- };
33
- return plugin;
34
- };
35
-
36
15
  // Base root CLI defaults (shared; kept untyped here to avoid cross-layer deps).
37
16
  const baseRootOptionDefaults = {
38
17
  dotenvToken: '.env',
@@ -589,6 +568,22 @@ const dotenvExpandAll = (values = {}, options = {}) => Object.keys(values).reduc
589
568
  });
590
569
  return acc;
591
570
  }, {});
571
+ /**
572
+ * Recursively expands environment variables in a string using `process.env` as
573
+ * the expansion reference. Variables may be presented with optional default as
574
+ * `$VAR[:default]` or `${VAR[:default]}`. Unknown variables will expand to an
575
+ * empty string.
576
+ *
577
+ * @param value - The string to expand.
578
+ * @returns The expanded string.
579
+ *
580
+ * @example
581
+ * ```ts
582
+ * process.env.FOO = 'bar';
583
+ * dotenvExpandFromProcessEnv('Hello $FOO'); // "Hello bar"
584
+ * ```
585
+ */
586
+ const dotenvExpandFromProcessEnv = (value) => dotenvExpand(value, process.env);
592
587
 
593
588
  const applyKv = (current, kv) => {
594
589
  if (!kv || Object.keys(kv).length === 0)
@@ -1439,6 +1434,412 @@ class GetDotenvCli extends commander.Command {
1439
1434
  }
1440
1435
  }
1441
1436
 
1437
+ /**
1438
+ * Validate a composed env against config-provided validation surfaces.
1439
+ * Precedence for validation definitions:
1440
+ * project.local -\> project.public -\> packaged
1441
+ *
1442
+ * Behavior:
1443
+ * - If a JS/TS `schema` is present, use schema.safeParse(finalEnv).
1444
+ * - Else if `requiredKeys` is present, check presence (value !== undefined).
1445
+ * - Returns a flat list of issue strings; caller decides warn vs fail.
1446
+ */
1447
+ const validateEnvAgainstSources = (finalEnv, sources) => {
1448
+ const pick = (getter) => {
1449
+ const pl = sources.project?.local;
1450
+ const pp = sources.project?.public;
1451
+ const pk = sources.packaged;
1452
+ return ((pl && getter(pl)) ||
1453
+ (pp && getter(pp)) ||
1454
+ (pk && getter(pk)) ||
1455
+ undefined);
1456
+ };
1457
+ const schema = pick((cfg) => cfg['schema']);
1458
+ if (schema &&
1459
+ typeof schema.safeParse === 'function') {
1460
+ try {
1461
+ const parsed = schema.safeParse(finalEnv);
1462
+ if (!parsed.success) {
1463
+ // Try to render zod-style issues when available.
1464
+ const err = parsed.error;
1465
+ const issues = Array.isArray(err.issues) && err.issues.length > 0
1466
+ ? err.issues.map((i) => {
1467
+ const path = Array.isArray(i.path) ? i.path.join('.') : '';
1468
+ const msg = i.message ?? 'Invalid value';
1469
+ return path ? `[schema] ${path}: ${msg}` : `[schema] ${msg}`;
1470
+ })
1471
+ : ['[schema] validation failed'];
1472
+ return issues;
1473
+ }
1474
+ return [];
1475
+ }
1476
+ catch {
1477
+ // If schema invocation fails, surface a single diagnostic.
1478
+ return [
1479
+ '[schema] validation failed (unable to execute schema.safeParse)',
1480
+ ];
1481
+ }
1482
+ }
1483
+ const requiredKeys = pick((cfg) => cfg['requiredKeys']);
1484
+ if (Array.isArray(requiredKeys) && requiredKeys.length > 0) {
1485
+ const missing = requiredKeys.filter((k) => finalEnv[k] === undefined);
1486
+ if (missing.length > 0) {
1487
+ return missing.map((k) => `[requiredKeys] missing: ${k}`);
1488
+ }
1489
+ }
1490
+ return [];
1491
+ };
1492
+
1493
+ /**
1494
+ * Attach legacy root flags to a Commander program.
1495
+ * Uses provided defaults to render help labels without coupling to generators.
1496
+ */
1497
+ const attachRootOptions = (program, defaults, opts) => {
1498
+ // Install temporary wrappers to tag all options added here as "base".
1499
+ const GROUP = 'base';
1500
+ const tagLatest = (cmd, group) => {
1501
+ const optsArr = cmd.options;
1502
+ if (Array.isArray(optsArr) && optsArr.length > 0) {
1503
+ const last = optsArr[optsArr.length - 1];
1504
+ last.__group = group;
1505
+ }
1506
+ };
1507
+ const originalAddOption = program.addOption.bind(program);
1508
+ const originalOption = program.option.bind(program);
1509
+ program.addOption = function patchedAdd(opt) {
1510
+ // Tag before adding, in case consumers inspect the Option directly.
1511
+ opt.__group = GROUP;
1512
+ const ret = originalAddOption(opt);
1513
+ return ret;
1514
+ };
1515
+ program.option = function patchedOption(...args) {
1516
+ const ret = originalOption(...args);
1517
+ tagLatest(this, GROUP);
1518
+ return ret;
1519
+ };
1520
+ const { defaultEnv, dotenvToken, dynamicPath, env, excludeDynamic, excludeEnv, excludeGlobal, excludePrivate, excludePublic, loadProcess, log, outputPath, paths, pathsDelimiter, pathsDelimiterPattern, privateToken, scripts, shell, varsAssignor, varsAssignorPattern, varsDelimiter, varsDelimiterPattern, } = defaults ?? {};
1521
+ const va = typeof defaults?.varsAssignor === 'string' ? defaults.varsAssignor : '=';
1522
+ const vd = typeof defaults?.varsDelimiter === 'string' ? defaults.varsDelimiter : ' ';
1523
+ // Build initial chain.
1524
+ let p = program
1525
+ .enablePositionalOptions()
1526
+ .passThroughOptions()
1527
+ .option('-e, --env <string>', `target environment (dotenv-expanded)`, dotenvExpandFromProcessEnv, env);
1528
+ p = p.option('-v, --vars <string>', `extra variables expressed as delimited key-value pairs (dotenv-expanded): ${[
1529
+ ['KEY1', 'VAL1'],
1530
+ ['KEY2', 'VAL2'],
1531
+ ]
1532
+ .map((v) => v.join(va))
1533
+ .join(vd)}`, dotenvExpandFromProcessEnv);
1534
+ // Optional legacy root command flag (kept for generated CLI compatibility).
1535
+ // Default is OFF; the generator opts in explicitly.
1536
+ if (opts?.includeCommandOption === true) {
1537
+ p = p.option('-c, --command <string>', 'command executed according to the --shell option, conflicts with cmd subcommand (dotenv-expanded)', dotenvExpandFromProcessEnv);
1538
+ }
1539
+ p = p
1540
+ .option('-o, --output-path <string>', 'consolidated output file (dotenv-expanded)', dotenvExpandFromProcessEnv, outputPath)
1541
+ .addOption(new commander.Option('-s, --shell [string]', (() => {
1542
+ let defaultLabel = '';
1543
+ if (shell !== undefined) {
1544
+ if (typeof shell === 'boolean') {
1545
+ defaultLabel = ' (default OS shell)';
1546
+ }
1547
+ else if (typeof shell === 'string') {
1548
+ // Safe string interpolation
1549
+ defaultLabel = ` (default ${shell})`;
1550
+ }
1551
+ }
1552
+ return `command execution shell, no argument for default OS shell or provide shell string${defaultLabel}`;
1553
+ })()).conflicts('shellOff'))
1554
+ .addOption(new commander.Option('-S, --shell-off', `command execution shell OFF${!shell ? ' (default)' : ''}`).conflicts('shell'))
1555
+ .addOption(new commander.Option('-p, --load-process', `load variables to process.env ON${loadProcess ? ' (default)' : ''}`).conflicts('loadProcessOff'))
1556
+ .addOption(new commander.Option('-P, --load-process-off', `load variables to process.env OFF${!loadProcess ? ' (default)' : ''}`).conflicts('loadProcess'))
1557
+ .addOption(new commander.Option('-a, --exclude-all', `exclude all dotenv variables from loading ON${excludeDynamic &&
1558
+ ((excludeEnv && excludeGlobal) || (excludePrivate && excludePublic))
1559
+ ? ' (default)'
1560
+ : ''}`).conflicts('excludeAllOff'))
1561
+ .addOption(new commander.Option('-A, --exclude-all-off', `exclude all dotenv variables from loading OFF (default)`).conflicts('excludeAll'))
1562
+ .addOption(new commander.Option('-z, --exclude-dynamic', `exclude dynamic dotenv variables from loading ON${excludeDynamic ? ' (default)' : ''}`).conflicts('excludeDynamicOff'))
1563
+ .addOption(new commander.Option('-Z, --exclude-dynamic-off', `exclude dynamic dotenv variables from loading OFF${!excludeDynamic ? ' (default)' : ''}`).conflicts('excludeDynamic'))
1564
+ .addOption(new commander.Option('-n, --exclude-env', `exclude environment-specific dotenv variables from loading${excludeEnv ? ' (default)' : ''}`).conflicts('excludeEnvOff'))
1565
+ .addOption(new commander.Option('-N, --exclude-env-off', `exclude environment-specific dotenv variables from loading OFF${!excludeEnv ? ' (default)' : ''}`).conflicts('excludeEnv'))
1566
+ .addOption(new commander.Option('-g, --exclude-global', `exclude global dotenv variables from loading ON${excludeGlobal ? ' (default)' : ''}`).conflicts('excludeGlobalOff'))
1567
+ .addOption(new commander.Option('-G, --exclude-global-off', `exclude global dotenv variables from loading OFF${!excludeGlobal ? ' (default)' : ''}`).conflicts('excludeGlobal'))
1568
+ .addOption(new commander.Option('-r, --exclude-private', `exclude private dotenv variables from loading ON${excludePrivate ? ' (default)' : ''}`).conflicts('excludePrivateOff'))
1569
+ .addOption(new commander.Option('-R, --exclude-private-off', `exclude private dotenv variables from loading OFF${!excludePrivate ? ' (default)' : ''}`).conflicts('excludePrivate'))
1570
+ .addOption(new commander.Option('-u, --exclude-public', `exclude public dotenv variables from loading ON${excludePublic ? ' (default)' : ''}`).conflicts('excludePublicOff'))
1571
+ .addOption(new commander.Option('-U, --exclude-public-off', `exclude public dotenv variables from loading OFF${!excludePublic ? ' (default)' : ''}`).conflicts('excludePublic'))
1572
+ .addOption(new commander.Option('-l, --log', `console log loaded variables ON${log ? ' (default)' : ''}`).conflicts('logOff'))
1573
+ .addOption(new commander.Option('-L, --log-off', `console log loaded variables OFF${!log ? ' (default)' : ''}`).conflicts('log'))
1574
+ .option('--capture', 'capture child process stdio for commands (tests/CI)')
1575
+ .option('--redact', 'mask secret-like values in logs/trace (presentation-only)')
1576
+ .option('--default-env <string>', 'default target environment', dotenvExpandFromProcessEnv, defaultEnv)
1577
+ .option('--dotenv-token <string>', 'dotenv-expanded token indicating a dotenv file', dotenvExpandFromProcessEnv, dotenvToken)
1578
+ .option('--dynamic-path <string>', 'dynamic variables path (.js or .ts; .ts is auto-compiled when esbuild is available, otherwise precompile)', dotenvExpandFromProcessEnv, dynamicPath)
1579
+ .option('--paths <string>', 'dotenv-expanded delimited list of paths to dotenv directory', dotenvExpandFromProcessEnv, paths)
1580
+ .option('--paths-delimiter <string>', 'paths delimiter string', pathsDelimiter)
1581
+ .option('--paths-delimiter-pattern <string>', 'paths delimiter regex pattern', pathsDelimiterPattern)
1582
+ .option('--private-token <string>', 'dotenv-expanded token indicating private variables', dotenvExpandFromProcessEnv, privateToken)
1583
+ .option('--vars-delimiter <string>', 'vars delimiter string', varsDelimiter)
1584
+ .option('--vars-delimiter-pattern <string>', 'vars delimiter regex pattern', varsDelimiterPattern)
1585
+ .option('--vars-assignor <string>', 'vars assignment operator string', varsAssignor)
1586
+ .option('--vars-assignor-pattern <string>', 'vars assignment operator regex pattern', varsAssignorPattern)
1587
+ // Hidden scripts pipe-through (stringified)
1588
+ .addOption(new commander.Option('--scripts <string>')
1589
+ .default(JSON.stringify(scripts))
1590
+ .hideHelp());
1591
+ // Diagnostics: opt-in tracing; optional variadic keys after the flag.
1592
+ p = p.option('--trace [keys...]', 'emit diagnostics for child env composition (optional keys)');
1593
+ // Validation: strict mode fails on env validation issues (warn by default).
1594
+ p = p.option('--strict', 'fail on env validation errors (schema/requiredKeys)');
1595
+ // Entropy diagnostics (presentation-only)
1596
+ p = p
1597
+ .addOption(new commander.Option('--entropy-warn', 'enable entropy warnings (default on)').conflicts('entropyWarnOff'))
1598
+ .addOption(new commander.Option('--entropy-warn-off', 'disable entropy warnings').conflicts('entropyWarn'))
1599
+ .option('--entropy-threshold <number>', 'entropy bits/char threshold (default 3.8)')
1600
+ .option('--entropy-min-length <number>', 'min length to examine for entropy (default 16)')
1601
+ .option('--entropy-whitelist <pattern...>', 'suppress entropy warnings when key matches any regex pattern')
1602
+ .option('--redact-pattern <pattern...>', 'additional key-match regex patterns to trigger redaction');
1603
+ // Restore original methods to avoid tagging future additions outside base.
1604
+ program.addOption = originalAddOption;
1605
+ program.option = originalOption;
1606
+ return p;
1607
+ };
1608
+
1609
+ /**
1610
+ * Resolve a tri-state optional boolean flag under exactOptionalPropertyTypes.
1611
+ * - If the user explicitly enabled the flag, return true.
1612
+ * - If the user explicitly disabled (the "...-off" variant), return undefined (unset).
1613
+ * - Otherwise, adopt the default (true → set; false/undefined → unset).
1614
+ *
1615
+ * @param exclude - The "on" flag value as parsed by Commander.
1616
+ * @param excludeOff - The "off" toggle (present when specified) as parsed by Commander.
1617
+ * @param defaultValue - The generator default to adopt when no explicit toggle is present.
1618
+ * @returns boolean | undefined — use `undefined` to indicate "unset" (do not emit).
1619
+ *
1620
+ * @example
1621
+ * ```ts
1622
+ * resolveExclusion(undefined, undefined, true); // => true
1623
+ * ```
1624
+ */
1625
+ const resolveExclusion = (exclude, excludeOff, defaultValue) => exclude ? true : excludeOff ? undefined : defaultValue ? true : undefined;
1626
+ /**
1627
+ * Resolve an optional flag with "--exclude-all" overrides.
1628
+ * If excludeAll is set and the individual "...-off" is not, force true.
1629
+ * If excludeAllOff is set and the individual flag is not explicitly set, unset.
1630
+ * Otherwise, adopt the default (true → set; false/undefined → unset).
1631
+ *
1632
+ * @param exclude - Individual include/exclude flag.
1633
+ * @param excludeOff - Individual "...-off" flag.
1634
+ * @param defaultValue - Default for the individual flag.
1635
+ * @param excludeAll - Global "exclude-all" flag.
1636
+ * @param excludeAllOff - Global "exclude-all-off" flag.
1637
+ *
1638
+ * @example
1639
+ * resolveExclusionAll(undefined, undefined, false, true, undefined) =\> true
1640
+ */
1641
+ const resolveExclusionAll = (exclude, excludeOff, defaultValue, excludeAll, excludeAllOff) =>
1642
+ // Order of precedence:
1643
+ // 1) Individual explicit "on" wins outright.
1644
+ // 2) Individual explicit "off" wins over any global.
1645
+ // 3) Global exclude-all forces true when not explicitly turned off.
1646
+ // 4) Global exclude-all-off unsets when the individual wasn't explicitly enabled.
1647
+ // 5) Fall back to the default (true => set; false/undefined => unset).
1648
+ (() => {
1649
+ // Individual "on"
1650
+ if (exclude === true)
1651
+ return true;
1652
+ // Individual "off"
1653
+ if (excludeOff === true)
1654
+ return undefined;
1655
+ // Global "exclude-all" ON (unless explicitly turned off)
1656
+ if (excludeAll === true)
1657
+ return true;
1658
+ // Global "exclude-all-off" (unless explicitly enabled)
1659
+ if (excludeAllOff === true)
1660
+ return undefined;
1661
+ // Default
1662
+ return defaultValue ? true : undefined;
1663
+ })();
1664
+ /**
1665
+ * exactOptionalPropertyTypes-safe setter for optional boolean flags:
1666
+ * delete when undefined; assign when defined — without requiring an index signature on T.
1667
+ *
1668
+ * @typeParam T - Target object type.
1669
+ * @param obj - The object to write to.
1670
+ * @param key - The optional boolean property key of {@link T}.
1671
+ * @param value - The value to set or `undefined` to unset.
1672
+ *
1673
+ * @remarks
1674
+ * Writes through a local `Record<string, unknown>` view to avoid requiring an index signature on {@link T}.
1675
+ */
1676
+ const setOptionalFlag = (obj, key, value) => {
1677
+ const target = obj;
1678
+ const k = key;
1679
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
1680
+ if (value === undefined)
1681
+ delete target[k];
1682
+ else
1683
+ target[k] = value;
1684
+ };
1685
+
1686
+ /**
1687
+ * Merge and normalize raw Commander options (current + parent + defaults)
1688
+ * into a GetDotenvCliOptions-like object. Types are intentionally wide to
1689
+ * avoid cross-layer coupling; callers may cast as needed.
1690
+ */
1691
+ const resolveCliOptions = (rawCliOptions, defaults, parentJson) => {
1692
+ const parent = typeof parentJson === 'string' && parentJson.length > 0
1693
+ ? JSON.parse(parentJson)
1694
+ : undefined;
1695
+ const { command, debugOff, excludeAll, excludeAllOff, excludeDynamicOff, excludeEnvOff, excludeGlobalOff, excludePrivateOff, excludePublicOff, loadProcessOff, logOff, entropyWarn, entropyWarnOff, scripts, shellOff, ...rest } = rawCliOptions;
1696
+ const current = { ...rest };
1697
+ if (typeof scripts === 'string') {
1698
+ try {
1699
+ current.scripts = JSON.parse(scripts);
1700
+ }
1701
+ catch {
1702
+ // ignore parse errors; leave scripts undefined
1703
+ }
1704
+ }
1705
+ const merged = defaultsDeep({}, defaults, parent ?? {}, current);
1706
+ const d = defaults;
1707
+ setOptionalFlag(merged, 'debug', resolveExclusion(merged.debug, debugOff, d.debug));
1708
+ setOptionalFlag(merged, 'excludeDynamic', resolveExclusionAll(merged.excludeDynamic, excludeDynamicOff, d.excludeDynamic, excludeAll, excludeAllOff));
1709
+ setOptionalFlag(merged, 'excludeEnv', resolveExclusionAll(merged.excludeEnv, excludeEnvOff, d.excludeEnv, excludeAll, excludeAllOff));
1710
+ setOptionalFlag(merged, 'excludeGlobal', resolveExclusionAll(merged.excludeGlobal, excludeGlobalOff, d.excludeGlobal, excludeAll, excludeAllOff));
1711
+ setOptionalFlag(merged, 'excludePrivate', resolveExclusionAll(merged.excludePrivate, excludePrivateOff, d.excludePrivate, excludeAll, excludeAllOff));
1712
+ setOptionalFlag(merged, 'excludePublic', resolveExclusionAll(merged.excludePublic, excludePublicOff, d.excludePublic, excludeAll, excludeAllOff));
1713
+ setOptionalFlag(merged, 'log', resolveExclusion(merged.log, logOff, d.log));
1714
+ setOptionalFlag(merged, 'loadProcess', resolveExclusion(merged.loadProcess, loadProcessOff, d.loadProcess));
1715
+ // warnEntropy (tri-state)
1716
+ setOptionalFlag(merged, 'warnEntropy', resolveExclusion(merged.warnEntropy, entropyWarnOff, d.warnEntropy));
1717
+ // Normalize shell for predictability: explicit default shell per OS.
1718
+ const defaultShell = process.platform === 'win32' ? 'powershell.exe' : '/bin/bash';
1719
+ let resolvedShell = merged.shell;
1720
+ if (shellOff)
1721
+ resolvedShell = false;
1722
+ else if (resolvedShell === true || resolvedShell === undefined) {
1723
+ resolvedShell = defaultShell;
1724
+ }
1725
+ else if (typeof resolvedShell !== 'string' &&
1726
+ typeof defaults.shell === 'string') {
1727
+ resolvedShell = defaults.shell;
1728
+ }
1729
+ merged.shell = resolvedShell;
1730
+ const cmd = typeof command === 'string' ? command : undefined;
1731
+ return cmd !== undefined ? { merged, command: cmd } : { merged };
1732
+ };
1733
+
1734
+ GetDotenvCli.prototype.attachRootOptions = function (defaults, opts) {
1735
+ const d = (defaults ?? baseRootOptionDefaults);
1736
+ attachRootOptions(this, d, opts);
1737
+ return this;
1738
+ };
1739
+ GetDotenvCli.prototype.passOptions = function (defaults) {
1740
+ const d = (defaults ?? baseRootOptionDefaults);
1741
+ this.hook('preSubcommand', async (thisCommand) => {
1742
+ const raw = thisCommand.opts();
1743
+ const { merged } = resolveCliOptions(raw, d, process.env.getDotenvCliOptions);
1744
+ // Persist merged options for nested invocations (batch exec).
1745
+ thisCommand.getDotenvCliOptions =
1746
+ merged;
1747
+ // Also store on the host for downstream ergonomic accessors.
1748
+ this._setOptionsBag(merged);
1749
+ // Build service options and compute context (always-on config loader path).
1750
+ const serviceOptions = getDotenvCliOptions2Options(merged);
1751
+ await this.resolveAndLoad(serviceOptions);
1752
+ // Global validation: once after Phase C using config sources.
1753
+ try {
1754
+ const ctx = this.getCtx();
1755
+ const dotenv = (ctx?.dotenv ?? {});
1756
+ const sources = await resolveGetDotenvConfigSources((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cliHost.cjs', document.baseURI).href)));
1757
+ const issues = validateEnvAgainstSources(dotenv, sources);
1758
+ if (Array.isArray(issues) && issues.length > 0) {
1759
+ const logger = (merged.logger ??
1760
+ console);
1761
+ const emit = logger.error ?? logger.log;
1762
+ issues.forEach((m) => {
1763
+ emit(m);
1764
+ });
1765
+ if (merged.strict) {
1766
+ // Deterministic failure under strict mode
1767
+ process.exit(1);
1768
+ }
1769
+ }
1770
+ }
1771
+ catch {
1772
+ // Be tolerant: validation errors reported above; unexpected failures here
1773
+ // should not crash non-strict flows.
1774
+ }
1775
+ });
1776
+ // Also handle root-level flows (no subcommand) so option-aliases can run
1777
+ // with the same merged options and context without duplicating logic.
1778
+ this.hook('preAction', async (thisCommand) => {
1779
+ const raw = thisCommand.opts();
1780
+ const { merged } = resolveCliOptions(raw, d, process.env.getDotenvCliOptions);
1781
+ thisCommand.getDotenvCliOptions =
1782
+ merged;
1783
+ this._setOptionsBag(merged);
1784
+ // Avoid duplicate heavy work if a context is already present.
1785
+ if (!this.getCtx()) {
1786
+ const serviceOptions = getDotenvCliOptions2Options(merged);
1787
+ await this.resolveAndLoad(serviceOptions);
1788
+ try {
1789
+ const ctx = this.getCtx();
1790
+ const dotenv = (ctx?.dotenv ?? {});
1791
+ const sources = await resolveGetDotenvConfigSources((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cliHost.cjs', document.baseURI).href)));
1792
+ const issues = validateEnvAgainstSources(dotenv, sources);
1793
+ if (Array.isArray(issues) && issues.length > 0) {
1794
+ const logger = (merged
1795
+ .logger ?? console);
1796
+ const emit = logger.error ?? logger.log;
1797
+ issues.forEach((m) => {
1798
+ emit(m);
1799
+ });
1800
+ if (merged.strict) {
1801
+ process.exit(1);
1802
+ }
1803
+ }
1804
+ }
1805
+ catch {
1806
+ // Tolerate validation side-effects in non-strict mode
1807
+ }
1808
+ }
1809
+ });
1810
+ return this;
1811
+ };
1812
+
1813
+ /** src/cliHost/definePlugin.ts
1814
+ * Plugin contracts for the GetDotenv CLI host.
1815
+ *
1816
+ * This module exposes a structural public interface for the host that plugins
1817
+ * should use (GetDotenvCliPublic). Using a structural type at the seam avoids
1818
+ * nominal class identity issues (private fields) in downstream consumers.
1819
+ */
1820
+ /**
1821
+ * Define a GetDotenv CLI plugin with compositional helpers.
1822
+ *
1823
+ * @example
1824
+ * const parent = definePlugin(\{ id: 'p', setup(cli) \{ /* ... *\/ \} \})
1825
+ * .use(childA)
1826
+ * .use(childB);
1827
+ */
1828
+ const definePlugin = (spec) => {
1829
+ const { children = [], ...rest } = spec;
1830
+ const plugin = {
1831
+ ...rest,
1832
+ children: [...children],
1833
+ use(child) {
1834
+ this.children.push(child);
1835
+ return this;
1836
+ },
1837
+ };
1838
+ return plugin;
1839
+ };
1840
+
1841
+ // Ensure attachRootOptions() and passOptions() are available whenever the
1842
+ // /cliHost subpath is imported (unconditional for downstream hosts).
1442
1843
  /**
1443
1844
  * Helper to retrieve the merged root options bag from any action handler
1444
1845
  * that only has access to thisCommand. Avoids structural casts.
@@ -1,6 +1,47 @@
1
1
  import { Command } from 'commander';
2
2
  import { ZodType } from 'zod';
3
3
 
4
+ /**
5
+ * Minimal root options shape shared by CLI and generator layers.
6
+ * Keep keys optional to respect exactOptionalPropertyTypes semantics.
7
+ */
8
+ type RootOptionsShape = {
9
+ env?: string;
10
+ vars?: string;
11
+ command?: string;
12
+ outputPath?: string;
13
+ shell?: string | boolean;
14
+ loadProcess?: boolean;
15
+ excludeAll?: boolean;
16
+ excludeDynamic?: boolean;
17
+ excludeEnv?: boolean;
18
+ excludeGlobal?: boolean;
19
+ excludePrivate?: boolean;
20
+ excludePublic?: boolean;
21
+ log?: boolean;
22
+ debug?: boolean;
23
+ capture?: boolean;
24
+ strict?: boolean;
25
+ redact?: boolean;
26
+ warnEntropy?: boolean;
27
+ entropyThreshold?: number;
28
+ entropyMinLength?: number;
29
+ entropyWhitelist?: string[];
30
+ redactPatterns?: string[];
31
+ defaultEnv?: string;
32
+ dotenvToken?: string;
33
+ dynamicPath?: string;
34
+ trace?: boolean | string[];
35
+ paths?: string;
36
+ pathsDelimiter?: string;
37
+ pathsDelimiterPattern?: string;
38
+ privateToken?: string;
39
+ varsDelimiter?: string;
40
+ varsDelimiterPattern?: string;
41
+ varsAssignor?: string;
42
+ varsAssignorPattern?: string;
43
+ scripts?: ScriptsTable;
44
+ };
4
45
  /**
5
46
  * Scripts table shape (configurable shell type).
6
47
  */
@@ -9,6 +50,27 @@ type ScriptsTable<TShell extends string | boolean = string | boolean> = Record<s
9
50
  shell?: TShell;
10
51
  }>;
11
52
 
53
+ /**
54
+ * Adapter-layer augmentation: add chainable helpers to GetDotenvCli without
55
+ * coupling the core host to cliCore. Importing this module has side effects:
56
+ * it extends the prototype and merges types for consumers.
57
+ */
58
+ declare module '../cliHost/GetDotenvCli' {
59
+ interface GetDotenvCli {
60
+ /**
61
+ * Attach legacy root flags to this CLI instance. Defaults come from
62
+ * baseRootOptionDefaults when none are provided. */
63
+ attachRootOptions(defaults?: Partial<RootOptionsShape>, opts?: {
64
+ includeCommandOption?: boolean;
65
+ }): this;
66
+ /**
67
+ * Install a preSubcommand hook that merges CLI flags (including parent
68
+ * round-trip) and resolves the dotenv context before executing actions.
69
+ * Defaults come from baseRootOptionDefaults when none are provided.
70
+ */ passOptions(defaults?: Partial<RootOptionsShape>): this;
71
+ }
72
+ }
73
+
12
74
  /**
13
75
  * A minimal representation of an environment key/value mapping.
14
76
  * Values may be `undefined` to represent "unset". */ type ProcessEnv = Record<string, string | undefined>;
@@ -269,17 +331,41 @@ declare class GetDotenvCli<TOptions extends GetDotenvOptions = GetDotenvOptions>
269
331
  private _runAfterResolve;
270
332
  }
271
333
 
272
- /** Public plugin contract used by the GetDotenv CLI host. */ interface GetDotenvCliPlugin {
273
- id?: string /**
274
- * Setup phase: register commands and wiring on the provided CLI instance. * Runs parent → children (pre-order).
275
- */;
276
- setup: (cli: GetDotenvCli) => void | Promise<void>;
334
+ /** src/cliHost/definePlugin.ts
335
+ * Plugin contracts for the GetDotenv CLI host.
336
+ *
337
+ * This module exposes a structural public interface for the host that plugins
338
+ * should use (GetDotenvCliPublic). Using a structural type at the seam avoids
339
+ * nominal class identity issues (private fields) in downstream consumers.
340
+ */
341
+
342
+ /**
343
+ * Structural public interface for the host exposed to plugins.
344
+ * - Extends Commander.Command so plugins can attach options/commands/hooks.
345
+ * - Adds host-specific helpers used by built-in plugins.
346
+ *
347
+ * Purpose: remove nominal class identity (private fields) from the plugin seam
348
+ * to avoid TS2379 under exactOptionalPropertyTypes in downstream consumers.
349
+ */
350
+ type GetDotenvCliPublic<TOptions extends GetDotenvOptions = GetDotenvOptions> = Command & {
351
+ ns: (name: string) => Command;
352
+ getCtx: () => GetDotenvCliCtx<TOptions> | undefined;
353
+ resolveAndLoad: (customOptions?: Partial<TOptions>) => Promise<GetDotenvCliCtx<TOptions>>;
354
+ };
355
+ /** Public plugin contract used by the GetDotenv CLI host. */
356
+ interface GetDotenvCliPlugin {
357
+ id?: string;
358
+ /**
359
+ * Setup phase: register commands and wiring on the provided CLI instance.
360
+ * Runs parent → children (pre-order).
361
+ */
362
+ setup: (cli: GetDotenvCliPublic) => void | Promise<void>;
277
363
  /**
278
364
  * After the dotenv context is resolved, initialize any clients/secrets
279
365
  * or attach per-plugin state under ctx.plugins (by convention).
280
366
  * Runs parent → children (pre-order).
281
367
  */
282
- afterResolve?: (cli: GetDotenvCli, ctx: GetDotenvCliCtx) => void | Promise<void>;
368
+ afterResolve?: (cli: GetDotenvCliPublic, ctx: GetDotenvCliCtx) => void | Promise<void>;
283
369
  /**
284
370
  * Optional Zod schema for this plugin's config slice (from config.plugins[id]).
285
371
  * When provided, the host validates the merged config under the guarded loader path.
@@ -287,7 +373,8 @@ declare class GetDotenvCli<TOptions extends GetDotenvOptions = GetDotenvOptions>
287
373
  configSchema?: ZodType;
288
374
  /**
289
375
  * Compositional children. Installed after the parent per pre-order.
290
- */ children: GetDotenvCliPlugin[];
376
+ */
377
+ children: GetDotenvCliPlugin[];
291
378
  /**
292
379
  * Compose a child plugin. Returns the parent to enable chaining.
293
380
  */
@@ -317,4 +404,4 @@ declare const definePlugin: (spec: DefineSpec) => GetDotenvCliPlugin;
317
404
  declare const readMergedOptions: (cmd: Command) => GetDotenvCliOptions | undefined;
318
405
 
319
406
  export { GetDotenvCli, definePlugin, readMergedOptions };
320
- export type { DefineSpec, GetDotenvCliCtx, GetDotenvCliOptions, GetDotenvCliPlugin, ScriptsTable };
407
+ export type { DefineSpec, GetDotenvCliCtx, GetDotenvCliOptions, GetDotenvCliPlugin, GetDotenvCliPublic, ScriptsTable };