@karmaniverous/get-dotenv 7.0.6 → 7.0.8

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 (51) hide show
  1. package/dist/chunks/{createCli-BkHLeYXL.mjs → createCli-CUPiFVdZ.mjs} +27 -10
  2. package/dist/chunks/index-CNXekCQC.mjs +96 -0
  3. package/dist/chunks/{index-Dd6S1nZ-.mjs → index-DnG3N6yj.mjs} +6 -6
  4. package/dist/chunks/{loader-V1vbmtyw.mjs → loader-C3DtD6HB.mjs} +4 -2
  5. package/dist/chunks/{readDotenvCascade-Dgx4SC1p.mjs → readDotenvCascade-CfFPgLCp.mjs} +52 -21
  6. package/dist/chunks/{readMergedOptions-CraAnYdB.mjs → readMergedOptions-BT1C87_u.mjs} +53 -13
  7. package/dist/chunks/{resolveCliOptions-BMBkWDYJ.mjs → resolveCliOptions-BbfouWSK.mjs} +1 -1
  8. package/dist/chunks/{spawnEnv-CKgnHGpr.mjs → spawnEnv-DvisqPiU.mjs} +28 -3
  9. package/dist/chunks/{types-BthqmnDr.mjs → types-BkQxnyZK.mjs} +1 -1
  10. package/dist/cli.d.ts +29 -8
  11. package/dist/cli.mjs +11 -15
  12. package/dist/cliHost.d.ts +29 -8
  13. package/dist/cliHost.mjs +6 -6
  14. package/dist/config.d.ts +1 -1
  15. package/dist/config.mjs +1 -1
  16. package/dist/env-overlay.d.ts +13 -9
  17. package/dist/env-overlay.mjs +2 -2
  18. package/dist/getdotenv.cli.mjs +11 -15
  19. package/dist/index.d.ts +30 -9
  20. package/dist/index.mjs +31 -23
  21. package/dist/plugins-aws.d.ts +12 -1
  22. package/dist/plugins-aws.mjs +8 -7
  23. package/dist/plugins-batch.d.ts +28 -1
  24. package/dist/plugins-batch.mjs +202 -68
  25. package/dist/plugins-cmd.d.ts +12 -1
  26. package/dist/plugins-cmd.mjs +6 -6
  27. package/dist/plugins-init.d.ts +12 -1
  28. package/dist/plugins-init.mjs +3 -3
  29. package/dist/plugins.d.ts +14 -1
  30. package/dist/plugins.mjs +10 -14
  31. package/package.json +40 -40
  32. package/schema/getdotenv.config.schema.json +207 -0
  33. package/dist/chunks/AwsRestJsonProtocol-4m7HHwvS.mjs +0 -1026
  34. package/dist/chunks/externalDataInterceptor-DyDNbv-D.mjs +0 -19
  35. package/dist/chunks/getSSOTokenFromFile-CkYcEieD.mjs +0 -22
  36. package/dist/chunks/index-B35hOhgq.mjs +0 -669
  37. package/dist/chunks/index-B3fM_U6F.mjs +0 -349
  38. package/dist/chunks/index-B_yJRqty.mjs +0 -541
  39. package/dist/chunks/index-BuxEK_Z4.mjs +0 -12529
  40. package/dist/chunks/index-C58EanKv.mjs +0 -383
  41. package/dist/chunks/index-CjWZ4uNg.mjs +0 -103
  42. package/dist/chunks/index-CsI5JuIM.mjs +0 -188
  43. package/dist/chunks/index-D-a5vkZL.mjs +0 -82
  44. package/dist/chunks/index-D6iVe1wh.mjs +0 -946
  45. package/dist/chunks/index-Dl8qC51H.mjs +0 -290
  46. package/dist/chunks/index-FT37CtcF.mjs +0 -31
  47. package/dist/chunks/index-QkVZTs6l.mjs +0 -519
  48. package/dist/chunks/loadSso-DF7GLUZf.mjs +0 -488
  49. package/dist/chunks/package-DbbYaehr.mjs +0 -5
  50. package/dist/chunks/parseKnownFiles-BZNrX_JE.mjs +0 -23
  51. package/dist/chunks/sdk-stream-mixin-BL49AxZx.mjs +0 -307
@@ -1,24 +1,24 @@
1
1
  import 'zod';
2
2
  import 'path';
3
- import { r as resolveGetDotenvConfigSources } from './loader-V1vbmtyw.mjs';
3
+ import { r as resolveGetDotenvConfigSources } from './loader-C3DtD6HB.mjs';
4
4
  import 'nanoid';
5
5
  import 'fs-extra';
6
6
  import 'node:path';
7
7
  import 'radash';
8
8
  import 'node:buffer';
9
- import { d as defaultsDeep, h as getDotenvCliOptions2Options, c as baseRootOptionDefaults, G as GetDotenvCli, b as attachRootOptions } from './readMergedOptions-CraAnYdB.mjs';
9
+ import { a as defaultsDeep, g as getDotenvCliOptions2Options, b as baseRootOptionDefaults, G as GetDotenvCli, c as attachRootOptions } from './readMergedOptions-BT1C87_u.mjs';
10
10
  import 'crypto';
11
11
  import 'url';
12
12
  import '@commander-js/extra-typings';
13
13
  import 'dotenv';
14
14
  import 'execa';
15
15
  import { t as toHelpConfig } from './helpConfig-CGejgwWW.mjs';
16
- import { r as resolveCliOptions } from './resolveCliOptions-BMBkWDYJ.mjs';
16
+ import { r as resolveCliOptions } from './resolveCliOptions-BbfouWSK.mjs';
17
17
  import { v as validateEnvAgainstSources } from './validate-CDl0rE6k.mjs';
18
18
  import { awsPlugin } from '../plugins-aws.mjs';
19
- import { R as awsWhoamiPlugin } from './index-BuxEK_Z4.mjs';
19
+ import { a as awsWhoamiPlugin } from './index-CNXekCQC.mjs';
20
20
  import { batchPlugin } from '../plugins-batch.mjs';
21
- import { c as cmdPlugin } from './index-Dd6S1nZ-.mjs';
21
+ import { c as cmdPlugin } from './index-DnG3N6yj.mjs';
22
22
  import { initPlugin } from '../plugins-init.mjs';
23
23
 
24
24
  const dbg = (...args) => {
@@ -66,7 +66,7 @@ function installRootHooks(program, defaults) {
66
66
  }
67
67
  };
68
68
  // Hook: preSubcommand — always runs for subcommand flows.
69
- program.hook('preSubcommand', async (thisCommand) => {
69
+ program.hook('preSubcommand', async (thisCommand, _actionCommand) => {
70
70
  const sources = await resolveGetDotenvConfigSources(import.meta.url);
71
71
  const rawArgs = thisCommand.rawArgs ?? [];
72
72
  dbg('preSubcommand:rawArgs', rawArgs);
@@ -82,8 +82,11 @@ function installRootHooks(program, defaults) {
82
82
  thisCommand.getDotenvCliOptions = merged;
83
83
  program._setOptionsBag(merged);
84
84
  // Resolve context for this run via programmatic converter.
85
+ // afterResolve is deferred to preAction where the full command path is known.
85
86
  const serviceOptions = getDotenvCliOptions2Options(merged);
86
- await program.resolveAndLoad(serviceOptions);
87
+ await program.resolveAndLoad(serviceOptions, {
88
+ runAfterResolve: false,
89
+ });
87
90
  propagateResolvedEnv(merged);
88
91
  // Refresh dynamic help text using the resolved config slices.
89
92
  try {
@@ -113,8 +116,8 @@ function installRootHooks(program, defaults) {
113
116
  /* tolerate non-strict flows */
114
117
  }
115
118
  });
116
- // Hook: preAction — root-only and parent-alias flows.
117
- program.hook('preAction', async (thisCommand) => {
119
+ // Hook: preAction — root-only and parent-alias flows + scoped afterResolve.
120
+ program.hook('preAction', async (thisCommand, actionCommand) => {
118
121
  const sources = await resolveGetDotenvConfigSources(import.meta.url);
119
122
  const rawArgs = thisCommand.rawArgs ?? [];
120
123
  dbg('preAction:rawArgs', rawArgs);
@@ -130,7 +133,7 @@ function installRootHooks(program, defaults) {
130
133
  program._setOptionsBag(merged);
131
134
  if (!program.hasCtx()) {
132
135
  const serviceOptions = getDotenvCliOptions2Options(merged);
133
- await program.resolveAndLoad(serviceOptions);
136
+ await program.resolveAndLoad(serviceOptions, { runAfterResolve: false });
134
137
  }
135
138
  // Always propagate resolved env into the merged bag — even when
136
139
  // preSubcommand already set the context. preAction unconditionally
@@ -160,6 +163,20 @@ function installRootHooks(program, defaults) {
160
163
  catch {
161
164
  /* tolerate non-strict flows */
162
165
  }
166
+ // Run afterResolve scoped to the invoked command branch.
167
+ // Walk actionCommand.parent chain to build the full plugin path.
168
+ // Always true after resolution above, but satisfies the type checker.
169
+ const ctx = program.hasCtx() ? program.getCtx() : undefined;
170
+ if (ctx) {
171
+ const segments = [];
172
+ let node = actionCommand;
173
+ while (node && node !== thisCommand) {
174
+ segments.unshift(node.name());
175
+ node = node
176
+ .parent;
177
+ }
178
+ await program._runAfterResolve(ctx, segments.length > 0 ? segments : undefined);
179
+ }
163
180
  });
164
181
  return program;
165
182
  }
@@ -0,0 +1,96 @@
1
+ import { d as definePlugin } from './readMergedOptions-BT1C87_u.mjs';
2
+ import 'execa';
3
+ import 'radash';
4
+ import 'node:buffer';
5
+ import 'fs-extra';
6
+ import 'node:path';
7
+ import 'crypto';
8
+ import 'path';
9
+ import 'url';
10
+ import '@commander-js/extra-typings';
11
+ import 'nanoid';
12
+ import 'dotenv';
13
+ import './loader-C3DtD6HB.mjs';
14
+ import 'package-directory';
15
+ import 'yaml';
16
+ import 'zod';
17
+ import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
18
+
19
+ /**
20
+ * Attach the default action for the `aws whoami` command.
21
+ *
22
+ * This behavior executes only when `aws whoami` is invoked without a subcommand.
23
+ *
24
+ * @param cli - The `whoami` command mount.
25
+ * @returns Nothing.
26
+ */
27
+ function attachWhoamiDefaultAction(cli) {
28
+ cli.action(async () => {
29
+ // The AWS SDK default providers will read credentials from process.env,
30
+ // which the aws parent has already populated.
31
+ const client = new STSClient();
32
+ const result = await client.send(new GetCallerIdentityCommand());
33
+ console.log(JSON.stringify(result, null, 2));
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Attach options/arguments for the `aws whoami` plugin mount.
39
+ *
40
+ * This subcommand currently takes no flags/args; this module exists to keep the
41
+ * wiring layout consistent across shipped plugins (options vs actions).
42
+ *
43
+ * Note: the plugin description is owned by `src/plugins/aws/whoami/index.ts` and
44
+ * must not be set here.
45
+ *
46
+ * @param cli - The `whoami` command mount under `aws`.
47
+ * @returns The same `cli` instance for chaining.
48
+ *
49
+ * @internal
50
+ */
51
+ function attachWhoamiOptions(cli) {
52
+ return cli;
53
+ }
54
+
55
+ /**
56
+ * Attach the `really` subcommand under `aws whoami`.
57
+ *
58
+ * Reads `SECRET_IDENTITY` from the resolved get-dotenv context (`cli.getCtx().dotenv`).
59
+ *
60
+ * @param cli - The `whoami` command mount.
61
+ * @returns Nothing.
62
+ */
63
+ function attachWhoamiReallyAction(cli) {
64
+ const really = cli
65
+ .ns('really')
66
+ .description('Print SECRET_IDENTITY from the resolved dotenv context');
67
+ really.action(() => {
68
+ const secretIdentity = really.getCtx().dotenv.SECRET_IDENTITY;
69
+ console.log(`Your secret identity is ${secretIdentity ?? 'still a secret'}.`);
70
+ });
71
+ }
72
+
73
+ /**
74
+ * AWS Whoami plugin factory.
75
+ *
76
+ * This plugin demonstrates a “bucket of subcommands” pattern:
77
+ * - Subcommand behavior is articulated in separate modules as `attach*` helpers.
78
+ * - Those helpers are not individually composable plugins; they are internal wiring for one plugin instance.
79
+ *
80
+ * @returns A plugin instance mounted at `aws whoami`.
81
+ */
82
+ const awsWhoamiPlugin = () => definePlugin({
83
+ ns: 'whoami',
84
+ setup(cli) {
85
+ cli.description('Print AWS caller identity (uses parent aws session)');
86
+ // Options/args (none today, but keep layout consistent with other plugins).
87
+ const whoami = attachWhoamiOptions(cli);
88
+ // Default behavior: `getdotenv aws whoami`
89
+ attachWhoamiDefaultAction(whoami);
90
+ // Subcommand behavior: `getdotenv aws whoami really`
91
+ attachWhoamiReallyAction(whoami);
92
+ return undefined;
93
+ },
94
+ });
95
+
96
+ export { awsWhoamiPlugin as a };
@@ -1,5 +1,5 @@
1
1
  import { camel } from 'radash';
2
- import { r as readMergedOptions, d as defaultsDeep, h as getDotenvCliOptions2Options, g as definePlugin } from './readMergedOptions-CraAnYdB.mjs';
2
+ import { r as readMergedOptions, a as defaultsDeep, g as getDotenvCliOptions2Options, d as definePlugin } from './readMergedOptions-BT1C87_u.mjs';
3
3
  import 'execa';
4
4
  import 'node:buffer';
5
5
  import 'fs-extra';
@@ -10,14 +10,14 @@ import 'url';
10
10
  import '@commander-js/extra-typings';
11
11
  import 'nanoid';
12
12
  import 'dotenv';
13
- import { r as resolveGetDotenvConfigSources } from './loader-V1vbmtyw.mjs';
13
+ import { r as resolveGetDotenvConfigSources } from './loader-C3DtD6HB.mjs';
14
14
  import 'package-directory';
15
15
  import 'yaml';
16
16
  import { z } from 'zod';
17
- import { r as resolveCommand, a as resolveShell, t as tokenize, s as shouldCapture, c as runCommand, b as buildSpawnEnv } from './spawnEnv-CKgnHGpr.mjs';
17
+ import { r as resolveCommand, a as resolveShell, t as tokenize, s as shouldCapture, c as runCommand, b as buildSpawnEnv } from './spawnEnv-DvisqPiU.mjs';
18
18
  import { m as maybePreserveNodeEvalArgv, c as composeNestedEnv, s as stripOne } from './invoke-DuRPU1oC.mjs';
19
- import { f as dotenvExpandFromProcessEnv } from './readDotenvCascade-Dgx4SC1p.mjs';
20
- import { b as baseGetDotenvCliOptions, r as resolveCliOptions } from './resolveCliOptions-BMBkWDYJ.mjs';
19
+ import { f as dotenvExpandFromProcessEnv } from './readDotenvCascade-CfFPgLCp.mjs';
20
+ import { b as baseGetDotenvCliOptions, r as resolveCliOptions } from './resolveCliOptions-BbfouWSK.mjs';
21
21
 
22
22
  /** src/diagnostics/entropy.ts
23
23
  * Entropy diagnostics (presentation-only).
@@ -480,4 +480,4 @@ const cmdPlugin = (options = {}) => {
480
480
  return plugin;
481
481
  };
482
482
 
483
- export { redactObject as a, cmdPlugin as c, maybeWarnEntropy as m, redactDisplay as r, traceChildEnv as t };
483
+ export { redactDisplay as a, cmdPlugin as c, maybeWarnEntropy as m, redactObject as r, traceChildEnv as t };
@@ -288,7 +288,7 @@ const discoverConfigFiles = async (importMetaUrl) => {
288
288
  * For JS/TS: default export is loaded; "dynamic" is allowed.
289
289
  */
290
290
  const loadConfigFile = async (filePath) => {
291
- let raw = {};
291
+ let raw;
292
292
  try {
293
293
  const abs = path.resolve(filePath);
294
294
  if (isJsOrTs(abs)) {
@@ -302,7 +302,9 @@ const loadConfigFile = async (filePath) => {
302
302
  }
303
303
  }
304
304
  catch (err) {
305
- throw new Error(`Failed to read/parse config: ${filePath}. ${String(err)}`);
305
+ throw new Error(`Failed to read/parse config: ${filePath}.`, {
306
+ cause: err,
307
+ });
306
308
  }
307
309
  // Validate RAW
308
310
  const parsed = getDotenvConfigSchemaRaw.safeParse(raw);
@@ -189,21 +189,31 @@ function pushDotenvProvenance(prov, key, entry) {
189
189
  */
190
190
  /**
191
191
  * Apply a dynamic map to the target progressively.
192
- * - Functions receive (target, env) and may return string | undefined.
193
- * - Literals are assigned directly (including undefined).
192
+ * - Functions receive (target, env) and may return string | null | undefined.
193
+ * - string set the key to that value.
194
+ * - undefined → no-op, leave existing value unchanged.
195
+ * - null → delete the key from the target.
194
196
  *
195
197
  * @param target - Mutable target environment to assign into.
196
198
  * @param map - Dynamic map to apply (functions and/or literal values).
197
199
  * @param env - Selected environment name (if any) passed through to dynamic functions.
198
- * @returns Nothing.
200
+ * @returns Set of keys that were deleted (value was null).
199
201
  */
200
202
  function applyDynamicMap(target, map, env) {
203
+ const deleted = new Set();
201
204
  if (!map)
202
- return;
205
+ return deleted;
203
206
  for (const key of Object.keys(map)) {
204
207
  const val = typeof map[key] === 'function' ? map[key](target, env) : map[key];
205
- Object.assign(target, { [key]: val });
208
+ if (val === null) {
209
+ Reflect.deleteProperty(target, key);
210
+ deleted.add(key);
211
+ }
212
+ else if (val !== undefined)
213
+ target[key] = val;
214
+ // undefined → no-op
206
215
  }
216
+ return deleted;
207
217
  }
208
218
  /**
209
219
  * Load a default-export dynamic map from a JS/TS file (without applying it).
@@ -245,25 +255,46 @@ async function loadDynamicModuleDefault(absPath, cacheDirName) {
245
255
  * @param prov - Provenance map to append into.
246
256
  * @param meta - Dynamic provenance metadata (source tier and optional dynamicPath).
247
257
  *
258
+ * @returns Set of keys that were deleted (value was null).
259
+ *
248
260
  * @public
249
261
  */
250
262
  function applyDynamicMapWithProvenance(target, map, env, prov, meta) {
263
+ const deleted = new Set();
251
264
  if (!map)
252
- return;
265
+ return deleted;
253
266
  for (const key of Object.keys(map)) {
254
267
  const val = typeof map[key] === 'function' ? map[key](target, env) : map[key];
255
- Object.assign(target, { [key]: val });
256
- pushDotenvProvenance(prov, key, {
257
- kind: 'dynamic',
258
- op: typeof val === 'string' ? 'set' : 'unset',
259
- dynamicSource: meta.dynamicSource,
260
- ...(meta.dynamicSource === 'dynamicPath' &&
261
- typeof meta.dynamicPath === 'string' &&
262
- meta.dynamicPath.length > 0
263
- ? { dynamicPath: meta.dynamicPath }
264
- : {}),
265
- });
268
+ if (val === null) {
269
+ Reflect.deleteProperty(target, key);
270
+ deleted.add(key);
271
+ pushDotenvProvenance(prov, key, {
272
+ kind: 'dynamic',
273
+ op: 'unset',
274
+ dynamicSource: meta.dynamicSource,
275
+ ...(meta.dynamicSource === 'dynamicPath' &&
276
+ typeof meta.dynamicPath === 'string' &&
277
+ meta.dynamicPath.length > 0
278
+ ? { dynamicPath: meta.dynamicPath }
279
+ : {}),
280
+ });
281
+ }
282
+ else if (val !== undefined) {
283
+ target[key] = val;
284
+ pushDotenvProvenance(prov, key, {
285
+ kind: 'dynamic',
286
+ op: 'set',
287
+ dynamicSource: meta.dynamicSource,
288
+ ...(meta.dynamicSource === 'dynamicPath' &&
289
+ typeof meta.dynamicPath === 'string' &&
290
+ meta.dynamicPath.length > 0
291
+ ? { dynamicPath: meta.dynamicPath }
292
+ : {}),
293
+ });
294
+ }
295
+ // undefined → no-op, no provenance entry
266
296
  }
297
+ return deleted;
267
298
  }
268
299
  /**
269
300
  * Load a default-export dynamic map from a JS/TS file and apply it.
@@ -278,13 +309,13 @@ function applyDynamicMapWithProvenance(target, map, env, prov, meta) {
278
309
  * @param absPath - Absolute path to the dynamic module file.
279
310
  * @param env - Selected environment name (if any).
280
311
  * @param cacheDirName - Cache subdirectory under `.tsbuild/` for compiled artifacts.
281
- * @returns A `Promise\<void\>` which resolves after the module (if present) has been applied.
312
+ * @returns A `Promise\<Set\<string\>\>` resolving to the set of deleted keys.
282
313
  */
283
314
  async function loadAndApplyDynamic(target, absPath, env, cacheDirName) {
284
315
  const dyn = await loadDynamicModuleDefault(absPath, cacheDirName);
285
316
  if (!dyn)
286
- return;
287
- applyDynamicMap(target, dyn, env);
317
+ return new Set();
318
+ return applyDynamicMap(target, dyn, env);
288
319
  }
289
320
 
290
321
  /**
@@ -542,4 +573,4 @@ async function readDotenvCascadeWithProvenance(args) {
542
573
  return { dotenv: expanded, provenance: prov };
543
574
  }
544
575
 
545
- export { applyDynamicMap as a, applyDynamicMapWithProvenance as b, createDotenvProvenance as c, dotenvExpand as d, dotenvExpandAll as e, dotenvExpandFromProcessEnv as f, loadDynamicModuleDefault as g, readDotenvCascadeWithProvenance as h, loadAndApplyDynamic as l, overlayEnvWithProvenance as o, pushDotenvProvenance as p, readDotenv as r };
576
+ export { applyDynamicMap as a, applyDynamicMapWithProvenance as b, createDotenvProvenance as c, dotenvExpandAll as d, loadDynamicModuleDefault as e, dotenvExpandFromProcessEnv as f, readDotenv as g, dotenvExpand as h, loadAndApplyDynamic as l, overlayEnvWithProvenance as o, pushDotenvProvenance as p, readDotenvCascadeWithProvenance as r };
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { Option, Command } from '@commander-js/extra-typings';
3
- import { d as dotenvExpand, h as readDotenvCascadeWithProvenance, o as overlayEnvWithProvenance, b as applyDynamicMapWithProvenance, g as loadDynamicModuleDefault, f as dotenvExpandFromProcessEnv } from './readDotenvCascade-Dgx4SC1p.mjs';
3
+ import { h as dotenvExpand, r as readDotenvCascadeWithProvenance, o as overlayEnvWithProvenance, b as applyDynamicMapWithProvenance, e as loadDynamicModuleDefault, f as dotenvExpandFromProcessEnv } from './readDotenvCascade-CfFPgLCp.mjs';
4
4
  import fs from 'fs-extra';
5
5
  import 'node:path';
6
6
  import 'nanoid';
@@ -10,7 +10,7 @@ import 'node:buffer';
10
10
  import 'crypto';
11
11
  import { fileURLToPath } from 'url';
12
12
  import 'dotenv';
13
- import { g as getDotenvOptionsSchemaResolved, r as resolveGetDotenvConfigSources } from './loader-V1vbmtyw.mjs';
13
+ import { g as getDotenvOptionsSchemaResolved, r as resolveGetDotenvConfigSources } from './loader-C3DtD6HB.mjs';
14
14
  import { packageDirectory } from 'package-directory';
15
15
 
16
16
  /**
@@ -947,7 +947,7 @@ function buildHelpInformation(base, cmd) {
947
947
  }
948
948
  const marker = '\nCommands:';
949
949
  const idx = base.indexOf(marker);
950
- let out = base;
950
+ let out;
951
951
  if (idx >= 0) {
952
952
  const toInsert = groups.startsWith('\n') ? groups : `\n${groups}`;
953
953
  out = `${base.slice(0, idx)}${toInsert}${base.slice(idx)}`;
@@ -1078,12 +1078,14 @@ function initializeInstance(cli, headerGetter) {
1078
1078
  cli.hook('preSubcommand', async () => {
1079
1079
  if (cli.hasCtx())
1080
1080
  return;
1081
- await cli.resolveAndLoad({ loadProcess: false });
1081
+ await cli.resolveAndLoad({ loadProcess: false }, { runAfterResolve: false });
1082
1082
  });
1083
1083
  }
1084
1084
 
1085
1085
  /**
1086
1086
  * Determine the effective namespace for a child plugin (override \> default).
1087
+ * Uses a structural parameter so any entry with `.override?.ns` and `.plugin.ns`
1088
+ * is accepted regardless of full generic variance.
1087
1089
  */
1088
1090
  const effectiveNs = (child) => {
1089
1091
  const o = child.override;
@@ -1258,10 +1260,14 @@ class GetDotenvCli extends Command {
1258
1260
  * Resolve options (strict) and compute dotenv context.
1259
1261
  * Stores the context on the instance under a symbol.
1260
1262
  *
1261
- * Options:
1262
- * - opts.runAfterResolve (default true): when false, skips running plugin
1263
- * afterResolve hooks. Useful for top-level help rendering to avoid
1264
- * long-running side-effects while still evaluating dynamic help text.
1263
+ * @param customOptions - Partial options to overlay for this invocation.
1264
+ * @param opts - Optional resolver behavior switches.
1265
+ * - `runAfterResolve` (default `true`): when false, skips running plugin
1266
+ * afterResolve hooks. Useful for top-level help rendering to avoid
1267
+ * long-running side-effects while still evaluating dynamic help text.
1268
+ * - `invokedSubcommand`: when provided, only the plugin subtree whose
1269
+ * namespace matches this name runs afterResolve hooks. When omitted,
1270
+ * all plugins run afterResolve (backward compatibility).
1265
1271
  */
1266
1272
  async resolveAndLoad(customOptions = {}, opts) {
1267
1273
  const ctx = await resolveAndComputeContext(customOptions,
@@ -1272,7 +1278,10 @@ class GetDotenvCli extends Command {
1272
1278
  // Ensure plugins are installed exactly once, then run afterResolve.
1273
1279
  await this.install();
1274
1280
  if (opts?.runAfterResolve ?? true) {
1275
- await this._runAfterResolve(ctx);
1281
+ const path = opts?.invokedSubcommand
1282
+ ? [opts.invokedSubcommand]
1283
+ : undefined;
1284
+ await this._runAfterResolve(ctx, path);
1276
1285
  }
1277
1286
  return ctx;
1278
1287
  }
@@ -1431,10 +1440,41 @@ class GetDotenvCli extends Command {
1431
1440
  }
1432
1441
  }
1433
1442
  /**
1434
- * Run afterResolve hooks for all plugins (parent → children).
1443
+ * Run afterResolve hooks for registered plugins (parent → children).
1444
+ * When {@link commandPath} is provided, only the matching branch of the
1445
+ * plugin tree runs; otherwise all plugins run (backward compatibility).
1446
+ *
1447
+ * The path is walked segment-by-segment against the plugin tree.
1448
+ * Intermediate matches run their own afterResolve hook.
1449
+ * The deepest match runs afterResolve for itself and all its descendants.
1435
1450
  */
1436
- async _runAfterResolve(ctx) {
1437
- await runAfterResolveTree(this, this._plugins.map((e) => e.plugin), ctx);
1451
+ async _runAfterResolve(ctx, commandPath) {
1452
+ if (!commandPath || commandPath.length === 0) {
1453
+ await runAfterResolveTree(this, this._plugins.map((e) => e.plugin), ctx);
1454
+ return;
1455
+ }
1456
+ // Walk the plugin tree along the command path, scoping to the invoked branch.
1457
+ let entries = this._plugins;
1458
+ for (let i = 0; i < commandPath.length; i++) {
1459
+ const segment = commandPath[i];
1460
+ const entry = entries.find((e) => effectiveNs(e) === segment);
1461
+ if (!entry)
1462
+ return;
1463
+ const isLast = i === commandPath.length - 1;
1464
+ const nextSegment = commandPath[i + 1];
1465
+ const hasMatchingChild = !isLast &&
1466
+ entry.plugin.children.some((e) => effectiveNs(e) === nextSegment);
1467
+ if (isLast || !hasMatchingChild) {
1468
+ // Deepest match: run this plugin + all its descendants.
1469
+ await runAfterResolveTree(this, [entry.plugin], ctx);
1470
+ return;
1471
+ }
1472
+ // Intermediate match: run only this plugin's own afterResolve.
1473
+ if (entry.plugin.afterResolve) {
1474
+ await entry.plugin.afterResolve(this, ctx);
1475
+ }
1476
+ entries = entry.plugin.children;
1477
+ }
1438
1478
  }
1439
1479
  }
1440
1480
 
@@ -1464,4 +1504,4 @@ const readMergedOptions = (cmd) => {
1464
1504
  return bag;
1465
1505
  };
1466
1506
 
1467
- export { GetDotenvCli as G, assertLogger as a, attachRootOptions as b, baseRootOptionDefaults as c, defaultsDeep as d, defineDynamic as e, defineGetDotenvConfig as f, definePlugin as g, getDotenvCliOptions2Options as h, interpolateDeep as i, resolveGetDotenvOptions as j, readMergedOptions as r, writeDotenvFile as w };
1507
+ export { GetDotenvCli as G, defaultsDeep as a, baseRootOptionDefaults as b, attachRootOptions as c, definePlugin as d, resolveGetDotenvOptions as e, assertLogger as f, getDotenvCliOptions2Options as g, defineDynamic as h, defineGetDotenvConfig as i, interpolateDeep as j, readMergedOptions as r, writeDotenvFile as w };
@@ -1,4 +1,4 @@
1
- import { c as baseRootOptionDefaults, d as defaultsDeep } from './readMergedOptions-CraAnYdB.mjs';
1
+ import { b as baseRootOptionDefaults, a as defaultsDeep } from './readMergedOptions-BT1C87_u.mjs';
2
2
  import 'radash';
3
3
  import 'node:buffer';
4
4
  import 'fs-extra';
@@ -84,6 +84,26 @@ const shouldCapture = (bagCapture) => process.env.GETDOTENV_STDIO === 'pipe' ||
84
84
  // This is safe for argv arrays passed to execa (no quoting needed) and avoids
85
85
  // passing quote characters through to Node (e.g., for `node -e "<code>"`).
86
86
  // Handles stacked quotes from shells like PowerShell: """code""" -> code.
87
+ // Shell-quote a single token for safe interpolation when joining an argv
88
+ // array into a command string destined for a shell. On Windows (cmd.exe)
89
+ // wrap in double quotes when the token contains shell metacharacters; on
90
+ // Unix wrap in single quotes with escaped embedded singles.
91
+ const shellQuoteToken = (s) => {
92
+ if (process.platform === 'win32') {
93
+ // cmd.exe: double-quote tokens with special chars. Inner double
94
+ // quotes are escaped with backslash (Node/libuv convention).
95
+ if (/[\s"'&|<>^()!%+,;=]/.test(s)) {
96
+ return '"' + s.replace(/"/g, '\\"') + '"';
97
+ }
98
+ return s;
99
+ }
100
+ // POSIX: single-quote tokens with special chars. Embedded single
101
+ // quotes break out, add an escaped single, and re-enter.
102
+ if (/[\s"'&|<>()!$\\`~#;{}[\]*?+]/.test(s)) {
103
+ return "'" + s.replace(/'/g, "'\\''") + "'";
104
+ }
105
+ return s;
106
+ };
87
107
  const stripOuterQuotes = (s) => {
88
108
  let out = s;
89
109
  // Repeatedly trim only when the entire string is wrapped in matching quotes.
@@ -129,7 +149,7 @@ async function _execNormalized(command, shell, opts = {}) {
129
149
  const stdio = opts.stdio ?? 'pipe';
130
150
  if (shell === false) {
131
151
  let file;
132
- let args = [];
152
+ let args;
133
153
  if (typeof command === 'string') {
134
154
  const tokens = tokenize(command);
135
155
  file = tokens[0];
@@ -158,8 +178,13 @@ async function _execNormalized(command, shell, opts = {}) {
158
178
  return out;
159
179
  }
160
180
  }
161
- // Shell path (string|true|URL): execaCommand handles shell resolution.
162
- const commandStr = typeof command === 'string' ? command : command.join(' ');
181
+ // Shell path (string|true|URL): build a single command string for the
182
+ // target shell. When the command is an array we shell-quote each token
183
+ // individually before joining so that metacharacters (spaces, quotes, +,
184
+ // parentheses, etc.) survive the round-trip through cmd.exe / sh.
185
+ const commandStr = typeof command === 'string'
186
+ ? command
187
+ : command.map(shellQuoteToken).join(' ');
163
188
  dbg('exec (shell)', {
164
189
  command: commandStr,
165
190
  shell: typeof shell === 'string' ? shell : 'custom',
@@ -1,4 +1,4 @@
1
- import { g as definePlugin } from './readMergedOptions-CraAnYdB.mjs';
1
+ import { d as definePlugin } from './readMergedOptions-BT1C87_u.mjs';
2
2
 
3
3
  /**
4
4
  * Create a namespace-only parent plugin (a group command) for composing plugins
package/dist/cli.d.ts CHANGED
@@ -286,7 +286,7 @@ interface RootOptionsShape {
286
286
  */
287
287
  trace?: boolean | string[];
288
288
  /** Paths to search for dotenv files (space-delimited string or array). */
289
- paths?: string;
289
+ paths?: string | string[];
290
290
  /** Delimiter for paths string (default: space). */
291
291
  pathsDelimiter?: string;
292
292
  /** Regex pattern for paths delimiter. */
@@ -378,7 +378,7 @@ type ProcessEnv = Record<string, string | undefined>;
378
378
  * and the selected environment (if any), and returns either a string to set
379
379
  * or `undefined` to unset/skip the variable.
380
380
  */
381
- type GetDotenvDynamicFunction = (vars: ProcessEnv, env: string | undefined) => string | undefined;
381
+ type GetDotenvDynamicFunction = (vars: ProcessEnv, env: string | undefined) => string | null | undefined;
382
382
  /**
383
383
  * A map of dynamic variable definitions.
384
384
  * Keys are variable names; values are either literal strings or functions.
@@ -464,6 +464,12 @@ interface ResolveAndLoadOptions {
464
464
  * @default true
465
465
  */
466
466
  runAfterResolve?: boolean;
467
+ /**
468
+ * Name of the invoked subcommand (plugin namespace). When provided,
469
+ * only the matching plugin subtree runs afterResolve hooks.
470
+ * When omitted, all plugins run afterResolve (backward compatibility).
471
+ */
472
+ invokedSubcommand?: string;
467
473
  }
468
474
  /**
469
475
  * Structural public interface for the host exposed to plugins.
@@ -557,6 +563,11 @@ interface GetDotenvCliPlugin<TOptions extends GetDotenvOptions = GetDotenvOption
557
563
  * After the dotenv context is resolved, initialize any clients/secrets
558
564
  * or attach per-plugin state under ctx.plugins (by convention).
559
565
  * Runs parent → children (pre-order).
566
+ *
567
+ * **Scoping**: When the host provides an invoked subcommand filter,
568
+ * afterResolve only fires for plugins whose namespace matches the
569
+ * invoked command path. A plugin's afterResolve should never produce
570
+ * side effects for commands outside its subtree.
560
571
  */
561
572
  afterResolve?: (cli: GetDotenvCliPublic<TOptions, TArgs, TOpts, TGlobal>, ctx: GetDotenvCliCtx<TOptions>) => void | Promise<void>;
562
573
  /** Zod schema for this plugin's config slice (from config.plugins[…]). */
@@ -622,10 +633,14 @@ declare class GetDotenvCli<TOptions extends GetDotenvOptions = GetDotenvOptions,
622
633
  * Resolve options (strict) and compute dotenv context.
623
634
  * Stores the context on the instance under a symbol.
624
635
  *
625
- * Options:
626
- * - opts.runAfterResolve (default true): when false, skips running plugin
627
- * afterResolve hooks. Useful for top-level help rendering to avoid
628
- * long-running side-effects while still evaluating dynamic help text.
636
+ * @param customOptions - Partial options to overlay for this invocation.
637
+ * @param opts - Optional resolver behavior switches.
638
+ * - `runAfterResolve` (default `true`): when false, skips running plugin
639
+ * afterResolve hooks. Useful for top-level help rendering to avoid
640
+ * long-running side-effects while still evaluating dynamic help text.
641
+ * - `invokedSubcommand`: when provided, only the plugin subtree whose
642
+ * namespace matches this name runs afterResolve hooks. When omitted,
643
+ * all plugins run afterResolve (backward compatibility).
629
644
  */
630
645
  resolveAndLoad(customOptions?: Partial<TOptions>, opts?: ResolveAndLoadOptions): Promise<GetDotenvCliCtx<TOptions>>;
631
646
  /**
@@ -697,9 +712,15 @@ declare class GetDotenvCli<TOptions extends GetDotenvOptions = GetDotenvOptions,
697
712
  */
698
713
  install(): Promise<void>;
699
714
  /**
700
- * Run afterResolve hooks for all plugins (parent → children).
715
+ * Run afterResolve hooks for registered plugins (parent → children).
716
+ * When {@link commandPath} is provided, only the matching branch of the
717
+ * plugin tree runs; otherwise all plugins run (backward compatibility).
718
+ *
719
+ * The path is walked segment-by-segment against the plugin tree.
720
+ * Intermediate matches run their own afterResolve hook.
721
+ * The deepest match runs afterResolve for itself and all its descendants.
701
722
  */
702
- private _runAfterResolve;
723
+ _runAfterResolve(ctx: GetDotenvCliCtx<TOptions>, commandPath?: string[]): Promise<void>;
703
724
  }
704
725
 
705
726
  /**