@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
@@ -272,7 +272,7 @@ type ProcessEnv = Record<string, string | undefined>;
272
272
  * and the selected environment (if any), and returns either a string to set
273
273
  * or `undefined` to unset/skip the variable.
274
274
  */
275
- type GetDotenvDynamicFunction = (vars: ProcessEnv, env: string | undefined) => string | undefined;
275
+ type GetDotenvDynamicFunction = (vars: ProcessEnv, env: string | undefined) => string | null | undefined;
276
276
  /**
277
277
  * A map of dynamic variable definitions.
278
278
  * Keys are variable names; values are either literal strings or functions.
@@ -358,6 +358,12 @@ interface ResolveAndLoadOptions {
358
358
  * @default true
359
359
  */
360
360
  runAfterResolve?: boolean;
361
+ /**
362
+ * Name of the invoked subcommand (plugin namespace). When provided,
363
+ * only the matching plugin subtree runs afterResolve hooks.
364
+ * When omitted, all plugins run afterResolve (backward compatibility).
365
+ */
366
+ invokedSubcommand?: string;
361
367
  }
362
368
  /**
363
369
  * Structural public interface for the host exposed to plugins.
@@ -451,6 +457,11 @@ interface GetDotenvCliPlugin<TOptions extends GetDotenvOptions = GetDotenvOption
451
457
  * After the dotenv context is resolved, initialize any clients/secrets
452
458
  * or attach per-plugin state under ctx.plugins (by convention).
453
459
  * Runs parent → children (pre-order).
460
+ *
461
+ * **Scoping**: When the host provides an invoked subcommand filter,
462
+ * afterResolve only fires for plugins whose namespace matches the
463
+ * invoked command path. A plugin's afterResolve should never produce
464
+ * side effects for commands outside its subtree.
454
465
  */
455
466
  afterResolve?: (cli: GetDotenvCliPublic<TOptions, TArgs, TOpts, TGlobal>, ctx: GetDotenvCliCtx<TOptions>) => void | Promise<void>;
456
467
  /** Zod schema for this plugin's config slice (from config.plugins[…]). */
@@ -550,6 +561,11 @@ interface ExecShellCommandBatchOptions {
550
561
  * an array is treated as argv for shell‑off execution.
551
562
  */
552
563
  command?: string | string[];
564
+ /**
565
+ * Maximum number of concurrent executions when {@link ExecShellCommandBatchOptions.parallel} is true.
566
+ * Defaults to {@link defaultConcurrency}.
567
+ */
568
+ concurrency?: number;
553
569
  /**
554
570
  * Merged root CLI options bag (JSON‑serializable) forwarded for nested composition
555
571
  * by downstream executors. Used to compute child overlays deterministically.
@@ -575,6 +591,11 @@ interface ExecShellCommandBatchOptions {
575
591
  * Logger used for headings, listings, and diagnostics.
576
592
  */
577
593
  logger: Logger;
594
+ /**
595
+ * When true, execute commands across discovered directories in parallel.
596
+ * Output is buffered per-directory and printed in discovery order.
597
+ */
598
+ parallel?: boolean;
578
599
  /**
579
600
  * Resolve the batch root from the nearest package directory instead of CWD.
580
601
  */
@@ -597,12 +618,16 @@ interface ExecShellCommandBatchOptions {
597
618
  interface BatchParentInvokerFlags {
598
619
  /** Command to execute. */
599
620
  command?: string;
621
+ /** Maximum concurrent executions in parallel mode. */
622
+ concurrency?: number;
600
623
  /** Space-delimited glob patterns. */
601
624
  globs?: string;
602
625
  /** List directories without executing. */
603
626
  list?: boolean;
604
627
  /** Ignore errors and continue. */
605
628
  ignoreErrors?: boolean;
629
+ /** Execute commands in parallel across directories. */
630
+ parallel?: boolean;
606
631
  /** Use package directory as root. */
607
632
  pkgCwd?: boolean;
608
633
  /** Root path for discovery. */
@@ -625,6 +650,7 @@ interface BatchCmdSubcommandOptions {
625
650
  * - scripts/shell: used to resolve command and shell behavior per script or global default.
626
651
  */
627
652
  declare const batchPlugin: (opts?: BatchPluginOptions) => PluginWithInstanceHelpers<GetDotenvOptions, {
653
+ concurrency?: number | undefined;
628
654
  scripts?: Record<string, string | {
629
655
  cmd: string;
630
656
  shell?: string | boolean | undefined;
@@ -632,6 +658,7 @@ declare const batchPlugin: (opts?: BatchPluginOptions) => PluginWithInstanceHelp
632
658
  shell?: string | boolean | undefined;
633
659
  rootPath?: string | undefined;
634
660
  globs?: string | undefined;
661
+ parallel?: boolean | undefined;
635
662
  pkgCwd?: boolean | undefined;
636
663
  }, [], {}, {}>;
637
664
  /**
@@ -1,4 +1,4 @@
1
- import { r as readMergedOptions, g as definePlugin } from './chunks/readMergedOptions-CraAnYdB.mjs';
1
+ import { r as readMergedOptions, d as definePlugin } from './chunks/readMergedOptions-BT1C87_u.mjs';
2
2
  import 'execa';
3
3
  import 'radash';
4
4
  import 'node:buffer';
@@ -10,16 +10,56 @@ import 'url';
10
10
  import { Command } from '@commander-js/extra-typings';
11
11
  import 'nanoid';
12
12
  import 'dotenv';
13
- import './chunks/loader-V1vbmtyw.mjs';
13
+ import './chunks/loader-C3DtD6HB.mjs';
14
14
  import { packageDirectory } from 'package-directory';
15
15
  import 'yaml';
16
16
  import { z } from 'zod';
17
17
  import { c as composeNestedEnv, m as maybePreserveNodeEvalArgv } from './chunks/invoke-DuRPU1oC.mjs';
18
- import { c as runCommand, b as buildSpawnEnv, a as resolveShell, r as resolveCommand } from './chunks/spawnEnv-CKgnHGpr.mjs';
18
+ import { b as buildSpawnEnv, c as runCommand, d as runCommandResult, a as resolveShell, r as resolveCommand } from './chunks/spawnEnv-DvisqPiU.mjs';
19
19
  import { globby } from 'globby';
20
- import './chunks/readDotenvCascade-Dgx4SC1p.mjs';
20
+ import os from 'os';
21
+ import './chunks/readDotenvCascade-CfFPgLCp.mjs';
21
22
  import './chunks/loadModuleDefault-Dj8B3Stt.mjs';
22
23
 
24
+ /**
25
+ * Default concurrency limit for parallel batch execution.
26
+ * Uses `os.availableParallelism()` when available, otherwise falls back to CPU count.
27
+ */
28
+ const defaultConcurrency = typeof os.availableParallelism === 'function'
29
+ ? os.availableParallelism()
30
+ : os.cpus().length;
31
+ /**
32
+ * Zod schema for a single script entry (string or object).
33
+ */
34
+ const ScriptSchema = z.union([
35
+ z.string(),
36
+ z.object({
37
+ /** Command string to execute. */
38
+ cmd: z.string(),
39
+ /** Optional shell override for this script entry. */
40
+ shell: z.union([z.string(), z.boolean()]).optional(),
41
+ }),
42
+ ]);
43
+ /**
44
+ * Zod schema for batch plugin configuration.
45
+ */
46
+ const batchPluginConfigSchema = z.object({
47
+ /** Maximum concurrent executions in parallel mode. */
48
+ concurrency: z.number().int().positive().optional(),
49
+ /** Optional scripts table scoped to the batch plugin. */
50
+ scripts: z.record(z.string(), ScriptSchema).optional(),
51
+ /** Optional default shell for batch execution (overridden by per-script shell when present). */
52
+ shell: z.union([z.string(), z.boolean()]).optional(),
53
+ /** Root path for discovery, relative to CWD (or package root when pkgCwd is true). */
54
+ rootPath: z.string().optional(),
55
+ /** Space-delimited glob patterns used to discover directories. */
56
+ globs: z.string().optional(),
57
+ /** Execute commands in parallel across discovered directories. */
58
+ parallel: z.boolean().optional(),
59
+ /** When true, resolve the batch root from the nearest package directory. */
60
+ pkgCwd: z.boolean().optional(),
61
+ });
62
+
23
63
  const globPaths = async ({ globs, logger, pkgCwd, rootPath, }) => {
24
64
  let cwd = process.cwd();
25
65
  if (pkgCwd) {
@@ -47,7 +87,7 @@ const globPaths = async ({ globs, logger, pkgCwd, rootPath, }) => {
47
87
  * Execute a batch of commands across multiple directories.
48
88
  * Discovers targets via globs/rootPath and runs the command in each.
49
89
  */
50
- const execShellCommandBatch = async ({ command, getDotenvCliOptions, dotenvEnv, globs, ignoreErrors, list, logger, pkgCwd, rootPath, shell, }) => {
90
+ const execShellCommandBatch = async ({ command, concurrency, getDotenvCliOptions, dotenvEnv, globs, ignoreErrors, list, logger, parallel, pkgCwd, rootPath, shell, }) => {
51
91
  const capture = process.env.GETDOTENV_STDIO === 'pipe' ||
52
92
  Boolean(getDotenvCliOptions?.capture);
53
93
  // Require a command only when not listing. In list mode, a command is optional.
@@ -64,7 +104,9 @@ const execShellCommandBatch = async ({ command, getDotenvCliOptions, dotenvEnv,
64
104
  });
65
105
  const headerTitle = list
66
106
  ? 'Listing working directories...'
67
- : 'Executing command batch...';
107
+ : parallel
108
+ ? 'Executing command batch (parallel)...'
109
+ : 'Executing command batch...';
68
110
  logger.info('');
69
111
  const headerRootPath = `ROOT: ${absRootPath}`;
70
112
  const headerGlobs = `GLOBS: ${globs}`;
@@ -81,39 +123,130 @@ const execShellCommandBatch = async ({ command, getDotenvCliOptions, dotenvEnv,
81
123
  logger.info(headerRootPath);
82
124
  logger.info(headerGlobs);
83
125
  logger.info(headerCommand);
84
- for (const path of paths) {
85
- // Write path and command to console.
86
- const pathLabel = `CWD: ${path}`;
87
- if (list) {
88
- logger.info(pathLabel);
89
- continue;
126
+ // List mode: no parallelism needed.
127
+ if (list) {
128
+ for (const p of paths) {
129
+ logger.info(`CWD: ${p}`);
90
130
  }
91
131
  logger.info('');
92
- logger.info('*'.repeat(pathLabel.length));
93
- logger.info(pathLabel);
94
- logger.info(headerCommand);
95
- // Execute command.
96
- try {
97
- const hasCmd = (typeof command === 'string' && command.length > 0) ||
98
- (Array.isArray(command) && command.length > 0);
99
- if (hasCmd) {
100
- // Compose child env overlay (dotenv + nested bag)
101
- const overlay = composeNestedEnv(getDotenvCliOptions ?? {}, dotenvEnv ?? {});
102
- await runCommand(command, shell, {
103
- cwd: path,
104
- env: buildSpawnEnv(process.env, overlay),
105
- stdio: capture ? 'pipe' : 'inherit',
106
- });
132
+ return;
133
+ }
134
+ // Narrow command to a non-empty string or array after validation.
135
+ const validCommand = typeof command === 'string' && command.length > 0
136
+ ? command
137
+ : Array.isArray(command) && command.length > 0
138
+ ? command
139
+ : undefined;
140
+ if (!validCommand) {
141
+ logger.error(`No command provided. Use --command or --list.`);
142
+ process.exit(0);
143
+ }
144
+ // Compose child env overlay once (shared across all paths).
145
+ const overlay = composeNestedEnv(getDotenvCliOptions ?? {}, dotenvEnv ?? {});
146
+ const spawnEnv = buildSpawnEnv(process.env, overlay);
147
+ if (parallel) {
148
+ // Parallel execution with concurrency-limited pool.
149
+ const limit = concurrency ?? defaultConcurrency;
150
+ const results = [];
151
+ // Simple semaphore-based concurrency pool.
152
+ let active = 0;
153
+ let nextIdx = 0;
154
+ const total = paths.length;
155
+ results.length = total;
156
+ await new Promise((resolveAll, rejectAll) => {
157
+ let settled = false;
158
+ let completedCount = 0;
159
+ const tryLaunch = () => {
160
+ while (active < limit && nextIdx < total) {
161
+ const idx = nextIdx++;
162
+ const dirPath = paths[idx];
163
+ active++;
164
+ runCommandResult(validCommand, shell, {
165
+ cwd: dirPath,
166
+ env: spawnEnv,
167
+ })
168
+ .then((res) => {
169
+ results[idx] = {
170
+ dirPath,
171
+ exitCode: res.exitCode,
172
+ stdout: res.stdout,
173
+ stderr: res.stderr,
174
+ };
175
+ })
176
+ .catch((err) => {
177
+ results[idx] = {
178
+ dirPath,
179
+ exitCode: 1,
180
+ stdout: '',
181
+ stderr: String(err),
182
+ error: err,
183
+ };
184
+ })
185
+ .finally(() => {
186
+ active--;
187
+ completedCount++;
188
+ if (completedCount === total && !settled) {
189
+ settled = true;
190
+ resolveAll();
191
+ }
192
+ else {
193
+ tryLaunch();
194
+ }
195
+ });
196
+ }
197
+ };
198
+ if (total === 0) {
199
+ resolveAll();
107
200
  }
108
201
  else {
109
- // Should not occur due to the early guard; retain for type safety.
110
- logger.error(`No command provided. Use --command or --list.`);
111
- process.exit(0);
202
+ tryLaunch();
203
+ }
204
+ });
205
+ // Print results in discovery order.
206
+ const failures = [];
207
+ for (const result of results) {
208
+ const pathLabel = `CWD: ${result.dirPath}`;
209
+ logger.info('');
210
+ logger.info('*'.repeat(pathLabel.length));
211
+ logger.info(pathLabel);
212
+ logger.info(headerCommand);
213
+ if (result.stdout) {
214
+ process.stdout.write(result.stdout + (result.stdout.endsWith('\n') ? '' : '\n'));
215
+ }
216
+ if (result.stderr) {
217
+ process.stderr.write(result.stderr + (result.stderr.endsWith('\n') ? '' : '\n'));
218
+ }
219
+ if (result.exitCode !== 0) {
220
+ failures.push(result);
221
+ }
222
+ }
223
+ if (failures.length > 0 && !ignoreErrors) {
224
+ logger.error('');
225
+ logger.error(`${String(failures.length)} of ${String(total)} directories failed:`);
226
+ for (const f of failures) {
227
+ logger.error(` ${f.dirPath} (exit code ${String(f.exitCode)})`);
112
228
  }
113
229
  }
114
- catch (error) {
115
- if (!ignoreErrors) {
116
- throw error;
230
+ }
231
+ else {
232
+ // Sequential execution (original behavior).
233
+ for (const p of paths) {
234
+ const pathLabel = `CWD: ${p}`;
235
+ logger.info('');
236
+ logger.info('*'.repeat(pathLabel.length));
237
+ logger.info(pathLabel);
238
+ logger.info(headerCommand);
239
+ try {
240
+ await runCommand(validCommand, shell, {
241
+ cwd: p,
242
+ env: spawnEnv,
243
+ stdio: capture ? 'pipe' : 'inherit',
244
+ });
245
+ }
246
+ catch (error) {
247
+ if (!ignoreErrors) {
248
+ throw error;
249
+ }
117
250
  }
118
251
  }
119
252
  }
@@ -153,6 +286,8 @@ const attachBatchCmdAction = (plugin, cli, batchCmd, pluginOpts, cmd) => {
153
286
  : typeof cfg.rootPath === 'string'
154
287
  ? cfg.rootPath
155
288
  : './';
289
+ const parallel = g.parallel !== undefined ? g.parallel : Boolean(cfg.parallel);
290
+ const concurrency = typeof g.concurrency === 'number' ? g.concurrency : cfg.concurrency;
156
291
  // Resolve scripts/shell with precedence:
157
292
  // plugin opts → plugin config → merged root CLI options
158
293
  const scripts = pluginOpts.scripts ?? cfg.scripts ?? mergedBag.scripts ?? undefined;
@@ -163,11 +298,13 @@ const attachBatchCmdAction = (plugin, cli, batchCmd, pluginOpts, cmd) => {
163
298
  if (typeof commandOpt === 'string') {
164
299
  await execShellCommandBatch({
165
300
  command: resolveCommand(scripts, commandOpt),
301
+ ...(concurrency !== undefined ? { concurrency } : {}),
166
302
  dotenvEnv,
167
303
  globs,
168
304
  ignoreErrors,
169
305
  list: false,
170
306
  logger,
307
+ parallel,
171
308
  ...(pkgCwd ? { pkgCwd } : {}),
172
309
  rootPath,
173
310
  shell: resolveShell(scripts, commandOpt, shell),
@@ -183,10 +320,12 @@ const attachBatchCmdAction = (plugin, cli, batchCmd, pluginOpts, cmd) => {
183
320
  ? rootShell
184
321
  : false;
185
322
  await execShellCommandBatch({
323
+ ...(concurrency !== undefined ? { concurrency } : {}),
186
324
  globs,
187
325
  ignoreErrors,
188
326
  list: true,
189
327
  logger,
328
+ parallel,
190
329
  ...(pkgCwd ? { pkgCwd } : {}),
191
330
  rootPath,
192
331
  shell: listShell,
@@ -208,23 +347,29 @@ const attachBatchCmdAction = (plugin, cli, batchCmd, pluginOpts, cmd) => {
208
347
  const shellExec = shell ?? mergedBag.shell;
209
348
  const resolved = resolveCommand(scriptsExec, input);
210
349
  const shellSetting = resolveShell(scriptsExec, input, shellExec);
211
- // Preserve argv array only for shell-off Node -e snippets to avoid
212
- // lossy re-tokenization (Windows/PowerShell quoting). For simple
213
- // commands (e.g., "echo OK") keep string form to satisfy unit tests.
350
+ // Preserve argv array to avoid lossy re-tokenization (Windows/
351
+ // PowerShell quoting). When the command was not resolved to a script
352
+ // (i.e. resolved === input), keep the original argv tokens so that
353
+ // _execNormalized can pass them individually to execa — both in
354
+ // shell-off mode (where stripOuterQuotes handles argv) and in shell
355
+ // mode (where execa applies per-platform shell escaping per arg).
214
356
  let commandArg = resolved;
215
- if (shellSetting === false && resolved === input) {
216
- const preserved = maybePreserveNodeEvalArgv(args);
217
- if (preserved !== args)
218
- commandArg = preserved;
357
+ if (resolved === input) {
358
+ // Always pass the argv array to avoid lossy re-tokenization.
359
+ // For shell-off + node -e, apply outer-quote stripping first.
360
+ commandArg =
361
+ shellSetting === false ? maybePreserveNodeEvalArgv(args) : args;
219
362
  }
220
363
  await execShellCommandBatch({
221
364
  command: commandArg,
365
+ ...(concurrency !== undefined ? { concurrency } : {}),
222
366
  dotenvEnv,
223
367
  ...(envBag ? { getDotenvCliOptions: envBag } : {}),
224
368
  globs,
225
369
  ignoreErrors,
226
370
  list: false,
227
371
  logger,
372
+ parallel,
228
373
  ...(pkgCwd ? { pkgCwd } : {}),
229
374
  rootPath,
230
375
  shell: shellSetting,
@@ -279,6 +424,8 @@ const attachBatchDefaultAction = (plugin, cli, pluginOpts, parent) => {
279
424
  const ignoreErrors = Boolean(opts.ignoreErrors);
280
425
  let globs = typeof opts.globs === 'string' ? opts.globs : (cfg.globs ?? '*');
281
426
  const list = Boolean(opts.list);
427
+ const parallel = opts.parallel !== undefined ? opts.parallel : Boolean(cfg.parallel);
428
+ const concurrency = typeof opts.concurrency === 'number' ? opts.concurrency : cfg.concurrency;
282
429
  const pkgCwd = opts.pkgCwd !== undefined ? opts.pkgCwd : Boolean(cfg.pkgCwd);
283
430
  const rootPath = typeof opts.rootPath === 'string'
284
431
  ? opts.rootPath
@@ -297,11 +444,13 @@ const attachBatchDefaultAction = (plugin, cli, pluginOpts, parent) => {
297
444
  const commandArg = resolved;
298
445
  await execShellCommandBatch({
299
446
  command: commandArg,
447
+ ...(concurrency !== undefined ? { concurrency } : {}),
300
448
  dotenvEnv,
301
449
  globs,
302
450
  ignoreErrors,
303
451
  list: false,
304
452
  logger,
453
+ parallel,
305
454
  ...(pkgCwd ? { pkgCwd } : {}),
306
455
  rootPath,
307
456
  shell: shellSetting,
@@ -316,10 +465,12 @@ const attachBatchDefaultAction = (plugin, cli, pluginOpts, parent) => {
316
465
  globs = [globs, extra].filter(Boolean).join(' ');
317
466
  const shellMerged = pluginOpts.shell ?? cfg.shell ?? merged.shell ?? false;
318
467
  await execShellCommandBatch({
468
+ ...(concurrency !== undefined ? { concurrency } : {}),
319
469
  globs,
320
470
  ignoreErrors,
321
471
  list: true,
322
472
  logger,
473
+ parallel,
323
474
  ...(pkgCwd ? { pkgCwd } : {}),
324
475
  rootPath,
325
476
  shell: shellMerged,
@@ -336,11 +487,13 @@ const attachBatchDefaultAction = (plugin, cli, pluginOpts, parent) => {
336
487
  const shellOpt = pluginOpts.shell ?? cfg.shell ?? merged.shell;
337
488
  await execShellCommandBatch({
338
489
  command: resolveCommand(scriptsOpt, commandOpt),
490
+ ...(concurrency !== undefined ? { concurrency } : {}),
339
491
  dotenvEnv,
340
492
  globs,
341
493
  ignoreErrors,
342
494
  list,
343
495
  logger,
496
+ parallel,
344
497
  ...(pkgCwd ? { pkgCwd } : {}),
345
498
  rootPath,
346
499
  shell: resolveShell(scriptsOpt, commandOpt, shellOpt),
@@ -351,10 +504,12 @@ const attachBatchDefaultAction = (plugin, cli, pluginOpts, parent) => {
351
504
  // list only (explicit --list without --command)
352
505
  const shellOnly = pluginOpts.shell ?? cfg.shell ?? merged.shell ?? false;
353
506
  await execShellCommandBatch({
507
+ ...(concurrency !== undefined ? { concurrency } : {}),
354
508
  globs,
355
509
  ignoreErrors,
356
510
  list: true,
357
511
  logger,
512
+ parallel,
358
513
  ...(pkgCwd ? { pkgCwd } : {}),
359
514
  rootPath,
360
515
  shell: shellOnly,
@@ -399,37 +554,16 @@ function attachBatchOptions(plugin, cli) {
399
554
  .option('-c, --command <string>', 'command executed according to the base shell resolution')
400
555
  .option('-l, --list', 'list working directories without executing command')
401
556
  .option('-e, --ignore-errors', 'ignore errors and continue with next path')
557
+ .option('-P, --parallel', 'execute commands in parallel across directories')
558
+ .option('-C, --concurrency <n>', `max concurrent executions in parallel mode (default: ${String(defaultConcurrency)})`, (v) => {
559
+ const n = Number.parseInt(v, 10);
560
+ if (Number.isNaN(n) || n < 1)
561
+ throw new Error(`invalid concurrency: ${v}`);
562
+ return n;
563
+ })
402
564
  .argument('[command...]'));
403
565
  }
404
566
 
405
- /**
406
- * Zod schema for a single script entry (string or object).
407
- */
408
- const ScriptSchema = z.union([
409
- z.string(),
410
- z.object({
411
- /** Command string to execute. */
412
- cmd: z.string(),
413
- /** Optional shell override for this script entry. */
414
- shell: z.union([z.string(), z.boolean()]).optional(),
415
- }),
416
- ]);
417
- /**
418
- * Zod schema for batch plugin configuration.
419
- */
420
- const batchPluginConfigSchema = z.object({
421
- /** Optional scripts table scoped to the batch plugin. */
422
- scripts: z.record(z.string(), ScriptSchema).optional(),
423
- /** Optional default shell for batch execution (overridden by per-script shell when present). */
424
- shell: z.union([z.string(), z.boolean()]).optional(),
425
- /** Root path for discovery, relative to CWD (or package root when pkgCwd is true). */
426
- rootPath: z.string().optional(),
427
- /** Space-delimited glob patterns used to discover directories. */
428
- globs: z.string().optional(),
429
- /** When true, resolve the batch root from the nearest package directory. */
430
- pkgCwd: z.boolean().optional(),
431
- });
432
-
433
567
  /**
434
568
  * @packageDocumentation
435
569
  * Batch plugin subpath. Provides the `batch` command for executing a command
@@ -272,7 +272,7 @@ type ProcessEnv = Record<string, string | undefined>;
272
272
  * and the selected environment (if any), and returns either a string to set
273
273
  * or `undefined` to unset/skip the variable.
274
274
  */
275
- type GetDotenvDynamicFunction = (vars: ProcessEnv, env: string | undefined) => string | undefined;
275
+ type GetDotenvDynamicFunction = (vars: ProcessEnv, env: string | undefined) => string | null | undefined;
276
276
  /**
277
277
  * A map of dynamic variable definitions.
278
278
  * Keys are variable names; values are either literal strings or functions.
@@ -358,6 +358,12 @@ interface ResolveAndLoadOptions {
358
358
  * @default true
359
359
  */
360
360
  runAfterResolve?: boolean;
361
+ /**
362
+ * Name of the invoked subcommand (plugin namespace). When provided,
363
+ * only the matching plugin subtree runs afterResolve hooks.
364
+ * When omitted, all plugins run afterResolve (backward compatibility).
365
+ */
366
+ invokedSubcommand?: string;
361
367
  }
362
368
  /**
363
369
  * Structural public interface for the host exposed to plugins.
@@ -451,6 +457,11 @@ interface GetDotenvCliPlugin<TOptions extends GetDotenvOptions = GetDotenvOption
451
457
  * After the dotenv context is resolved, initialize any clients/secrets
452
458
  * or attach per-plugin state under ctx.plugins (by convention).
453
459
  * Runs parent → children (pre-order).
460
+ *
461
+ * **Scoping**: When the host provides an invoked subcommand filter,
462
+ * afterResolve only fires for plugins whose namespace matches the
463
+ * invoked command path. A plugin's afterResolve should never produce
464
+ * side effects for commands outside its subtree.
454
465
  */
455
466
  afterResolve?: (cli: GetDotenvCliPublic<TOptions, TArgs, TOpts, TGlobal>, ctx: GetDotenvCliCtx<TOptions>) => void | Promise<void>;
456
467
  /** Zod schema for this plugin's config slice (from config.plugins[…]). */
@@ -1,5 +1,5 @@
1
1
  import 'radash';
2
- import './chunks/readMergedOptions-CraAnYdB.mjs';
2
+ import './chunks/readMergedOptions-BT1C87_u.mjs';
3
3
  import 'execa';
4
4
  import 'node:buffer';
5
5
  import 'fs-extra';
@@ -10,13 +10,13 @@ import 'url';
10
10
  import '@commander-js/extra-typings';
11
11
  import 'nanoid';
12
12
  import 'dotenv';
13
- import './chunks/loader-V1vbmtyw.mjs';
13
+ import './chunks/loader-C3DtD6HB.mjs';
14
14
  import 'package-directory';
15
15
  import 'yaml';
16
16
  import 'zod';
17
- export { c as cmdPlugin } from './chunks/index-Dd6S1nZ-.mjs';
18
- import './chunks/readDotenvCascade-Dgx4SC1p.mjs';
17
+ export { c as cmdPlugin } from './chunks/index-DnG3N6yj.mjs';
18
+ import './chunks/readDotenvCascade-CfFPgLCp.mjs';
19
19
  import './chunks/loadModuleDefault-Dj8B3Stt.mjs';
20
- import './chunks/spawnEnv-CKgnHGpr.mjs';
20
+ import './chunks/spawnEnv-DvisqPiU.mjs';
21
21
  import './chunks/invoke-DuRPU1oC.mjs';
22
- import './chunks/resolveCliOptions-BMBkWDYJ.mjs';
22
+ import './chunks/resolveCliOptions-BbfouWSK.mjs';
@@ -272,7 +272,7 @@ type ProcessEnv = Record<string, string | undefined>;
272
272
  * and the selected environment (if any), and returns either a string to set
273
273
  * or `undefined` to unset/skip the variable.
274
274
  */
275
- type GetDotenvDynamicFunction = (vars: ProcessEnv, env: string | undefined) => string | undefined;
275
+ type GetDotenvDynamicFunction = (vars: ProcessEnv, env: string | undefined) => string | null | undefined;
276
276
  /**
277
277
  * A map of dynamic variable definitions.
278
278
  * Keys are variable names; values are either literal strings or functions.
@@ -358,6 +358,12 @@ interface ResolveAndLoadOptions {
358
358
  * @default true
359
359
  */
360
360
  runAfterResolve?: boolean;
361
+ /**
362
+ * Name of the invoked subcommand (plugin namespace). When provided,
363
+ * only the matching plugin subtree runs afterResolve hooks.
364
+ * When omitted, all plugins run afterResolve (backward compatibility).
365
+ */
366
+ invokedSubcommand?: string;
361
367
  }
362
368
  /**
363
369
  * Structural public interface for the host exposed to plugins.
@@ -451,6 +457,11 @@ interface GetDotenvCliPlugin<TOptions extends GetDotenvOptions = GetDotenvOption
451
457
  * After the dotenv context is resolved, initialize any clients/secrets
452
458
  * or attach per-plugin state under ctx.plugins (by convention).
453
459
  * Runs parent → children (pre-order).
460
+ *
461
+ * **Scoping**: When the host provides an invoked subcommand filter,
462
+ * afterResolve only fires for plugins whose namespace matches the
463
+ * invoked command path. A plugin's afterResolve should never produce
464
+ * side effects for commands outside its subtree.
454
465
  */
455
466
  afterResolve?: (cli: GetDotenvCliPublic<TOptions, TArgs, TOpts, TGlobal>, ctx: GetDotenvCliCtx<TOptions>) => void | Promise<void>;
456
467
  /** Zod schema for this plugin's config slice (from config.plugins[…]). */
@@ -1,4 +1,4 @@
1
- import { r as readMergedOptions, g as definePlugin } from './chunks/readMergedOptions-CraAnYdB.mjs';
1
+ import { r as readMergedOptions, d as definePlugin } from './chunks/readMergedOptions-BT1C87_u.mjs';
2
2
  import 'execa';
3
3
  import 'radash';
4
4
  import 'node:buffer';
@@ -10,14 +10,14 @@ import 'url';
10
10
  import '@commander-js/extra-typings';
11
11
  import 'nanoid';
12
12
  import 'dotenv';
13
- import './chunks/loader-V1vbmtyw.mjs';
13
+ import './chunks/loader-C3DtD6HB.mjs';
14
14
  import { packageDirectory } from 'package-directory';
15
15
  import 'yaml';
16
16
  import 'zod';
17
17
  import { stdin, stdout } from 'node:process';
18
18
  import { createInterface } from 'readline/promises';
19
19
  import { fileURLToPath } from 'node:url';
20
- import './chunks/readDotenvCascade-Dgx4SC1p.mjs';
20
+ import './chunks/readDotenvCascade-CfFPgLCp.mjs';
21
21
  import './chunks/loadModuleDefault-Dj8B3Stt.mjs';
22
22
 
23
23
  /**