@karmaniverous/get-dotenv 5.2.6 → 6.0.0-1

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 (55) hide show
  1. package/README.md +106 -70
  2. package/dist/cliHost.d.ts +232 -226
  3. package/dist/cliHost.mjs +777 -545
  4. package/dist/config.d.ts +7 -2
  5. package/dist/env-overlay.d.ts +21 -9
  6. package/dist/env-overlay.mjs +14 -19
  7. package/dist/getdotenv.cli.mjs +1366 -1163
  8. package/dist/index.d.ts +415 -242
  9. package/dist/index.mjs +1364 -1414
  10. package/dist/plugins-aws.d.ts +149 -94
  11. package/dist/plugins-aws.mjs +307 -195
  12. package/dist/plugins-batch.d.ts +153 -99
  13. package/dist/plugins-batch.mjs +277 -95
  14. package/dist/plugins-cmd.d.ts +140 -94
  15. package/dist/plugins-cmd.mjs +636 -502
  16. package/dist/plugins-demo.d.ts +140 -94
  17. package/dist/plugins-demo.mjs +237 -46
  18. package/dist/plugins-init.d.ts +140 -94
  19. package/dist/plugins-init.mjs +129 -12
  20. package/dist/plugins.d.ts +166 -103
  21. package/dist/plugins.mjs +977 -840
  22. package/package.json +15 -53
  23. package/templates/cli/ts/plugins/hello.ts +27 -6
  24. package/templates/config/js/getdotenv.config.js +1 -1
  25. package/templates/config/ts/getdotenv.config.ts +9 -2
  26. package/dist/cliHost.cjs +0 -1875
  27. package/dist/cliHost.d.cts +0 -409
  28. package/dist/cliHost.d.mts +0 -409
  29. package/dist/config.cjs +0 -252
  30. package/dist/config.d.cts +0 -55
  31. package/dist/config.d.mts +0 -55
  32. package/dist/env-overlay.cjs +0 -163
  33. package/dist/env-overlay.d.cts +0 -50
  34. package/dist/env-overlay.d.mts +0 -50
  35. package/dist/index.cjs +0 -4140
  36. package/dist/index.d.cts +0 -457
  37. package/dist/index.d.mts +0 -457
  38. package/dist/plugins-aws.cjs +0 -667
  39. package/dist/plugins-aws.d.cts +0 -158
  40. package/dist/plugins-aws.d.mts +0 -158
  41. package/dist/plugins-batch.cjs +0 -616
  42. package/dist/plugins-batch.d.cts +0 -180
  43. package/dist/plugins-batch.d.mts +0 -180
  44. package/dist/plugins-cmd.cjs +0 -1113
  45. package/dist/plugins-cmd.d.cts +0 -178
  46. package/dist/plugins-cmd.d.mts +0 -178
  47. package/dist/plugins-demo.cjs +0 -307
  48. package/dist/plugins-demo.d.cts +0 -158
  49. package/dist/plugins-demo.d.mts +0 -158
  50. package/dist/plugins-init.cjs +0 -289
  51. package/dist/plugins-init.d.cts +0 -162
  52. package/dist/plugins-init.d.mts +0 -162
  53. package/dist/plugins.cjs +0 -2283
  54. package/dist/plugins.d.cts +0 -210
  55. package/dist/plugins.d.mts +0 -210
@@ -1,616 +0,0 @@
1
- 'use strict';
2
-
3
- var commander = require('commander');
4
- var globby = require('globby');
5
- var packageDirectory = require('package-directory');
6
- var path = require('path');
7
- var execa = require('execa');
8
- var zod = require('zod');
9
-
10
- /** src/cliHost/definePlugin.ts
11
- * Plugin contracts for the GetDotenv CLI host.
12
- *
13
- * This module exposes a structural public interface for the host that plugins
14
- * should use (GetDotenvCliPublic). Using a structural type at the seam avoids
15
- * nominal class identity issues (private fields) in downstream consumers.
16
- */
17
- /**
18
- * Define a GetDotenv CLI plugin with compositional helpers.
19
- *
20
- * @example
21
- * const parent = definePlugin(\{ id: 'p', setup(cli) \{ /* ... *\/ \} \})
22
- * .use(childA)
23
- * .use(childB);
24
- */
25
- const definePlugin = (spec) => {
26
- const { children = [], ...rest } = spec;
27
- const plugin = {
28
- ...rest,
29
- children: [...children],
30
- use(child) {
31
- this.children.push(child);
32
- return this;
33
- },
34
- };
35
- return plugin;
36
- };
37
-
38
- // Minimal tokenizer for shell-off execution:
39
- // Splits by whitespace while preserving quoted segments (single or double quotes).
40
- const tokenize = (command) => {
41
- const out = [];
42
- let cur = '';
43
- let quote = null;
44
- for (let i = 0; i < command.length; i++) {
45
- const c = command.charAt(i);
46
- if (quote) {
47
- if (c === quote) {
48
- // Support doubled quotes inside a quoted segment (Windows/PowerShell style):
49
- // "" -> " and '' -> '
50
- const next = command.charAt(i + 1);
51
- if (next === quote) {
52
- cur += quote;
53
- i += 1; // skip the second quote
54
- }
55
- else {
56
- // end of quoted segment
57
- quote = null;
58
- }
59
- }
60
- else {
61
- cur += c;
62
- }
63
- }
64
- else {
65
- if (c === '"' || c === "'") {
66
- quote = c;
67
- }
68
- else if (/\s/.test(c)) {
69
- if (cur) {
70
- out.push(cur);
71
- cur = '';
72
- }
73
- }
74
- else {
75
- cur += c;
76
- }
77
- }
78
- }
79
- if (cur)
80
- out.push(cur);
81
- return out;
82
- };
83
-
84
- const dbg = (...args) => {
85
- if (process.env.GETDOTENV_DEBUG) {
86
- // Use stderr to avoid interfering with stdout assertions
87
- console.error('[getdotenv:run]', ...args);
88
- }
89
- };
90
- // Strip repeated symmetric outer quotes (single or double) until stable.
91
- // This is safe for argv arrays passed to execa (no quoting needed) and avoids
92
- // passing quote characters through to Node (e.g., for `node -e "<code>"`).
93
- // Handles stacked quotes from shells like PowerShell: """code""" -> code.
94
- const stripOuterQuotes = (s) => {
95
- let out = s;
96
- // Repeatedly trim only when the entire string is wrapped in matching quotes.
97
- // Stop as soon as the ends are asymmetric or no quotes remain.
98
- while (out.length >= 2) {
99
- const a = out.charAt(0);
100
- const b = out.charAt(out.length - 1);
101
- const symmetric = (a === '"' && b === '"') || (a === "'" && b === "'");
102
- if (!symmetric)
103
- break;
104
- out = out.slice(1, -1);
105
- }
106
- return out;
107
- };
108
- // Convert NodeJS.ProcessEnv (string | undefined values) to the shape execa
109
- // expects (Readonly<Partial<Record<string, string>>>), dropping undefineds.
110
- const sanitizeEnv = (env) => {
111
- if (!env)
112
- return undefined;
113
- const entries = Object.entries(env).filter((e) => typeof e[1] === 'string');
114
- return entries.length > 0 ? Object.fromEntries(entries) : undefined;
115
- };
116
- const runCommand = async (command, shell, opts) => {
117
- if (shell === false) {
118
- let file;
119
- let args = [];
120
- if (Array.isArray(command)) {
121
- file = command[0];
122
- args = command.slice(1).map(stripOuterQuotes);
123
- }
124
- else {
125
- const tokens = tokenize(command);
126
- file = tokens[0];
127
- args = tokens.slice(1);
128
- }
129
- if (!file)
130
- return 0;
131
- dbg('exec (plain)', { file, args, stdio: opts.stdio });
132
- // Build options without injecting undefined properties (exactOptionalPropertyTypes).
133
- const envSan = sanitizeEnv(opts.env);
134
- const plainOpts = {};
135
- if (opts.cwd !== undefined)
136
- plainOpts.cwd = opts.cwd;
137
- if (envSan !== undefined)
138
- plainOpts.env = envSan;
139
- if (opts.stdio !== undefined)
140
- plainOpts.stdio = opts.stdio;
141
- const result = await execa.execa(file, args, plainOpts);
142
- if (opts.stdio === 'pipe' && result.stdout) {
143
- process.stdout.write(result.stdout + (result.stdout.endsWith('\n') ? '' : '\n'));
144
- }
145
- const exit = result?.exitCode;
146
- dbg('exit (plain)', { exitCode: exit });
147
- return typeof exit === 'number' ? exit : Number.NaN;
148
- }
149
- else {
150
- const commandStr = Array.isArray(command) ? command.join(' ') : command;
151
- dbg('exec (shell)', {
152
- shell: typeof shell === 'string' ? shell : 'custom',
153
- stdio: opts.stdio,
154
- command: commandStr,
155
- });
156
- const envSan = sanitizeEnv(opts.env);
157
- const shellOpts = { shell };
158
- if (opts.cwd !== undefined)
159
- shellOpts.cwd = opts.cwd;
160
- if (envSan !== undefined)
161
- shellOpts.env = envSan;
162
- if (opts.stdio !== undefined)
163
- shellOpts.stdio = opts.stdio;
164
- const result = await execa.execaCommand(commandStr, shellOpts);
165
- const out = result?.stdout;
166
- if (opts.stdio === 'pipe' && out) {
167
- process.stdout.write(out + (out.endsWith('\n') ? '' : '\n'));
168
- }
169
- const exit = result?.exitCode;
170
- dbg('exit (shell)', { exitCode: exit });
171
- return typeof exit === 'number' ? exit : Number.NaN;
172
- }
173
- };
174
-
175
- const dropUndefined = (bag) => Object.fromEntries(Object.entries(bag).filter((e) => typeof e[1] === 'string'));
176
- /** Build a sanitized env for child processes from base + overlay. */
177
- const buildSpawnEnv = (base, overlay) => {
178
- const raw = {
179
- ...(base ?? {}),
180
- ...(overlay ?? {}),
181
- };
182
- // Drop undefined first
183
- const entries = Object.entries(dropUndefined(raw));
184
- if (process.platform === 'win32') {
185
- // Windows: keys are case-insensitive; collapse duplicates
186
- const byLower = new Map();
187
- for (const [k, v] of entries) {
188
- byLower.set(k.toLowerCase(), [k, v]); // last wins; preserve latest casing
189
- }
190
- const out = {};
191
- for (const [, [k, v]] of byLower)
192
- out[k] = v;
193
- // HOME fallback from USERPROFILE (common expectation)
194
- if (!Object.prototype.hasOwnProperty.call(out, 'HOME')) {
195
- const up = out['USERPROFILE'];
196
- if (typeof up === 'string' && up.length > 0)
197
- out['HOME'] = up;
198
- }
199
- // Normalize TMP/TEMP coherence (pick any present; reflect to both)
200
- const tmp = out['TMP'] ?? out['TEMP'];
201
- if (typeof tmp === 'string' && tmp.length > 0) {
202
- out['TMP'] = tmp;
203
- out['TEMP'] = tmp;
204
- }
205
- return out;
206
- }
207
- // POSIX: keep keys as-is
208
- const out = Object.fromEntries(entries);
209
- // Ensure TMPDIR exists when any temp key is present (best-effort)
210
- const tmpdir = out['TMPDIR'] ?? out['TMP'] ?? out['TEMP'];
211
- if (typeof tmpdir === 'string' && tmpdir.length > 0) {
212
- out['TMPDIR'] = tmpdir;
213
- }
214
- return out;
215
- };
216
-
217
- const globPaths = async ({ globs, logger, pkgCwd, rootPath, }) => {
218
- let cwd = process.cwd();
219
- if (pkgCwd) {
220
- const pkgDir = await packageDirectory.packageDirectory();
221
- if (!pkgDir) {
222
- logger.error('No package directory found.');
223
- process.exit(0);
224
- }
225
- cwd = pkgDir;
226
- }
227
- const absRootPath = path.posix.join(cwd.split(path.sep).join(path.posix.sep), rootPath.split(path.sep).join(path.posix.sep));
228
- const paths = await globby.globby(globs.split(/\s+/), {
229
- cwd: absRootPath,
230
- expandDirectories: false,
231
- onlyDirectories: true,
232
- absolute: true,
233
- });
234
- if (!paths.length) {
235
- logger.error(`No paths found for globs '${globs}' at '${absRootPath}'.`);
236
- process.exit(0);
237
- }
238
- return { absRootPath, paths };
239
- };
240
- const execShellCommandBatch = async ({ command, getDotenvCliOptions, globs, ignoreErrors, list, logger, pkgCwd, rootPath, shell, }) => {
241
- const capture = process.env.GETDOTENV_STDIO === 'pipe' ||
242
- Boolean(getDotenvCliOptions?.capture); // Require a command only when not listing. In list mode, a command is optional.
243
- if (!command && !list) {
244
- logger.error(`No command provided. Use --command or --list.`);
245
- process.exit(0);
246
- }
247
- const { absRootPath, paths } = await globPaths({
248
- globs,
249
- logger,
250
- rootPath,
251
- // exactOptionalPropertyTypes: only include when defined
252
- ...(pkgCwd !== undefined ? { pkgCwd } : {}),
253
- });
254
- const headerTitle = list
255
- ? 'Listing working directories...'
256
- : 'Executing command batch...';
257
- logger.info('');
258
- const headerRootPath = `ROOT: ${absRootPath}`;
259
- const headerGlobs = `GLOBS: ${globs}`;
260
- // Prepare a safe label for the header (avoid undefined in template)
261
- const commandLabel = Array.isArray(command)
262
- ? command.join(' ')
263
- : typeof command === 'string' && command.length > 0
264
- ? command
265
- : '';
266
- const headerCommand = list ? `CMD: (list only)` : `CMD: ${commandLabel}`;
267
- logger.info('*'.repeat(Math.max(headerTitle.length, headerRootPath.length, headerGlobs.length, headerCommand.length)));
268
- logger.info(headerTitle);
269
- logger.info('');
270
- logger.info(headerRootPath);
271
- logger.info(headerGlobs);
272
- logger.info(headerCommand);
273
- for (const path of paths) {
274
- // Write path and command to console.
275
- const pathLabel = `CWD: ${path}`;
276
- if (list) {
277
- logger.info(pathLabel);
278
- continue;
279
- }
280
- logger.info('');
281
- logger.info('*'.repeat(pathLabel.length));
282
- logger.info(pathLabel);
283
- logger.info(headerCommand);
284
- // Execute command.
285
- try {
286
- const hasCmd = (typeof command === 'string' && command.length > 0) ||
287
- (Array.isArray(command) && command.length > 0);
288
- if (hasCmd) {
289
- const envBag = getDotenvCliOptions !== undefined
290
- ? { getDotenvCliOptions: JSON.stringify(getDotenvCliOptions) }
291
- : undefined;
292
- await runCommand(command, shell, {
293
- cwd: path,
294
- env: buildSpawnEnv(process.env, envBag),
295
- stdio: capture ? 'pipe' : 'inherit',
296
- });
297
- }
298
- else {
299
- // Should not occur due to the early guard; retain for type safety.
300
- logger.error(`No command provided. Use --command or --list.`);
301
- process.exit(0);
302
- }
303
- }
304
- catch (error) {
305
- if (!ignoreErrors) {
306
- throw error;
307
- }
308
- }
309
- }
310
- logger.info('');
311
- };
312
-
313
- /**
314
- * Batch services (neutral): resolve command and shell settings.
315
- * Shared by the generator path and the batch plugin to avoid circular deps.
316
- */
317
- /**
318
- * Resolve a command string from the {@link Scripts} table.
319
- * A script may be expressed as a string or an object with a `cmd` property.
320
- *
321
- * @param scripts - Optional scripts table.
322
- * @param command - User-provided command name or string.
323
- * @returns Resolved command string (falls back to the provided command).
324
- */
325
- const resolveCommand = (scripts, command) => scripts && typeof scripts[command] === 'object'
326
- ? scripts[command].cmd
327
- : (scripts?.[command] ?? command);
328
- /**
329
- * Resolve the shell setting for a given command:
330
- * - If the script entry is an object, prefer its `shell` override.
331
- * - Otherwise use the provided `shell` (string | boolean).
332
- *
333
- * @param scripts - Optional scripts table.
334
- * @param command - User-provided command name or string.
335
- * @param shell - Global shell preference (string | boolean).
336
- */
337
- const resolveShell = (scripts, command, shell) => scripts && typeof scripts[command] === 'object'
338
- ? (scripts[command].shell ?? false)
339
- : (shell ?? false);
340
-
341
- /**
342
- * Build the default "cmd" subcommand action for the batch plugin.
343
- * Mirrors the original inline implementation with identical behavior.
344
- */
345
- const buildDefaultCmdAction = (cli, batchCmd, opts) => async (commandParts, _subOpts, _thisCommand) => {
346
- const loggerLocal = opts.logger ?? console;
347
- // Guard: when invoked without positional args (e.g., `batch --list`),
348
- // defer entirely to the parent action handler.
349
- const argsRaw = Array.isArray(commandParts)
350
- ? commandParts
351
- : [];
352
- const localList = argsRaw.includes('-l') || argsRaw.includes('--list');
353
- const args = localList
354
- ? argsRaw.filter((t) => t !== '-l' && t !== '--list')
355
- : argsRaw;
356
- // Access merged per-plugin config from host context (if any).
357
- const ctx = cli.getCtx();
358
- const cfgRaw = (ctx?.pluginConfigs?.['batch'] ?? {});
359
- const cfg = (cfgRaw || {});
360
- // Resolve batch flags from the captured parent (batch) command.
361
- const raw = batchCmd.opts();
362
- const listFromParent = !!raw.list;
363
- const ignoreErrors = !!raw.ignoreErrors;
364
- const globs = typeof raw.globs === 'string' ? raw.globs : (cfg.globs ?? '*');
365
- const pkgCwd = raw.pkgCwd !== undefined ? !!raw.pkgCwd : !!cfg.pkgCwd;
366
- const rootPath = typeof raw.rootPath === 'string' ? raw.rootPath : (cfg.rootPath ?? './');
367
- // Resolve scripts/shell with precedence:
368
- // plugin opts → plugin config → merged root CLI options
369
- const mergedBag = ((batchCmd.parent ?? null)?.getDotenvCliOptions ?? {});
370
- const scripts = opts.scripts ?? cfg.scripts ?? mergedBag.scripts;
371
- const shell = opts.shell ?? cfg.shell ?? mergedBag.shell;
372
- // If no positional args were given, bridge to --command/--list paths here.
373
- if (args.length === 0) {
374
- const commandOpt = typeof raw.command === 'string' ? raw.command : undefined;
375
- if (typeof commandOpt === 'string') {
376
- await execShellCommandBatch({
377
- command: resolveCommand(scripts, commandOpt),
378
- globs,
379
- ignoreErrors,
380
- list: false,
381
- logger: loggerLocal,
382
- ...(pkgCwd ? { pkgCwd } : {}),
383
- rootPath,
384
- shell: resolveShell(scripts, commandOpt, shell),
385
- });
386
- return;
387
- }
388
- if (raw.list || localList) {
389
- await execShellCommandBatch({
390
- globs,
391
- ignoreErrors,
392
- list: true,
393
- logger: loggerLocal,
394
- ...(pkgCwd ? { pkgCwd } : {}),
395
- rootPath,
396
- shell: (shell ?? false),
397
- });
398
- return;
399
- }
400
- {
401
- const lr = loggerLocal;
402
- const emit = lr.error ?? lr.log;
403
- emit(`No command provided. Use --command or --list.`);
404
- }
405
- process.exit(0);
406
- }
407
- // If a local list flag was supplied with positional tokens (and no --command),
408
- // treat tokens as additional globs and execute list mode.
409
- if (localList && typeof raw.command !== 'string') {
410
- const extraGlobs = args.map(String).join(' ').trim();
411
- const mergedGlobs = [globs, extraGlobs].filter(Boolean).join(' ');
412
- const shellBag = ((batchCmd.parent ?? undefined)?.getDotenvCliOptions ?? {});
413
- await execShellCommandBatch({
414
- globs: mergedGlobs,
415
- ignoreErrors,
416
- list: true,
417
- logger: loggerLocal,
418
- ...(pkgCwd ? { pkgCwd } : {}),
419
- rootPath,
420
- shell: (shell ?? shellBag.shell ?? false),
421
- });
422
- return;
423
- }
424
- // If parent list flag is set and positional tokens are present (and no --command),
425
- // treat tokens as additional globs for list-only mode.
426
- if (listFromParent && args.length > 0 && typeof raw.command !== 'string') {
427
- const extra = args.map(String).join(' ').trim();
428
- const mergedGlobs = [globs, extra].filter(Boolean).join(' ');
429
- const mergedBag2 = ((batchCmd.parent ?? undefined)?.getDotenvCliOptions ?? {});
430
- await execShellCommandBatch({
431
- globs: mergedGlobs,
432
- ignoreErrors,
433
- list: true,
434
- logger: loggerLocal,
435
- ...(pkgCwd ? { pkgCwd } : {}),
436
- rootPath,
437
- shell: (shell ?? mergedBag2.shell ?? false),
438
- });
439
- return;
440
- }
441
- // Join positional args as the command to execute.
442
- const input = args.map(String).join(' ');
443
- // Optional: round-trip parent merged options if present (shipped CLI).
444
- const envBag = (batchCmd.parent ?? undefined)?.getDotenvCliOptions;
445
- const mergedExec = ((batchCmd.parent ?? undefined)?.getDotenvCliOptions ?? {});
446
- const scriptsExec = scripts ?? mergedExec.scripts;
447
- const shellExec = shell ?? mergedExec.shell;
448
- const resolved = resolveCommand(scriptsExec, input);
449
- const shellSetting = resolveShell(scriptsExec, input, shellExec);
450
- // Preserve argv array only for shell-off Node -e snippets to avoid
451
- // lossy re-tokenization (Windows/PowerShell quoting). For simple
452
- // commands (e.g., "echo OK") keep string form to satisfy unit tests.
453
- let commandArg = resolved;
454
- if (shellSetting === false && resolved === input) {
455
- const first = (args[0] ?? '').toLowerCase();
456
- const hasEval = args.includes('-e') || args.includes('--eval');
457
- if (first === 'node' && hasEval) {
458
- commandArg = args.map(String);
459
- }
460
- }
461
- await execShellCommandBatch({
462
- command: commandArg,
463
- ...(envBag ? { getDotenvCliOptions: envBag } : {}),
464
- globs,
465
- ignoreErrors,
466
- list: false,
467
- logger: loggerLocal,
468
- ...(pkgCwd ? { pkgCwd } : {}),
469
- rootPath,
470
- shell: shellSetting,
471
- });
472
- };
473
-
474
- /**
475
- * Build the parent "batch" action handler (no explicit subcommand).
476
- */
477
- const buildParentAction = (cli, opts) => async (commandParts, thisCommand) => {
478
- const logger = opts.logger ?? console;
479
- // Ensure context exists (host preSubcommand on root creates if missing).
480
- const ctx = cli.getCtx();
481
- const cfgRaw = (ctx?.pluginConfigs?.['batch'] ?? {});
482
- const cfg = (cfgRaw || {});
483
- const raw = thisCommand.opts();
484
- const commandOpt = typeof raw.command === 'string' ? raw.command : undefined;
485
- const ignoreErrors = !!raw.ignoreErrors;
486
- let globs = typeof raw.globs === 'string' ? raw.globs : (cfg.globs ?? '*');
487
- const list = !!raw.list;
488
- const pkgCwd = raw.pkgCwd !== undefined ? !!raw.pkgCwd : !!cfg.pkgCwd;
489
- const rootPath = typeof raw.rootPath === 'string' ? raw.rootPath : (cfg.rootPath ?? './');
490
- // Treat parent positional tokens as the command when no explicit 'cmd' is used.
491
- const argsParent = Array.isArray(commandParts) ? commandParts : [];
492
- if (argsParent.length > 0 && !list) {
493
- const input = argsParent.map(String).join(' ');
494
- const mergedBag = ((thisCommand.parent ?? null)?.getDotenvCliOptions ?? {});
495
- const scriptsAll = opts.scripts ?? cfg.scripts ?? mergedBag.scripts;
496
- const shellAll = opts.shell ?? cfg.shell ?? mergedBag.shell;
497
- const resolved = resolveCommand(scriptsAll, input);
498
- const shellSetting = resolveShell(scriptsAll, input, shellAll);
499
- // Parent path: pass a string; executor handles shell-specific details.
500
- const commandArg = resolved;
501
- await execShellCommandBatch({
502
- command: commandArg,
503
- globs,
504
- ignoreErrors,
505
- list: false,
506
- logger,
507
- ...(pkgCwd ? { pkgCwd } : {}),
508
- rootPath,
509
- shell: shellSetting,
510
- });
511
- return;
512
- }
513
- // List-only: merge extra positional tokens into globs when no --command is present.
514
- if (list && argsParent.length > 0 && !commandOpt) {
515
- const extra = argsParent.map(String).join(' ').trim();
516
- if (extra.length > 0)
517
- globs = [globs, extra].filter(Boolean).join(' ');
518
- const mergedBag = ((thisCommand.parent ?? null)?.getDotenvCliOptions ?? {});
519
- await execShellCommandBatch({
520
- globs,
521
- ignoreErrors,
522
- list: true,
523
- logger,
524
- ...(pkgCwd ? { pkgCwd } : {}),
525
- rootPath,
526
- shell: (opts.shell ?? cfg.shell ?? mergedBag.shell ?? false),
527
- });
528
- return;
529
- }
530
- if (!commandOpt && !list) {
531
- logger.error(`No command provided. Use --command or --list.`);
532
- process.exit(0);
533
- }
534
- if (typeof commandOpt === 'string') {
535
- const mergedBag = ((thisCommand.parent ?? null)?.getDotenvCliOptions ?? {});
536
- const scriptsOpt = opts.scripts ?? cfg.scripts ?? mergedBag.scripts;
537
- const shellOpt = opts.shell ?? cfg.shell ?? mergedBag.shell;
538
- await execShellCommandBatch({
539
- command: resolveCommand(scriptsOpt, commandOpt),
540
- globs,
541
- ignoreErrors,
542
- list,
543
- logger,
544
- ...(pkgCwd ? { pkgCwd } : {}),
545
- rootPath,
546
- shell: resolveShell(scriptsOpt, commandOpt, shellOpt),
547
- });
548
- return;
549
- }
550
- // list only (explicit --list without --command)
551
- const mergedBag = ((thisCommand.parent ?? null)?.getDotenvCliOptions ?? {});
552
- const shellOnly = (opts.shell ?? cfg.shell ?? mergedBag.shell ?? false);
553
- await execShellCommandBatch({
554
- globs,
555
- ignoreErrors,
556
- list: true,
557
- logger,
558
- ...(pkgCwd ? { pkgCwd } : {}),
559
- rootPath,
560
- shell: (shellOnly ?? false),
561
- });
562
- };
563
-
564
- // Per-plugin config schema (optional fields; used as defaults).
565
- const ScriptSchema = zod.z.union([
566
- zod.z.string(),
567
- zod.z.object({
568
- cmd: zod.z.string(),
569
- shell: zod.z.union([zod.z.string(), zod.z.boolean()]).optional(),
570
- }),
571
- ]);
572
- const BatchConfigSchema = zod.z.object({
573
- scripts: zod.z.record(zod.z.string(), ScriptSchema).optional(),
574
- shell: zod.z.union([zod.z.string(), zod.z.boolean()]).optional(),
575
- rootPath: zod.z.string().optional(),
576
- globs: zod.z.string().optional(),
577
- pkgCwd: zod.z.boolean().optional(),
578
- });
579
-
580
- /**
581
- * Batch plugin for the GetDotenv CLI host.
582
- *
583
- * Mirrors the legacy batch subcommand behavior without altering the shipped CLI. * Options:
584
- * - scripts/shell: used to resolve command and shell behavior per script or global default.
585
- * - logger: defaults to console.
586
- */
587
- const batchPlugin = (opts = {}) => definePlugin({
588
- id: 'batch',
589
- // Host validates this when config-loader is enabled; plugins may also
590
- // re-validate at action time as a safety belt.
591
- configSchema: BatchConfigSchema,
592
- setup(cli) {
593
- const ns = cli.ns('batch');
594
- const batchCmd = ns; // capture the parent "batch" command for default-subcommand context
595
- ns.description('Batch command execution across multiple working directories.')
596
- .enablePositionalOptions()
597
- .passThroughOptions()
598
- .option('-p, --pkg-cwd', 'use nearest package directory as current working directory')
599
- .option('-r, --root-path <string>', 'path to batch root directory from current working directory', './')
600
- .option('-g, --globs <string>', 'space-delimited globs from root path', '*')
601
- .option('-c, --command <string>', 'command executed according to the base shell resolution')
602
- .option('-l, --list', 'list working directories without executing command')
603
- .option('-e, --ignore-errors', 'ignore errors and continue with next path')
604
- .argument('[command...]')
605
- .addCommand(new commander.Command()
606
- .name('cmd')
607
- .description('execute command, conflicts with --command option (default subcommand)')
608
- .enablePositionalOptions()
609
- .passThroughOptions()
610
- .argument('[command...]')
611
- .action(buildDefaultCmdAction(cli, batchCmd, opts)), { isDefault: true })
612
- .action(buildParentAction(cli, opts));
613
- },
614
- });
615
-
616
- exports.batchPlugin = batchPlugin;