@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.
- package/README.md +106 -70
- package/dist/cliHost.d.ts +232 -226
- package/dist/cliHost.mjs +777 -545
- package/dist/config.d.ts +7 -2
- package/dist/env-overlay.d.ts +21 -9
- package/dist/env-overlay.mjs +14 -19
- package/dist/getdotenv.cli.mjs +1366 -1163
- package/dist/index.d.ts +415 -242
- package/dist/index.mjs +1364 -1414
- package/dist/plugins-aws.d.ts +149 -94
- package/dist/plugins-aws.mjs +307 -195
- package/dist/plugins-batch.d.ts +153 -99
- package/dist/plugins-batch.mjs +277 -95
- package/dist/plugins-cmd.d.ts +140 -94
- package/dist/plugins-cmd.mjs +636 -502
- package/dist/plugins-demo.d.ts +140 -94
- package/dist/plugins-demo.mjs +237 -46
- package/dist/plugins-init.d.ts +140 -94
- package/dist/plugins-init.mjs +129 -12
- package/dist/plugins.d.ts +166 -103
- package/dist/plugins.mjs +977 -840
- package/package.json +15 -53
- package/templates/cli/ts/plugins/hello.ts +27 -6
- package/templates/config/js/getdotenv.config.js +1 -1
- package/templates/config/ts/getdotenv.config.ts +9 -2
- package/dist/cliHost.cjs +0 -1875
- package/dist/cliHost.d.cts +0 -409
- package/dist/cliHost.d.mts +0 -409
- package/dist/config.cjs +0 -252
- package/dist/config.d.cts +0 -55
- package/dist/config.d.mts +0 -55
- package/dist/env-overlay.cjs +0 -163
- package/dist/env-overlay.d.cts +0 -50
- package/dist/env-overlay.d.mts +0 -50
- package/dist/index.cjs +0 -4140
- package/dist/index.d.cts +0 -457
- package/dist/index.d.mts +0 -457
- package/dist/plugins-aws.cjs +0 -667
- package/dist/plugins-aws.d.cts +0 -158
- package/dist/plugins-aws.d.mts +0 -158
- package/dist/plugins-batch.cjs +0 -616
- package/dist/plugins-batch.d.cts +0 -180
- package/dist/plugins-batch.d.mts +0 -180
- package/dist/plugins-cmd.cjs +0 -1113
- package/dist/plugins-cmd.d.cts +0 -178
- package/dist/plugins-cmd.d.mts +0 -178
- package/dist/plugins-demo.cjs +0 -307
- package/dist/plugins-demo.d.cts +0 -158
- package/dist/plugins-demo.d.mts +0 -158
- package/dist/plugins-init.cjs +0 -289
- package/dist/plugins-init.d.cts +0 -162
- package/dist/plugins-init.d.mts +0 -162
- package/dist/plugins.cjs +0 -2283
- package/dist/plugins.d.cts +0 -210
- package/dist/plugins.d.mts +0 -210
package/dist/plugins-batch.mjs
CHANGED
|
@@ -1,9 +1,92 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import 'fs-extra';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import { packageDirectory } from 'package-directory';
|
|
6
|
+
import 'url';
|
|
7
|
+
import 'yaml';
|
|
8
|
+
import 'nanoid';
|
|
9
|
+
import 'dotenv';
|
|
10
|
+
import 'crypto';
|
|
11
|
+
import { globby } from 'globby';
|
|
5
12
|
import { execa, execaCommand } from 'execa';
|
|
6
|
-
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Zod schemas for configuration files discovered by the new loader.
|
|
16
|
+
*
|
|
17
|
+
* Notes:
|
|
18
|
+
* - RAW: all fields optional; shapes are stringly-friendly (paths may be string[] or string).
|
|
19
|
+
* - RESOLVED: normalized shapes (paths always string[]).
|
|
20
|
+
* - For JSON/YAML configs, the loader rejects "dynamic" and "schema" (JS/TS-only).
|
|
21
|
+
*/
|
|
22
|
+
// String-only env value map
|
|
23
|
+
const stringMap = z.record(z.string(), z.string());
|
|
24
|
+
const envStringMap = z.record(z.string(), stringMap);
|
|
25
|
+
// Allow string[] or single string for "paths" in RAW; normalize later.
|
|
26
|
+
const rawPathsSchema = z.union([z.array(z.string()), z.string()]).optional();
|
|
27
|
+
const getDotenvConfigSchemaRaw = z.object({
|
|
28
|
+
dotenvToken: z.string().optional(),
|
|
29
|
+
privateToken: z.string().optional(),
|
|
30
|
+
paths: rawPathsSchema,
|
|
31
|
+
loadProcess: z.boolean().optional(),
|
|
32
|
+
log: z.boolean().optional(),
|
|
33
|
+
shell: z.union([z.string(), z.boolean()]).optional(),
|
|
34
|
+
scripts: z.record(z.string(), z.unknown()).optional(), // Scripts validation left wide; generator validates elsewhere
|
|
35
|
+
requiredKeys: z.array(z.string()).optional(),
|
|
36
|
+
schema: z.unknown().optional(), // JS/TS-only; loader rejects in JSON/YAML
|
|
37
|
+
vars: stringMap.optional(), // public, global
|
|
38
|
+
envVars: envStringMap.optional(), // public, per-env
|
|
39
|
+
// Dynamic in config (JS/TS only). JSON/YAML loader will reject if set.
|
|
40
|
+
dynamic: z.unknown().optional(),
|
|
41
|
+
// Per-plugin config bag; validated by plugins/host when used.
|
|
42
|
+
plugins: z.record(z.string(), z.unknown()).optional(),
|
|
43
|
+
});
|
|
44
|
+
// Normalize paths to string[]
|
|
45
|
+
const normalizePaths = (p) => p === undefined ? undefined : Array.isArray(p) ? p : [p];
|
|
46
|
+
getDotenvConfigSchemaRaw.transform((raw) => ({
|
|
47
|
+
...raw,
|
|
48
|
+
paths: normalizePaths(raw.paths),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Zod schemas for programmatic GetDotenv options.
|
|
53
|
+
*
|
|
54
|
+
* Canonical source of truth for options shape. Public types are derived
|
|
55
|
+
* from these schemas (see consumers via z.output\<\>).
|
|
56
|
+
*/
|
|
57
|
+
// Minimal process env representation: string values or undefined to indicate "unset".
|
|
58
|
+
const processEnvSchema = z.record(z.string(), z.string().optional());
|
|
59
|
+
// RAW: all fields optional — undefined means "inherit" from lower layers.
|
|
60
|
+
z.object({
|
|
61
|
+
defaultEnv: z.string().optional(),
|
|
62
|
+
dotenvToken: z.string().optional(),
|
|
63
|
+
dynamicPath: z.string().optional(),
|
|
64
|
+
// Dynamic map is intentionally wide for now; refine once sources are normalized.
|
|
65
|
+
dynamic: z.record(z.string(), z.unknown()).optional(),
|
|
66
|
+
env: z.string().optional(),
|
|
67
|
+
excludeDynamic: z.boolean().optional(),
|
|
68
|
+
excludeEnv: z.boolean().optional(),
|
|
69
|
+
excludeGlobal: z.boolean().optional(),
|
|
70
|
+
excludePrivate: z.boolean().optional(),
|
|
71
|
+
excludePublic: z.boolean().optional(),
|
|
72
|
+
loadProcess: z.boolean().optional(),
|
|
73
|
+
log: z.boolean().optional(),
|
|
74
|
+
logger: z.unknown().optional(),
|
|
75
|
+
outputPath: z.string().optional(),
|
|
76
|
+
paths: z.array(z.string()).optional(),
|
|
77
|
+
privateToken: z.string().optional(),
|
|
78
|
+
vars: processEnvSchema.optional(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Instance-bound plugin config store.
|
|
83
|
+
* Host stores the validated/interpolated slice per plugin instance.
|
|
84
|
+
* The store is intentionally private to this module; definePlugin()
|
|
85
|
+
* provides a typed accessor that reads from this store for the calling
|
|
86
|
+
* plugin instance.
|
|
87
|
+
*/
|
|
88
|
+
const PLUGIN_CONFIG_STORE = new WeakMap();
|
|
89
|
+
const _getPluginConfigForInstance = (plugin) => PLUGIN_CONFIG_STORE.get(plugin);
|
|
7
90
|
|
|
8
91
|
/** src/cliHost/definePlugin.ts
|
|
9
92
|
* Plugin contracts for the GetDotenv CLI host.
|
|
@@ -12,30 +95,66 @@ import { z } from 'zod';
|
|
|
12
95
|
* should use (GetDotenvCliPublic). Using a structural type at the seam avoids
|
|
13
96
|
* nominal class identity issues (private fields) in downstream consumers.
|
|
14
97
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* const parent = definePlugin(\{ id: 'p', setup(cli) \{ /* ... *\/ \} \})
|
|
20
|
-
* .use(childA)
|
|
21
|
-
* .use(childB);
|
|
22
|
-
*/
|
|
23
|
-
const definePlugin = (spec) => {
|
|
98
|
+
/* eslint-disable tsdoc/syntax */
|
|
99
|
+
function definePlugin(spec) {
|
|
24
100
|
const { children = [], ...rest } = spec;
|
|
25
|
-
|
|
101
|
+
// Default to a strict empty-object schema so “no-config” plugins fail fast
|
|
102
|
+
// on unknown keys and provide a concrete {} at runtime.
|
|
103
|
+
const effectiveSchema = spec.configSchema ?? z.object({}).strict();
|
|
104
|
+
// Build base plugin first, then extend with instance-bound helpers.
|
|
105
|
+
const base = {
|
|
26
106
|
...rest,
|
|
107
|
+
// Always carry a schema (strict empty by default) to simplify host logic
|
|
108
|
+
// and improve inference/ergonomics for plugin authors.
|
|
109
|
+
configSchema: effectiveSchema,
|
|
27
110
|
children: [...children],
|
|
28
111
|
use(child) {
|
|
29
112
|
this.children.push(child);
|
|
30
113
|
return this;
|
|
31
114
|
},
|
|
32
115
|
};
|
|
33
|
-
|
|
34
|
-
|
|
116
|
+
// Attach instance-bound helpers on the returned plugin object.
|
|
117
|
+
const extended = base;
|
|
118
|
+
extended.readConfig = function (_cli) {
|
|
119
|
+
// Config is stored per-plugin-instance by the host (WeakMap in computeContext).
|
|
120
|
+
const value = _getPluginConfigForInstance(extended);
|
|
121
|
+
if (value === undefined) {
|
|
122
|
+
// Guard: host has not resolved config yet (incorrect lifecycle usage).
|
|
123
|
+
throw new Error('Plugin config not available. Ensure resolveAndLoad() has been called before readConfig().');
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
};
|
|
127
|
+
// Plugin-bound dynamic option factory
|
|
128
|
+
extended.createPluginDynamicOption = function (cli, flags, desc, parser, defaultValue) {
|
|
129
|
+
return cli.createDynamicOption(flags, (cfg) => {
|
|
130
|
+
// Prefer the validated slice stored per instance; fallback to help-bag
|
|
131
|
+
// (by-id) so top-level `-h` can render effective defaults before resolve.
|
|
132
|
+
const fromStore = _getPluginConfigForInstance(extended);
|
|
133
|
+
const id = extended.id;
|
|
134
|
+
let fromBag;
|
|
135
|
+
if (!fromStore && id) {
|
|
136
|
+
const maybe = cfg.plugins[id];
|
|
137
|
+
if (maybe && typeof maybe === 'object') {
|
|
138
|
+
fromBag = maybe;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Always provide a concrete object to dynamic callbacks:
|
|
142
|
+
// - With a schema: computeContext stores the parsed object.
|
|
143
|
+
// - Without a schema: computeContext stores {}.
|
|
144
|
+
// - Help-time fallback: coalesce to {} when only a by-id bag exists.
|
|
145
|
+
const cfgVal = (fromStore ?? fromBag ?? {});
|
|
146
|
+
return desc(cfg, cfgVal);
|
|
147
|
+
}, parser, defaultValue);
|
|
148
|
+
};
|
|
149
|
+
return extended;
|
|
150
|
+
}
|
|
35
151
|
|
|
36
152
|
// Minimal tokenizer for shell-off execution:
|
|
37
153
|
// Splits by whitespace while preserving quoted segments (single or double quotes).
|
|
38
|
-
|
|
154
|
+
// Optionally preserve doubled quotes inside quoted segments:
|
|
155
|
+
// - default: "" => " (Windows/PowerShell style literal-quote escape)
|
|
156
|
+
// - preserveDoubledQuotes: true => "" stays "" (needed for Node -e payloads)
|
|
157
|
+
const tokenize = (command, opts) => {
|
|
39
158
|
const out = [];
|
|
40
159
|
let cur = '';
|
|
41
160
|
let quote = null;
|
|
@@ -43,12 +162,16 @@ const tokenize = (command) => {
|
|
|
43
162
|
const c = command.charAt(i);
|
|
44
163
|
if (quote) {
|
|
45
164
|
if (c === quote) {
|
|
46
|
-
// Support doubled quotes inside a quoted segment
|
|
47
|
-
// "" -> " and '' -> '
|
|
165
|
+
// Support doubled quotes inside a quoted segment:
|
|
166
|
+
// default: "" -> " and '' -> ' (Windows/PowerShell style)
|
|
167
|
+
// preserve: keep as "" to allow empty string literals in Node -e payloads
|
|
48
168
|
const next = command.charAt(i + 1);
|
|
49
169
|
if (next === quote) {
|
|
50
|
-
|
|
51
|
-
|
|
170
|
+
{
|
|
171
|
+
// Collapse to a single literal quote
|
|
172
|
+
cur += quote;
|
|
173
|
+
i += 1; // skip the second quote
|
|
174
|
+
}
|
|
52
175
|
}
|
|
53
176
|
else {
|
|
54
177
|
// end of quoted segment
|
|
@@ -103,6 +226,17 @@ const stripOuterQuotes = (s) => {
|
|
|
103
226
|
}
|
|
104
227
|
return out;
|
|
105
228
|
};
|
|
229
|
+
// Extract exitCode/stdout/stderr from execa result or error in a tolerant way.
|
|
230
|
+
const pickResult = (r) => {
|
|
231
|
+
const exit = r.exitCode;
|
|
232
|
+
const stdoutVal = r.stdout;
|
|
233
|
+
const stderrVal = r.stderr;
|
|
234
|
+
return {
|
|
235
|
+
exitCode: typeof exit === 'number' ? exit : Number.NaN,
|
|
236
|
+
stdout: typeof stdoutVal === 'string' ? stdoutVal : '',
|
|
237
|
+
stderr: typeof stderrVal === 'string' ? stderrVal : '',
|
|
238
|
+
};
|
|
239
|
+
};
|
|
106
240
|
// Convert NodeJS.ProcessEnv (string | undefined values) to the shape execa
|
|
107
241
|
// expects (Readonly<Partial<Record<string, string>>>), dropping undefineds.
|
|
108
242
|
const sanitizeEnv = (env) => {
|
|
@@ -111,19 +245,19 @@ const sanitizeEnv = (env) => {
|
|
|
111
245
|
const entries = Object.entries(env).filter((e) => typeof e[1] === 'string');
|
|
112
246
|
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
113
247
|
};
|
|
114
|
-
|
|
248
|
+
async function runCommand(command, shell, opts) {
|
|
115
249
|
if (shell === false) {
|
|
116
250
|
let file;
|
|
117
251
|
let args = [];
|
|
118
|
-
if (
|
|
119
|
-
file = command[0];
|
|
120
|
-
args = command.slice(1).map(stripOuterQuotes);
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
252
|
+
if (typeof command === 'string') {
|
|
123
253
|
const tokens = tokenize(command);
|
|
124
254
|
file = tokens[0];
|
|
125
255
|
args = tokens.slice(1);
|
|
126
256
|
}
|
|
257
|
+
else {
|
|
258
|
+
file = command[0];
|
|
259
|
+
args = command.slice(1).map(stripOuterQuotes);
|
|
260
|
+
}
|
|
127
261
|
if (!file)
|
|
128
262
|
return 0;
|
|
129
263
|
dbg('exec (plain)', { file, args, stdio: opts.stdio });
|
|
@@ -136,16 +270,15 @@ const runCommand = async (command, shell, opts) => {
|
|
|
136
270
|
plainOpts.env = envSan;
|
|
137
271
|
if (opts.stdio !== undefined)
|
|
138
272
|
plainOpts.stdio = opts.stdio;
|
|
139
|
-
const
|
|
140
|
-
if (opts.stdio === 'pipe' &&
|
|
141
|
-
process.stdout.write(
|
|
273
|
+
const ok = pickResult((await execa(file, args, plainOpts)));
|
|
274
|
+
if (opts.stdio === 'pipe' && ok.stdout) {
|
|
275
|
+
process.stdout.write(ok.stdout + (ok.stdout.endsWith('\n') ? '' : '\n'));
|
|
142
276
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return typeof exit === 'number' ? exit : Number.NaN;
|
|
277
|
+
dbg('exit (plain)', { exitCode: ok.exitCode });
|
|
278
|
+
return typeof ok.exitCode === 'number' ? ok.exitCode : Number.NaN;
|
|
146
279
|
}
|
|
147
280
|
else {
|
|
148
|
-
const commandStr =
|
|
281
|
+
const commandStr = typeof command === 'string' ? command : command.join(' ');
|
|
149
282
|
dbg('exec (shell)', {
|
|
150
283
|
shell: typeof shell === 'string' ? shell : 'custom',
|
|
151
284
|
stdio: opts.stdio,
|
|
@@ -159,17 +292,29 @@ const runCommand = async (command, shell, opts) => {
|
|
|
159
292
|
shellOpts.env = envSan;
|
|
160
293
|
if (opts.stdio !== undefined)
|
|
161
294
|
shellOpts.stdio = opts.stdio;
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
process.stdout.write(out + (out.endsWith('\n') ? '' : '\n'));
|
|
295
|
+
const ok = pickResult((await execaCommand(commandStr, shellOpts)));
|
|
296
|
+
if (opts.stdio === 'pipe' && ok.stdout) {
|
|
297
|
+
process.stdout.write(ok.stdout + (ok.stdout.endsWith('\n') ? '' : '\n'));
|
|
166
298
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return typeof exit === 'number' ? exit : Number.NaN;
|
|
299
|
+
dbg('exit (shell)', { exitCode: ok.exitCode });
|
|
300
|
+
return typeof ok.exitCode === 'number' ? ok.exitCode : Number.NaN;
|
|
170
301
|
}
|
|
171
|
-
}
|
|
302
|
+
}
|
|
172
303
|
|
|
304
|
+
/** src/cliCore/spawnEnv.ts
|
|
305
|
+
* Build a sanitized environment bag for child processes.
|
|
306
|
+
*
|
|
307
|
+
* Requirements addressed:
|
|
308
|
+
* - Provide a single helper (buildSpawnEnv) to normalize/dedupe child env.
|
|
309
|
+
* - Drop undefined values (exactOptional semantics).
|
|
310
|
+
* - On Windows, dedupe keys case-insensitively and prefer the last value,
|
|
311
|
+
* preserving the latest key's casing. Ensure HOME fallback from USERPROFILE.
|
|
312
|
+
* Normalize TMP/TEMP consistency when either is present.
|
|
313
|
+
* - On POSIX, keep keys as-is; when a temp dir key is present (TMPDIR/TMP/TEMP),
|
|
314
|
+
* ensure TMPDIR exists for downstream consumers that expect it.
|
|
315
|
+
*
|
|
316
|
+
* Adapter responsibility: pure mapping; no business logic.
|
|
317
|
+
*/
|
|
173
318
|
const dropUndefined = (bag) => Object.fromEntries(Object.entries(bag).filter((e) => typeof e[1] === 'string'));
|
|
174
319
|
/** Build a sanitized env for child processes from base + overlay. */
|
|
175
320
|
const buildSpawnEnv = (base, overlay) => {
|
|
@@ -235,9 +380,10 @@ const globPaths = async ({ globs, logger, pkgCwd, rootPath, }) => {
|
|
|
235
380
|
}
|
|
236
381
|
return { absRootPath, paths };
|
|
237
382
|
};
|
|
238
|
-
const execShellCommandBatch = async ({ command, getDotenvCliOptions, globs, ignoreErrors, list, logger, pkgCwd, rootPath, shell, }) => {
|
|
383
|
+
const execShellCommandBatch = async ({ command, getDotenvCliOptions, dotenvEnv, globs, ignoreErrors, list, logger, pkgCwd, rootPath, shell, }) => {
|
|
239
384
|
const capture = process.env.GETDOTENV_STDIO === 'pipe' ||
|
|
240
|
-
Boolean(getDotenvCliOptions?.capture);
|
|
385
|
+
Boolean(getDotenvCliOptions?.capture);
|
|
386
|
+
// Require a command only when not listing. In list mode, a command is optional.
|
|
241
387
|
if (!command && !list) {
|
|
242
388
|
logger.error(`No command provided. Use --command or --list.`);
|
|
243
389
|
process.exit(0);
|
|
@@ -284,12 +430,25 @@ const execShellCommandBatch = async ({ command, getDotenvCliOptions, globs, igno
|
|
|
284
430
|
const hasCmd = (typeof command === 'string' && command.length > 0) ||
|
|
285
431
|
(Array.isArray(command) && command.length > 0);
|
|
286
432
|
if (hasCmd) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
433
|
+
// Compose child env overlay from dotenv (drop undefined) and merged options
|
|
434
|
+
const overlay = {};
|
|
435
|
+
if (dotenvEnv) {
|
|
436
|
+
for (const [k, v] of Object.entries(dotenvEnv)) {
|
|
437
|
+
if (typeof v === 'string')
|
|
438
|
+
overlay[k] = v;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (getDotenvCliOptions !== undefined) {
|
|
442
|
+
try {
|
|
443
|
+
overlay.getDotenvCliOptions = JSON.stringify(getDotenvCliOptions);
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
// best-effort: omit if serialization fails
|
|
447
|
+
}
|
|
448
|
+
}
|
|
290
449
|
await runCommand(command, shell, {
|
|
291
450
|
cwd: path,
|
|
292
|
-
env: buildSpawnEnv(process.env,
|
|
451
|
+
env: buildSpawnEnv(process.env, overlay),
|
|
293
452
|
stdio: capture ? 'pipe' : 'inherit',
|
|
294
453
|
});
|
|
295
454
|
}
|
|
@@ -340,7 +499,7 @@ const resolveShell = (scripts, command, shell) => scripts && typeof scripts[comm
|
|
|
340
499
|
* Build the default "cmd" subcommand action for the batch plugin.
|
|
341
500
|
* Mirrors the original inline implementation with identical behavior.
|
|
342
501
|
*/
|
|
343
|
-
const buildDefaultCmdAction = (cli, batchCmd, opts) => async (commandParts, _subOpts, _thisCommand) => {
|
|
502
|
+
const buildDefaultCmdAction = (plugin, cli, batchCmd, opts) => async (commandParts, _subOpts, _thisCommand) => {
|
|
344
503
|
const loggerLocal = opts.logger ?? console;
|
|
345
504
|
// Guard: when invoked without positional args (e.g., `batch --list`),
|
|
346
505
|
// defer entirely to the parent action handler.
|
|
@@ -352,9 +511,8 @@ const buildDefaultCmdAction = (cli, batchCmd, opts) => async (commandParts, _sub
|
|
|
352
511
|
? argsRaw.filter((t) => t !== '-l' && t !== '--list')
|
|
353
512
|
: argsRaw;
|
|
354
513
|
// Access merged per-plugin config from host context (if any).
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
const cfg = (cfgRaw || {});
|
|
514
|
+
const cfg = plugin.readConfig(cli);
|
|
515
|
+
const dotenvEnv = (cli.getCtx()?.dotenv ?? {});
|
|
358
516
|
// Resolve batch flags from the captured parent (batch) command.
|
|
359
517
|
const raw = batchCmd.opts();
|
|
360
518
|
const listFromParent = !!raw.list;
|
|
@@ -373,6 +531,7 @@ const buildDefaultCmdAction = (cli, batchCmd, opts) => async (commandParts, _sub
|
|
|
373
531
|
if (typeof commandOpt === 'string') {
|
|
374
532
|
await execShellCommandBatch({
|
|
375
533
|
command: resolveCommand(scripts, commandOpt),
|
|
534
|
+
dotenvEnv,
|
|
376
535
|
globs,
|
|
377
536
|
ignoreErrors,
|
|
378
537
|
list: false,
|
|
@@ -384,6 +543,7 @@ const buildDefaultCmdAction = (cli, batchCmd, opts) => async (commandParts, _sub
|
|
|
384
543
|
return;
|
|
385
544
|
}
|
|
386
545
|
if (raw.list || localList) {
|
|
546
|
+
const shellBag = ((batchCmd.parent ?? undefined)?.getDotenvCliOptions ?? {});
|
|
387
547
|
await execShellCommandBatch({
|
|
388
548
|
globs,
|
|
389
549
|
ignoreErrors,
|
|
@@ -391,7 +551,7 @@ const buildDefaultCmdAction = (cli, batchCmd, opts) => async (commandParts, _sub
|
|
|
391
551
|
logger: loggerLocal,
|
|
392
552
|
...(pkgCwd ? { pkgCwd } : {}),
|
|
393
553
|
rootPath,
|
|
394
|
-
shell:
|
|
554
|
+
shell: shell ?? shellBag.shell ?? false,
|
|
395
555
|
});
|
|
396
556
|
return;
|
|
397
557
|
}
|
|
@@ -415,7 +575,7 @@ const buildDefaultCmdAction = (cli, batchCmd, opts) => async (commandParts, _sub
|
|
|
415
575
|
logger: loggerLocal,
|
|
416
576
|
...(pkgCwd ? { pkgCwd } : {}),
|
|
417
577
|
rootPath,
|
|
418
|
-
shell:
|
|
578
|
+
shell: shell ?? shellBag.shell ?? false,
|
|
419
579
|
});
|
|
420
580
|
return;
|
|
421
581
|
}
|
|
@@ -458,6 +618,7 @@ const buildDefaultCmdAction = (cli, batchCmd, opts) => async (commandParts, _sub
|
|
|
458
618
|
}
|
|
459
619
|
await execShellCommandBatch({
|
|
460
620
|
command: commandArg,
|
|
621
|
+
dotenvEnv,
|
|
461
622
|
...(envBag ? { getDotenvCliOptions: envBag } : {}),
|
|
462
623
|
globs,
|
|
463
624
|
ignoreErrors,
|
|
@@ -472,12 +633,11 @@ const buildDefaultCmdAction = (cli, batchCmd, opts) => async (commandParts, _sub
|
|
|
472
633
|
/**
|
|
473
634
|
* Build the parent "batch" action handler (no explicit subcommand).
|
|
474
635
|
*/
|
|
475
|
-
const buildParentAction = (cli, opts) => async (commandParts, thisCommand) => {
|
|
476
|
-
const
|
|
636
|
+
const buildParentAction = (plugin, cli, opts) => async (commandParts, thisCommand) => {
|
|
637
|
+
const loggerLocal = opts.logger ?? console;
|
|
477
638
|
// Ensure context exists (host preSubcommand on root creates if missing).
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
const cfg = (cfgRaw || {});
|
|
639
|
+
const dotenvEnv = (cli.getCtx()?.dotenv ?? {});
|
|
640
|
+
const cfg = plugin.readConfig(cli);
|
|
481
641
|
const raw = thisCommand.opts();
|
|
482
642
|
const commandOpt = typeof raw.command === 'string' ? raw.command : undefined;
|
|
483
643
|
const ignoreErrors = !!raw.ignoreErrors;
|
|
@@ -498,10 +658,11 @@ const buildParentAction = (cli, opts) => async (commandParts, thisCommand) => {
|
|
|
498
658
|
const commandArg = resolved;
|
|
499
659
|
await execShellCommandBatch({
|
|
500
660
|
command: commandArg,
|
|
661
|
+
dotenvEnv,
|
|
501
662
|
globs,
|
|
502
663
|
ignoreErrors,
|
|
503
664
|
list: false,
|
|
504
|
-
logger,
|
|
665
|
+
logger: loggerLocal,
|
|
505
666
|
...(pkgCwd ? { pkgCwd } : {}),
|
|
506
667
|
rootPath,
|
|
507
668
|
shell: shellSetting,
|
|
@@ -514,19 +675,20 @@ const buildParentAction = (cli, opts) => async (commandParts, thisCommand) => {
|
|
|
514
675
|
if (extra.length > 0)
|
|
515
676
|
globs = [globs, extra].filter(Boolean).join(' ');
|
|
516
677
|
const mergedBag = ((thisCommand.parent ?? null)?.getDotenvCliOptions ?? {});
|
|
678
|
+
const shellMerged = opts.shell ?? cfg.shell ?? mergedBag.shell ?? false;
|
|
517
679
|
await execShellCommandBatch({
|
|
518
680
|
globs,
|
|
519
681
|
ignoreErrors,
|
|
520
682
|
list: true,
|
|
521
|
-
logger,
|
|
683
|
+
logger: loggerLocal,
|
|
522
684
|
...(pkgCwd ? { pkgCwd } : {}),
|
|
523
685
|
rootPath,
|
|
524
|
-
shell:
|
|
686
|
+
shell: shellMerged,
|
|
525
687
|
});
|
|
526
688
|
return;
|
|
527
689
|
}
|
|
528
690
|
if (!commandOpt && !list) {
|
|
529
|
-
|
|
691
|
+
loggerLocal.error(`No command provided. Use --command or --list.`);
|
|
530
692
|
process.exit(0);
|
|
531
693
|
}
|
|
532
694
|
if (typeof commandOpt === 'string') {
|
|
@@ -535,10 +697,11 @@ const buildParentAction = (cli, opts) => async (commandParts, thisCommand) => {
|
|
|
535
697
|
const shellOpt = opts.shell ?? cfg.shell ?? mergedBag.shell;
|
|
536
698
|
await execShellCommandBatch({
|
|
537
699
|
command: resolveCommand(scriptsOpt, commandOpt),
|
|
700
|
+
dotenvEnv,
|
|
538
701
|
globs,
|
|
539
702
|
ignoreErrors,
|
|
540
703
|
list,
|
|
541
|
-
logger,
|
|
704
|
+
logger: loggerLocal,
|
|
542
705
|
...(pkgCwd ? { pkgCwd } : {}),
|
|
543
706
|
rootPath,
|
|
544
707
|
shell: resolveShell(scriptsOpt, commandOpt, shellOpt),
|
|
@@ -547,15 +710,15 @@ const buildParentAction = (cli, opts) => async (commandParts, thisCommand) => {
|
|
|
547
710
|
}
|
|
548
711
|
// list only (explicit --list without --command)
|
|
549
712
|
const mergedBag = ((thisCommand.parent ?? null)?.getDotenvCliOptions ?? {});
|
|
550
|
-
const shellOnly =
|
|
713
|
+
const shellOnly = opts.shell ?? cfg.shell ?? mergedBag.shell ?? false;
|
|
551
714
|
await execShellCommandBatch({
|
|
552
715
|
globs,
|
|
553
716
|
ignoreErrors,
|
|
554
717
|
list: true,
|
|
555
|
-
logger,
|
|
718
|
+
logger: loggerLocal,
|
|
556
719
|
...(pkgCwd ? { pkgCwd } : {}),
|
|
557
720
|
rootPath,
|
|
558
|
-
shell:
|
|
721
|
+
shell: shellOnly,
|
|
559
722
|
});
|
|
560
723
|
};
|
|
561
724
|
|
|
@@ -578,37 +741,56 @@ const BatchConfigSchema = z.object({
|
|
|
578
741
|
/**
|
|
579
742
|
* Batch plugin for the GetDotenv CLI host.
|
|
580
743
|
*
|
|
581
|
-
* Mirrors the legacy batch subcommand behavior without altering the shipped CLI.
|
|
744
|
+
* Mirrors the legacy batch subcommand behavior without altering the shipped CLI.
|
|
745
|
+
* Options:
|
|
582
746
|
* - scripts/shell: used to resolve command and shell behavior per script or global default.
|
|
583
747
|
* - logger: defaults to console.
|
|
584
748
|
*/
|
|
585
|
-
const batchPlugin = (opts = {}) =>
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
.
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
749
|
+
const batchPlugin = (opts = {}) => {
|
|
750
|
+
const plugin = definePlugin({
|
|
751
|
+
id: 'batch',
|
|
752
|
+
// Host validates this when config-loader is enabled; plugins may also
|
|
753
|
+
// re-validate at action time as a safety belt.
|
|
754
|
+
configSchema: BatchConfigSchema,
|
|
755
|
+
setup(cli) {
|
|
756
|
+
const ns = cli.ns('batch');
|
|
757
|
+
const batchCmd = ns; // capture the parent "batch" command for default-subcommand context
|
|
758
|
+
const pluginId = 'batch';
|
|
759
|
+
const GROUP = `plugin:${pluginId}`;
|
|
760
|
+
ns.description('Batch command execution across multiple working directories.')
|
|
761
|
+
.enablePositionalOptions()
|
|
762
|
+
.passThroughOptions()
|
|
763
|
+
// Dynamic help: show effective defaults from the merged/interpolated plugin config slice.
|
|
764
|
+
.addOption((() => {
|
|
765
|
+
const opt = plugin.createPluginDynamicOption(cli, '-p, --pkg-cwd', (_bag, cfg) => `use nearest package directory as current working directory${cfg.pkgCwd ? ' (default)' : ''}`);
|
|
766
|
+
cli.setOptionGroup(opt, GROUP);
|
|
767
|
+
return opt;
|
|
768
|
+
})())
|
|
769
|
+
.addOption((() => {
|
|
770
|
+
const opt = plugin.createPluginDynamicOption(cli, '-r, --root-path <string>', (_bag, cfg) => `path to batch root directory from current working directory (default: ${JSON.stringify(cfg.rootPath || './')})`);
|
|
771
|
+
cli.setOptionGroup(opt, GROUP);
|
|
772
|
+
return opt;
|
|
773
|
+
})())
|
|
774
|
+
.addOption((() => {
|
|
775
|
+
const opt = plugin.createPluginDynamicOption(cli, '-g, --globs <string>', (_bag, cfg) => `space-delimited globs from root path (default: ${JSON.stringify(cfg.globs || '*')})`);
|
|
776
|
+
cli.setOptionGroup(opt, GROUP);
|
|
777
|
+
return opt;
|
|
778
|
+
})())
|
|
779
|
+
.option('-c, --command <string>', 'command executed according to the base shell resolution')
|
|
780
|
+
.option('-l, --list', 'list working directories without executing command')
|
|
781
|
+
.option('-e, --ignore-errors', 'ignore errors and continue with next path')
|
|
782
|
+
.argument('[command...]')
|
|
783
|
+
.addCommand(new Command()
|
|
784
|
+
.name('cmd')
|
|
785
|
+
.description('execute command, conflicts with --command option (default subcommand)')
|
|
786
|
+
.enablePositionalOptions()
|
|
787
|
+
.passThroughOptions()
|
|
788
|
+
.argument('[command...]')
|
|
789
|
+
.action(buildDefaultCmdAction(plugin, cli, batchCmd, opts)), { isDefault: true })
|
|
790
|
+
.action(buildParentAction(plugin, cli, opts));
|
|
791
|
+
},
|
|
792
|
+
});
|
|
793
|
+
return plugin;
|
|
794
|
+
};
|
|
613
795
|
|
|
614
796
|
export { batchPlugin };
|