@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-aws.mjs
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { execa, execaCommand } from 'execa';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import 'fs-extra';
|
|
4
|
+
import 'path';
|
|
5
|
+
import 'package-directory';
|
|
6
|
+
import 'url';
|
|
7
|
+
import 'yaml';
|
|
8
|
+
import 'nanoid';
|
|
9
|
+
import 'dotenv';
|
|
10
|
+
import 'crypto';
|
|
3
11
|
|
|
4
12
|
// Minimal tokenizer for shell-off execution:
|
|
5
13
|
// Splits by whitespace while preserving quoted segments (single or double quotes).
|
|
6
|
-
|
|
14
|
+
// Optionally preserve doubled quotes inside quoted segments:
|
|
15
|
+
// - default: "" => " (Windows/PowerShell style literal-quote escape)
|
|
16
|
+
// - preserveDoubledQuotes: true => "" stays "" (needed for Node -e payloads)
|
|
17
|
+
const tokenize = (command, opts) => {
|
|
7
18
|
const out = [];
|
|
8
19
|
let cur = '';
|
|
9
20
|
let quote = null;
|
|
@@ -11,12 +22,16 @@ const tokenize = (command) => {
|
|
|
11
22
|
const c = command.charAt(i);
|
|
12
23
|
if (quote) {
|
|
13
24
|
if (c === quote) {
|
|
14
|
-
// Support doubled quotes inside a quoted segment
|
|
15
|
-
// "" -> " and '' -> '
|
|
25
|
+
// Support doubled quotes inside a quoted segment:
|
|
26
|
+
// default: "" -> " and '' -> ' (Windows/PowerShell style)
|
|
27
|
+
// preserve: keep as "" to allow empty string literals in Node -e payloads
|
|
16
28
|
const next = command.charAt(i + 1);
|
|
17
29
|
if (next === quote) {
|
|
18
|
-
|
|
19
|
-
|
|
30
|
+
{
|
|
31
|
+
// Collapse to a single literal quote
|
|
32
|
+
cur += quote;
|
|
33
|
+
i += 1; // skip the second quote
|
|
34
|
+
}
|
|
20
35
|
}
|
|
21
36
|
else {
|
|
22
37
|
// end of quoted segment
|
|
@@ -90,62 +105,55 @@ const sanitizeEnv = (env) => {
|
|
|
90
105
|
const entries = Object.entries(env).filter((e) => typeof e[1] === 'string');
|
|
91
106
|
return entries.length > 0 ? Object.fromEntries(entries) : undefined;
|
|
92
107
|
};
|
|
93
|
-
|
|
94
|
-
* Execute a command and capture stdout/stderr (buffered).
|
|
95
|
-
* - Preserves plain vs shell behavior and argv/string normalization.
|
|
96
|
-
* - Never re-emits stdout/stderr to parent; returns captured buffers.
|
|
97
|
-
* - Supports optional timeout (ms).
|
|
98
|
-
*/
|
|
99
|
-
const runCommandResult = async (command, shell, opts = {}) => {
|
|
108
|
+
async function runCommandResult(command, shell, opts = {}) {
|
|
100
109
|
const envSan = sanitizeEnv(opts.env);
|
|
101
110
|
{
|
|
102
111
|
let file;
|
|
103
112
|
let args = [];
|
|
104
|
-
if (
|
|
105
|
-
file = command[0];
|
|
106
|
-
args = command.slice(1).map(stripOuterQuotes);
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
113
|
+
if (typeof command === 'string') {
|
|
109
114
|
const tokens = tokenize(command);
|
|
110
115
|
file = tokens[0];
|
|
111
116
|
args = tokens.slice(1);
|
|
112
117
|
}
|
|
118
|
+
else {
|
|
119
|
+
file = command[0];
|
|
120
|
+
args = command.slice(1).map(stripOuterQuotes);
|
|
121
|
+
}
|
|
113
122
|
if (!file)
|
|
114
123
|
return { exitCode: 0, stdout: '', stderr: '' };
|
|
115
124
|
dbg('exec:capture (plain)', { file, args });
|
|
116
125
|
try {
|
|
117
|
-
const
|
|
126
|
+
const ok = pickResult((await execa(file, args, {
|
|
118
127
|
...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
|
|
119
128
|
...(envSan !== undefined ? { env: envSan } : {}),
|
|
120
129
|
stdio: 'pipe',
|
|
121
130
|
...(opts.timeoutMs !== undefined
|
|
122
131
|
? { timeout: opts.timeoutMs, killSignal: 'SIGKILL' }
|
|
123
132
|
: {}),
|
|
124
|
-
});
|
|
125
|
-
const ok = pickResult(result);
|
|
133
|
+
})));
|
|
126
134
|
dbg('exit:capture (plain)', { exitCode: ok.exitCode });
|
|
127
135
|
return ok;
|
|
128
136
|
}
|
|
129
|
-
catch (
|
|
130
|
-
const out = pickResult(
|
|
137
|
+
catch (e) {
|
|
138
|
+
const out = pickResult(e);
|
|
131
139
|
dbg('exit:capture:error (plain)', { exitCode: out.exitCode });
|
|
132
140
|
return out;
|
|
133
141
|
}
|
|
134
142
|
}
|
|
135
|
-
}
|
|
136
|
-
|
|
143
|
+
}
|
|
144
|
+
async function runCommand(command, shell, opts) {
|
|
137
145
|
if (shell === false) {
|
|
138
146
|
let file;
|
|
139
147
|
let args = [];
|
|
140
|
-
if (
|
|
141
|
-
file = command[0];
|
|
142
|
-
args = command.slice(1).map(stripOuterQuotes);
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
148
|
+
if (typeof command === 'string') {
|
|
145
149
|
const tokens = tokenize(command);
|
|
146
150
|
file = tokens[0];
|
|
147
151
|
args = tokens.slice(1);
|
|
148
152
|
}
|
|
153
|
+
else {
|
|
154
|
+
file = command[0];
|
|
155
|
+
args = command.slice(1).map(stripOuterQuotes);
|
|
156
|
+
}
|
|
149
157
|
if (!file)
|
|
150
158
|
return 0;
|
|
151
159
|
dbg('exec (plain)', { file, args, stdio: opts.stdio });
|
|
@@ -158,16 +166,15 @@ const runCommand = async (command, shell, opts) => {
|
|
|
158
166
|
plainOpts.env = envSan;
|
|
159
167
|
if (opts.stdio !== undefined)
|
|
160
168
|
plainOpts.stdio = opts.stdio;
|
|
161
|
-
const
|
|
162
|
-
if (opts.stdio === 'pipe' &&
|
|
163
|
-
process.stdout.write(
|
|
169
|
+
const ok = pickResult((await execa(file, args, plainOpts)));
|
|
170
|
+
if (opts.stdio === 'pipe' && ok.stdout) {
|
|
171
|
+
process.stdout.write(ok.stdout + (ok.stdout.endsWith('\n') ? '' : '\n'));
|
|
164
172
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return typeof exit === 'number' ? exit : Number.NaN;
|
|
173
|
+
dbg('exit (plain)', { exitCode: ok.exitCode });
|
|
174
|
+
return typeof ok.exitCode === 'number' ? ok.exitCode : Number.NaN;
|
|
168
175
|
}
|
|
169
176
|
else {
|
|
170
|
-
const commandStr =
|
|
177
|
+
const commandStr = typeof command === 'string' ? command : command.join(' ');
|
|
171
178
|
dbg('exec (shell)', {
|
|
172
179
|
shell: typeof shell === 'string' ? shell : 'custom',
|
|
173
180
|
stdio: opts.stdio,
|
|
@@ -181,17 +188,29 @@ const runCommand = async (command, shell, opts) => {
|
|
|
181
188
|
shellOpts.env = envSan;
|
|
182
189
|
if (opts.stdio !== undefined)
|
|
183
190
|
shellOpts.stdio = opts.stdio;
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
process.stdout.write(out + (out.endsWith('\n') ? '' : '\n'));
|
|
191
|
+
const ok = pickResult((await execaCommand(commandStr, shellOpts)));
|
|
192
|
+
if (opts.stdio === 'pipe' && ok.stdout) {
|
|
193
|
+
process.stdout.write(ok.stdout + (ok.stdout.endsWith('\n') ? '' : '\n'));
|
|
188
194
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return typeof exit === 'number' ? exit : Number.NaN;
|
|
195
|
+
dbg('exit (shell)', { exitCode: ok.exitCode });
|
|
196
|
+
return typeof ok.exitCode === 'number' ? ok.exitCode : Number.NaN;
|
|
192
197
|
}
|
|
193
|
-
}
|
|
198
|
+
}
|
|
194
199
|
|
|
200
|
+
/** src/cliCore/spawnEnv.ts
|
|
201
|
+
* Build a sanitized environment bag for child processes.
|
|
202
|
+
*
|
|
203
|
+
* Requirements addressed:
|
|
204
|
+
* - Provide a single helper (buildSpawnEnv) to normalize/dedupe child env.
|
|
205
|
+
* - Drop undefined values (exactOptional semantics).
|
|
206
|
+
* - On Windows, dedupe keys case-insensitively and prefer the last value,
|
|
207
|
+
* preserving the latest key's casing. Ensure HOME fallback from USERPROFILE.
|
|
208
|
+
* Normalize TMP/TEMP consistency when either is present.
|
|
209
|
+
* - On POSIX, keep keys as-is; when a temp dir key is present (TMPDIR/TMP/TEMP),
|
|
210
|
+
* ensure TMPDIR exists for downstream consumers that expect it.
|
|
211
|
+
*
|
|
212
|
+
* Adapter responsibility: pure mapping; no business logic.
|
|
213
|
+
*/
|
|
195
214
|
const dropUndefined = (bag) => Object.fromEntries(Object.entries(bag).filter((e) => typeof e[1] === 'string'));
|
|
196
215
|
/** Build a sanitized env for child processes from base + overlay. */
|
|
197
216
|
const buildSpawnEnv = (base, overlay) => {
|
|
@@ -234,6 +253,83 @@ const buildSpawnEnv = (base, overlay) => {
|
|
|
234
253
|
return out;
|
|
235
254
|
};
|
|
236
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Zod schemas for configuration files discovered by the new loader.
|
|
258
|
+
*
|
|
259
|
+
* Notes:
|
|
260
|
+
* - RAW: all fields optional; shapes are stringly-friendly (paths may be string[] or string).
|
|
261
|
+
* - RESOLVED: normalized shapes (paths always string[]).
|
|
262
|
+
* - For JSON/YAML configs, the loader rejects "dynamic" and "schema" (JS/TS-only).
|
|
263
|
+
*/
|
|
264
|
+
// String-only env value map
|
|
265
|
+
const stringMap = z.record(z.string(), z.string());
|
|
266
|
+
const envStringMap = z.record(z.string(), stringMap);
|
|
267
|
+
// Allow string[] or single string for "paths" in RAW; normalize later.
|
|
268
|
+
const rawPathsSchema = z.union([z.array(z.string()), z.string()]).optional();
|
|
269
|
+
const getDotenvConfigSchemaRaw = z.object({
|
|
270
|
+
dotenvToken: z.string().optional(),
|
|
271
|
+
privateToken: z.string().optional(),
|
|
272
|
+
paths: rawPathsSchema,
|
|
273
|
+
loadProcess: z.boolean().optional(),
|
|
274
|
+
log: z.boolean().optional(),
|
|
275
|
+
shell: z.union([z.string(), z.boolean()]).optional(),
|
|
276
|
+
scripts: z.record(z.string(), z.unknown()).optional(), // Scripts validation left wide; generator validates elsewhere
|
|
277
|
+
requiredKeys: z.array(z.string()).optional(),
|
|
278
|
+
schema: z.unknown().optional(), // JS/TS-only; loader rejects in JSON/YAML
|
|
279
|
+
vars: stringMap.optional(), // public, global
|
|
280
|
+
envVars: envStringMap.optional(), // public, per-env
|
|
281
|
+
// Dynamic in config (JS/TS only). JSON/YAML loader will reject if set.
|
|
282
|
+
dynamic: z.unknown().optional(),
|
|
283
|
+
// Per-plugin config bag; validated by plugins/host when used.
|
|
284
|
+
plugins: z.record(z.string(), z.unknown()).optional(),
|
|
285
|
+
});
|
|
286
|
+
// Normalize paths to string[]
|
|
287
|
+
const normalizePaths = (p) => p === undefined ? undefined : Array.isArray(p) ? p : [p];
|
|
288
|
+
getDotenvConfigSchemaRaw.transform((raw) => ({
|
|
289
|
+
...raw,
|
|
290
|
+
paths: normalizePaths(raw.paths),
|
|
291
|
+
}));
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Zod schemas for programmatic GetDotenv options.
|
|
295
|
+
*
|
|
296
|
+
* Canonical source of truth for options shape. Public types are derived
|
|
297
|
+
* from these schemas (see consumers via z.output\<\>).
|
|
298
|
+
*/
|
|
299
|
+
// Minimal process env representation: string values or undefined to indicate "unset".
|
|
300
|
+
const processEnvSchema = z.record(z.string(), z.string().optional());
|
|
301
|
+
// RAW: all fields optional — undefined means "inherit" from lower layers.
|
|
302
|
+
z.object({
|
|
303
|
+
defaultEnv: z.string().optional(),
|
|
304
|
+
dotenvToken: z.string().optional(),
|
|
305
|
+
dynamicPath: z.string().optional(),
|
|
306
|
+
// Dynamic map is intentionally wide for now; refine once sources are normalized.
|
|
307
|
+
dynamic: z.record(z.string(), z.unknown()).optional(),
|
|
308
|
+
env: z.string().optional(),
|
|
309
|
+
excludeDynamic: z.boolean().optional(),
|
|
310
|
+
excludeEnv: z.boolean().optional(),
|
|
311
|
+
excludeGlobal: z.boolean().optional(),
|
|
312
|
+
excludePrivate: z.boolean().optional(),
|
|
313
|
+
excludePublic: z.boolean().optional(),
|
|
314
|
+
loadProcess: z.boolean().optional(),
|
|
315
|
+
log: z.boolean().optional(),
|
|
316
|
+
logger: z.unknown().optional(),
|
|
317
|
+
outputPath: z.string().optional(),
|
|
318
|
+
paths: z.array(z.string()).optional(),
|
|
319
|
+
privateToken: z.string().optional(),
|
|
320
|
+
vars: processEnvSchema.optional(),
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Instance-bound plugin config store.
|
|
325
|
+
* Host stores the validated/interpolated slice per plugin instance.
|
|
326
|
+
* The store is intentionally private to this module; definePlugin()
|
|
327
|
+
* provides a typed accessor that reads from this store for the calling
|
|
328
|
+
* plugin instance.
|
|
329
|
+
*/
|
|
330
|
+
const PLUGIN_CONFIG_STORE = new WeakMap();
|
|
331
|
+
const _getPluginConfigForInstance = (plugin) => PLUGIN_CONFIG_STORE.get(plugin);
|
|
332
|
+
|
|
237
333
|
/** src/cliHost/definePlugin.ts
|
|
238
334
|
* Plugin contracts for the GetDotenv CLI host.
|
|
239
335
|
*
|
|
@@ -241,26 +337,59 @@ const buildSpawnEnv = (base, overlay) => {
|
|
|
241
337
|
* should use (GetDotenvCliPublic). Using a structural type at the seam avoids
|
|
242
338
|
* nominal class identity issues (private fields) in downstream consumers.
|
|
243
339
|
*/
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
*
|
|
247
|
-
* @example
|
|
248
|
-
* const parent = definePlugin(\{ id: 'p', setup(cli) \{ /* ... *\/ \} \})
|
|
249
|
-
* .use(childA)
|
|
250
|
-
* .use(childB);
|
|
251
|
-
*/
|
|
252
|
-
const definePlugin = (spec) => {
|
|
340
|
+
/* eslint-disable tsdoc/syntax */
|
|
341
|
+
function definePlugin(spec) {
|
|
253
342
|
const { children = [], ...rest } = spec;
|
|
254
|
-
|
|
343
|
+
// Default to a strict empty-object schema so “no-config” plugins fail fast
|
|
344
|
+
// on unknown keys and provide a concrete {} at runtime.
|
|
345
|
+
const effectiveSchema = spec.configSchema ?? z.object({}).strict();
|
|
346
|
+
// Build base plugin first, then extend with instance-bound helpers.
|
|
347
|
+
const base = {
|
|
255
348
|
...rest,
|
|
349
|
+
// Always carry a schema (strict empty by default) to simplify host logic
|
|
350
|
+
// and improve inference/ergonomics for plugin authors.
|
|
351
|
+
configSchema: effectiveSchema,
|
|
256
352
|
children: [...children],
|
|
257
353
|
use(child) {
|
|
258
354
|
this.children.push(child);
|
|
259
355
|
return this;
|
|
260
356
|
},
|
|
261
357
|
};
|
|
262
|
-
|
|
263
|
-
|
|
358
|
+
// Attach instance-bound helpers on the returned plugin object.
|
|
359
|
+
const extended = base;
|
|
360
|
+
extended.readConfig = function (_cli) {
|
|
361
|
+
// Config is stored per-plugin-instance by the host (WeakMap in computeContext).
|
|
362
|
+
const value = _getPluginConfigForInstance(extended);
|
|
363
|
+
if (value === undefined) {
|
|
364
|
+
// Guard: host has not resolved config yet (incorrect lifecycle usage).
|
|
365
|
+
throw new Error('Plugin config not available. Ensure resolveAndLoad() has been called before readConfig().');
|
|
366
|
+
}
|
|
367
|
+
return value;
|
|
368
|
+
};
|
|
369
|
+
// Plugin-bound dynamic option factory
|
|
370
|
+
extended.createPluginDynamicOption = function (cli, flags, desc, parser, defaultValue) {
|
|
371
|
+
return cli.createDynamicOption(flags, (cfg) => {
|
|
372
|
+
// Prefer the validated slice stored per instance; fallback to help-bag
|
|
373
|
+
// (by-id) so top-level `-h` can render effective defaults before resolve.
|
|
374
|
+
const fromStore = _getPluginConfigForInstance(extended);
|
|
375
|
+
const id = extended.id;
|
|
376
|
+
let fromBag;
|
|
377
|
+
if (!fromStore && id) {
|
|
378
|
+
const maybe = cfg.plugins[id];
|
|
379
|
+
if (maybe && typeof maybe === 'object') {
|
|
380
|
+
fromBag = maybe;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Always provide a concrete object to dynamic callbacks:
|
|
384
|
+
// - With a schema: computeContext stores the parsed object.
|
|
385
|
+
// - Without a schema: computeContext stores {}.
|
|
386
|
+
// - Help-time fallback: coalesce to {} when only a by-id bag exists.
|
|
387
|
+
const cfgVal = (fromStore ?? fromBag ?? {});
|
|
388
|
+
return desc(cfg, cfgVal);
|
|
389
|
+
}, parser, defaultValue);
|
|
390
|
+
};
|
|
391
|
+
return extended;
|
|
392
|
+
}
|
|
264
393
|
|
|
265
394
|
/**
|
|
266
395
|
* Batch services (neutral): resolve command and shell settings.
|
|
@@ -481,90 +610,79 @@ const AwsPluginConfigSchema = z.object({
|
|
|
481
610
|
regionKey: z.string().default('AWS_REGION').optional(),
|
|
482
611
|
strategy: z.enum(['cli-export', 'none']).default('cli-export').optional(),
|
|
483
612
|
loginOnDemand: z.boolean().default(false).optional(),
|
|
484
|
-
setEnv: z.boolean().default(true).optional(),
|
|
485
|
-
addCtx: z.boolean().default(true).optional(),
|
|
486
613
|
});
|
|
487
614
|
|
|
488
|
-
const awsPlugin = () =>
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
...overlay,
|
|
560
|
-
};
|
|
561
|
-
// Resolve current context with overrides
|
|
562
|
-
const out = await resolveAwsContext({
|
|
563
|
-
dotenv: ctx?.dotenv ?? {},
|
|
564
|
-
cfg,
|
|
565
|
-
});
|
|
566
|
-
// Apply env/ctx mirrors per toggles
|
|
567
|
-
if (cfg.setEnv !== false) {
|
|
615
|
+
const awsPlugin = () => {
|
|
616
|
+
const plugin = definePlugin({
|
|
617
|
+
id: 'aws',
|
|
618
|
+
// Host validates this slice when the loader path is active.
|
|
619
|
+
configSchema: AwsPluginConfigSchema,
|
|
620
|
+
setup(cli) {
|
|
621
|
+
// Subcommand: aws
|
|
622
|
+
cli
|
|
623
|
+
.ns('aws')
|
|
624
|
+
.description('Establish an AWS session and optionally forward to the AWS CLI')
|
|
625
|
+
.enablePositionalOptions()
|
|
626
|
+
.passThroughOptions()
|
|
627
|
+
.allowUnknownOption(true)
|
|
628
|
+
// Boolean toggles with dynamic help labels (effective defaults)
|
|
629
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--login-on-demand', (_bag, cfg) => `attempt aws sso login on-demand${cfg.loginOnDemand ? ' (default)' : ''}`))
|
|
630
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--no-login-on-demand', (_bag, cfg) => `disable sso login on-demand${cfg.loginOnDemand === false ? ' (default)' : ''}`))
|
|
631
|
+
// Strings / enums
|
|
632
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile <string>', (_bag, cfg) => `AWS profile name${cfg.profile ? ` (default: ${JSON.stringify(cfg.profile)})` : ''}`))
|
|
633
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--region <string>', (_bag, cfg) => `AWS region${cfg.region ? ` (default: ${JSON.stringify(cfg.region)})` : ''}`))
|
|
634
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--default-region <string>', (_bag, cfg) => `fallback region${cfg.defaultRegion ? ` (default: ${JSON.stringify(cfg.defaultRegion)})` : ''}`))
|
|
635
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--strategy <string>', (_bag, cfg) => `credential acquisition strategy: cli-export|none${cfg.strategy ? ` (default: ${JSON.stringify(cfg.strategy)})` : ''}`))
|
|
636
|
+
// Advanced key overrides
|
|
637
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile-key <string>', (_bag, cfg) => `dotenv/config key for local profile${cfg.profileKey ? ` (default: ${JSON.stringify(cfg.profileKey)})` : ''}`))
|
|
638
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile-fallback-key <string>', (_bag, cfg) => `fallback dotenv/config key for profile${cfg.profileFallbackKey ? ` (default: ${JSON.stringify(cfg.profileFallbackKey)})` : ''}`))
|
|
639
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--region-key <string>', (_bag, cfg) => `dotenv/config key for region${cfg.regionKey ? ` (default: ${JSON.stringify(cfg.regionKey)})` : ''}`))
|
|
640
|
+
// Accept any extra operands so Commander does not error when tokens appear after "--".
|
|
641
|
+
.argument('[args...]')
|
|
642
|
+
.action(async (args, opts, thisCommand) => {
|
|
643
|
+
const pluginInst = plugin;
|
|
644
|
+
const cmdSelf = thisCommand;
|
|
645
|
+
const parent = (cmdSelf.parent ?? null);
|
|
646
|
+
// Access merged root CLI options (installed by passOptions())
|
|
647
|
+
const rootOpts = (parent?.getDotenvCliOptions ?? {});
|
|
648
|
+
const capture = process.env.GETDOTENV_STDIO === 'pipe' ||
|
|
649
|
+
Boolean(rootOpts?.capture);
|
|
650
|
+
const underTests = process.env.GETDOTENV_TEST === '1' ||
|
|
651
|
+
typeof process.env.VITEST_WORKER_ID === 'string';
|
|
652
|
+
// Build overlay cfg from subcommand flags layered over discovered config.
|
|
653
|
+
const ctx = cli.getCtx();
|
|
654
|
+
const cfgBase = pluginInst.readConfig(cli);
|
|
655
|
+
const o = opts;
|
|
656
|
+
const overlay = {};
|
|
657
|
+
// Map boolean toggles (respect explicit --no-*)
|
|
658
|
+
if (Object.prototype.hasOwnProperty.call(o, 'loginOnDemand'))
|
|
659
|
+
overlay.loginOnDemand = Boolean(o.loginOnDemand);
|
|
660
|
+
// Strings/enums
|
|
661
|
+
if (typeof o.profile === 'string')
|
|
662
|
+
overlay.profile = o.profile;
|
|
663
|
+
if (typeof o.region === 'string')
|
|
664
|
+
overlay.region = o.region;
|
|
665
|
+
if (typeof o.defaultRegion === 'string')
|
|
666
|
+
overlay.defaultRegion = o.defaultRegion;
|
|
667
|
+
if (typeof o.strategy === 'string')
|
|
668
|
+
overlay.strategy = o.strategy;
|
|
669
|
+
// Advanced key overrides
|
|
670
|
+
if (typeof o.profileKey === 'string')
|
|
671
|
+
overlay.profileKey = o.profileKey;
|
|
672
|
+
if (typeof o.profileFallbackKey === 'string')
|
|
673
|
+
overlay.profileFallbackKey = o.profileFallbackKey;
|
|
674
|
+
if (typeof o.regionKey === 'string')
|
|
675
|
+
overlay.regionKey = o.regionKey;
|
|
676
|
+
const cfg = {
|
|
677
|
+
...cfgBase,
|
|
678
|
+
...overlay,
|
|
679
|
+
};
|
|
680
|
+
// Resolve current context with overrides
|
|
681
|
+
const out = await resolveAwsContext({
|
|
682
|
+
dotenv: ctx?.dotenv ?? {},
|
|
683
|
+
cfg,
|
|
684
|
+
});
|
|
685
|
+
// Unconditional env writes (no per-plugin toggle)
|
|
568
686
|
if (out.region) {
|
|
569
687
|
process.env.AWS_REGION = out.region;
|
|
570
688
|
if (!process.env.AWS_DEFAULT_REGION)
|
|
@@ -578,58 +696,53 @@ const awsPlugin = () => definePlugin({
|
|
|
578
696
|
process.env.AWS_SESSION_TOKEN = out.credentials.sessionToken;
|
|
579
697
|
}
|
|
580
698
|
}
|
|
581
|
-
|
|
582
|
-
if (cfg.addCtx !== false) {
|
|
699
|
+
// Always publish minimal non-sensitive metadata
|
|
583
700
|
if (ctx) {
|
|
584
701
|
ctx.plugins ??= {};
|
|
585
702
|
ctx.plugins['aws'] = {
|
|
586
703
|
...(out.profile ? { profile: out.profile } : {}),
|
|
587
704
|
...(out.region ? { region: out.region } : {}),
|
|
588
|
-
...(out.credentials ? { credentials: out.credentials } : {}),
|
|
589
705
|
};
|
|
590
706
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
env: buildSpawnEnv(process.env, ctxDotenv),
|
|
599
|
-
stdio: capture ? 'pipe' : 'inherit',
|
|
600
|
-
});
|
|
601
|
-
// Deterministic termination (suppressed under tests)
|
|
602
|
-
if (!underTests) {
|
|
603
|
-
process.exit(typeof exit === 'number' ? exit : 0);
|
|
604
|
-
}
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
else {
|
|
608
|
-
// Session only: low-noise breadcrumb under debug
|
|
609
|
-
if (process.env.GETDOTENV_DEBUG) {
|
|
610
|
-
const log = console;
|
|
611
|
-
log.log('[aws] session established', {
|
|
612
|
-
profile: out.profile,
|
|
613
|
-
region: out.region,
|
|
614
|
-
hasCreds: Boolean(out.credentials),
|
|
707
|
+
// Forward when positional args are present; otherwise session-only.
|
|
708
|
+
if (Array.isArray(args) && args.length > 0) {
|
|
709
|
+
const argv = ['aws', ...args];
|
|
710
|
+
const shellSetting = resolveShell(rootOpts?.scripts, 'aws', rootOpts?.shell);
|
|
711
|
+
const exit = await runCommand(argv, shellSetting, {
|
|
712
|
+
env: buildSpawnEnv(process.env, ctx?.dotenv),
|
|
713
|
+
stdio: capture ? 'pipe' : 'inherit',
|
|
615
714
|
});
|
|
715
|
+
// Deterministic termination (suppressed under tests)
|
|
716
|
+
if (!underTests) {
|
|
717
|
+
process.exit(typeof exit === 'number' ? exit : 0);
|
|
718
|
+
}
|
|
719
|
+
return;
|
|
616
720
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
721
|
+
else {
|
|
722
|
+
// Session only: low-noise breadcrumb under debug
|
|
723
|
+
if (process.env.GETDOTENV_DEBUG) {
|
|
724
|
+
const log = console;
|
|
725
|
+
log.log('[aws] session established', {
|
|
726
|
+
profile: out.profile,
|
|
727
|
+
region: out.region,
|
|
728
|
+
hasCreds: Boolean(out.credentials),
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
if (!underTests)
|
|
732
|
+
process.exit(0);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
},
|
|
737
|
+
async afterResolve(_cli, ctx) {
|
|
738
|
+
const log = console;
|
|
739
|
+
const cfg = plugin.readConfig(_cli);
|
|
740
|
+
const out = await resolveAwsContext({
|
|
741
|
+
dotenv: ctx.dotenv,
|
|
742
|
+
cfg,
|
|
743
|
+
});
|
|
744
|
+
const { profile, region, credentials } = out;
|
|
745
|
+
// Unconditional env writes in host path
|
|
633
746
|
if (region) {
|
|
634
747
|
process.env.AWS_REGION = region;
|
|
635
748
|
if (!process.env.AWS_DEFAULT_REGION)
|
|
@@ -642,24 +755,23 @@ const awsPlugin = () => definePlugin({
|
|
|
642
755
|
process.env.AWS_SESSION_TOKEN = credentials.sessionToken;
|
|
643
756
|
}
|
|
644
757
|
}
|
|
645
|
-
|
|
646
|
-
if (cfg.addCtx !== false) {
|
|
758
|
+
// Always publish minimal non-sensitive metadata
|
|
647
759
|
ctx.plugins ??= {};
|
|
648
760
|
ctx.plugins['aws'] = {
|
|
649
761
|
...(profile ? { profile } : {}),
|
|
650
762
|
...(region ? { region } : {}),
|
|
651
|
-
...(credentials ? { credentials } : {}),
|
|
652
763
|
};
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
764
|
+
// Optional: low-noise breadcrumb for diagnostics
|
|
765
|
+
if (process.env.GETDOTENV_DEBUG) {
|
|
766
|
+
log.log('[aws] afterResolve', {
|
|
767
|
+
profile,
|
|
768
|
+
region,
|
|
769
|
+
hasCreds: Boolean(credentials),
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
});
|
|
774
|
+
return plugin;
|
|
775
|
+
};
|
|
664
776
|
|
|
665
777
|
export { awsPlugin };
|