@karmaniverous/get-dotenv 6.0.0 → 6.1.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 +18 -14
- package/dist/cli.mjs +744 -360
- package/dist/cliHost.d.ts +14 -1
- package/dist/cliHost.mjs +54 -8
- package/dist/config.d.ts +4 -0
- package/dist/config.mjs +17 -7
- package/dist/env-overlay.d.ts +11 -0
- package/dist/env-overlay.mjs +24 -7
- package/dist/getdotenv.cli.mjs +744 -360
- package/dist/index.d.ts +88 -2
- package/dist/index.mjs +757 -361
- package/dist/plugins-aws.d.ts +4 -3
- package/dist/plugins-aws.mjs +369 -177
- package/dist/plugins-batch.d.ts +2 -1
- package/dist/plugins-batch.mjs +124 -48
- package/dist/plugins-cmd.d.ts +2 -1
- package/dist/plugins-cmd.mjs +77 -22
- package/dist/plugins-init.d.ts +2 -1
- package/dist/plugins-init.mjs +235 -128
- package/dist/plugins.d.ts +7 -2
- package/dist/plugins.mjs +740 -360
- package/dist/templates/cli/index.ts +1 -2
- package/dist/templates/cli/plugins/hello/defaultAction.ts +27 -0
- package/dist/templates/cli/plugins/hello/index.ts +26 -0
- package/dist/templates/cli/plugins/hello/options.ts +31 -0
- package/dist/templates/cli/plugins/hello/strangerAction.ts +20 -0
- package/dist/templates/cli/plugins/hello/types.ts +13 -0
- package/dist/templates/config/ts/getdotenv.config.ts +1 -1
- package/dist/templates/defaultAction.ts +27 -0
- package/dist/templates/getdotenv.config.ts +1 -1
- package/dist/templates/hello/defaultAction.ts +27 -0
- package/dist/templates/hello/index.ts +26 -0
- package/dist/templates/hello/options.ts +31 -0
- package/dist/templates/hello/strangerAction.ts +20 -0
- package/dist/templates/hello/types.ts +13 -0
- package/dist/templates/index.ts +22 -22
- package/dist/templates/options.ts +31 -0
- package/dist/templates/plugins/hello/defaultAction.ts +27 -0
- package/dist/templates/plugins/hello/index.ts +26 -0
- package/dist/templates/plugins/hello/options.ts +31 -0
- package/dist/templates/plugins/hello/strangerAction.ts +20 -0
- package/dist/templates/plugins/hello/types.ts +13 -0
- package/dist/templates/strangerAction.ts +20 -0
- package/dist/templates/ts/getdotenv.config.ts +1 -1
- package/dist/templates/types.ts +13 -0
- package/package.json +2 -2
- package/templates/cli/index.ts +1 -2
- package/templates/cli/plugins/hello/defaultAction.ts +27 -0
- package/templates/cli/plugins/hello/index.ts +26 -0
- package/templates/cli/plugins/hello/options.ts +31 -0
- package/templates/cli/plugins/hello/strangerAction.ts +20 -0
- package/templates/cli/plugins/hello/types.ts +13 -0
- package/templates/config/ts/getdotenv.config.ts +1 -1
- package/dist/templates/cli/plugins/hello.ts +0 -43
- package/dist/templates/hello.ts +0 -43
- package/dist/templates/plugins/hello.ts +0 -43
- package/templates/cli/plugins/hello.ts +0 -43
package/dist/plugins-aws.mjs
CHANGED
|
@@ -151,6 +151,10 @@ function defaultsDeep(...layers) {
|
|
|
151
151
|
/**
|
|
152
152
|
* Serialize a dotenv record to a file with minimal quoting (multiline values are quoted).
|
|
153
153
|
* Future-proofs for ordering/sorting changes (currently insertion order).
|
|
154
|
+
*
|
|
155
|
+
* @param filename - Destination dotenv file path.
|
|
156
|
+
* @param data - Env-like map of values to write (values may be `undefined`).
|
|
157
|
+
* @returns A `Promise\<void\>` which resolves when the file has been written.
|
|
154
158
|
*/
|
|
155
159
|
async function writeDotenvFile(filename, data) {
|
|
156
160
|
// Serialize: key=value with quotes only for multiline values.
|
|
@@ -386,14 +390,20 @@ const cleanupOldCacheFiles = async (cacheDir, baseName, keep = Math.max(1, Numbe
|
|
|
386
390
|
}
|
|
387
391
|
};
|
|
388
392
|
/**
|
|
389
|
-
* Load a module default export from a JS/TS file with robust fallbacks
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
* 2) esbuild bundle to a temp ESM file,
|
|
393
|
-
* 3) typescript.transpileModule fallback for simple modules.
|
|
393
|
+
* Load a module default export from a JS/TS file with robust fallbacks.
|
|
394
|
+
*
|
|
395
|
+
* Behavior by extension:
|
|
394
396
|
*
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
+
* - `.js`/`.mjs`/`.cjs`: direct dynamic import.
|
|
398
|
+
* - `.ts`/`.mts`/`.cts`/`.tsx`:
|
|
399
|
+
* - try direct dynamic import (when a TS loader is active),
|
|
400
|
+
* - else compile via `esbuild` to a cached `.mjs` file and import,
|
|
401
|
+
* - else fallback to `typescript.transpileModule` for simple modules.
|
|
402
|
+
*
|
|
403
|
+
* @typeParam T - Type of the expected default export.
|
|
404
|
+
* @param absPath - Absolute path to the source file.
|
|
405
|
+
* @param cacheDirName - Cache subfolder under `.tsbuild/`.
|
|
406
|
+
* @returns A `Promise\<T | undefined\>` resolving to the default export (if any).
|
|
397
407
|
*/
|
|
398
408
|
const loadModuleDefault = async (absPath, cacheDirName) => {
|
|
399
409
|
const ext = path.extname(absPath).toLowerCase();
|
|
@@ -465,6 +475,10 @@ const loadModuleDefault = async (absPath, cacheDirName) => {
|
|
|
465
475
|
/**
|
|
466
476
|
* Omit keys whose runtime value is undefined from a shallow object.
|
|
467
477
|
* Returns a Partial with non-undefined value types preserved.
|
|
478
|
+
*
|
|
479
|
+
* @typeParam T - Input object shape.
|
|
480
|
+
* @param obj - Object to filter.
|
|
481
|
+
* @returns A shallow copy of `obj` without keys whose value is `undefined`.
|
|
468
482
|
*/
|
|
469
483
|
function omitUndefined(obj) {
|
|
470
484
|
const out = {};
|
|
@@ -476,6 +490,10 @@ function omitUndefined(obj) {
|
|
|
476
490
|
}
|
|
477
491
|
/**
|
|
478
492
|
* Specialized helper for env-like maps: drop undefined and return string-only.
|
|
493
|
+
*
|
|
494
|
+
* @typeParam V - Value type for present entries (must extend `string`).
|
|
495
|
+
* @param obj - Env-like record containing `string | undefined` values.
|
|
496
|
+
* @returns A new record containing only the keys with defined values.
|
|
479
497
|
*/
|
|
480
498
|
function omitUndefinedRecord(obj) {
|
|
481
499
|
const out = {};
|
|
@@ -690,6 +708,11 @@ const resolveGetDotenvConfigSources = async (importMetaUrl) => {
|
|
|
690
708
|
* Apply a dynamic map to the target progressively.
|
|
691
709
|
* - Functions receive (target, env) and may return string | undefined.
|
|
692
710
|
* - Literals are assigned directly (including undefined).
|
|
711
|
+
*
|
|
712
|
+
* @param target - Mutable target environment to assign into.
|
|
713
|
+
* @param map - Dynamic map to apply (functions and/or literal values).
|
|
714
|
+
* @param env - Selected environment name (if any) passed through to dynamic functions.
|
|
715
|
+
* @returns Nothing.
|
|
693
716
|
*/
|
|
694
717
|
function applyDynamicMap(target, map, env) {
|
|
695
718
|
if (!map)
|
|
@@ -709,6 +732,12 @@ function applyDynamicMap(target, map, env) {
|
|
|
709
732
|
* Error behavior:
|
|
710
733
|
* - On failure to load/compile/evaluate the module, throws a unified message:
|
|
711
734
|
* "Unable to load dynamic TypeScript file: <absPath>. Install 'esbuild'..."
|
|
735
|
+
*
|
|
736
|
+
* @param target - Mutable target environment to assign into.
|
|
737
|
+
* @param absPath - Absolute path to the dynamic module file.
|
|
738
|
+
* @param env - Selected environment name (if any).
|
|
739
|
+
* @param cacheDirName - Cache subdirectory under `.tsbuild/` for compiled artifacts.
|
|
740
|
+
* @returns A `Promise\<void\>` which resolves after the module (if present) has been applied.
|
|
712
741
|
*/
|
|
713
742
|
async function loadAndApplyDynamic(target, absPath, env, cacheDirName) {
|
|
714
743
|
if (!(await fs.exists(absPath)))
|
|
@@ -1799,6 +1828,10 @@ function renderOptionGroups(cmd) {
|
|
|
1799
1828
|
/**
|
|
1800
1829
|
* Compose root/parent help output by inserting grouped sections between
|
|
1801
1830
|
* Options and Commands, ensuring a trailing blank line.
|
|
1831
|
+
*
|
|
1832
|
+
* @param base - Base help text produced by Commander.
|
|
1833
|
+
* @param cmd - Command instance whose grouped options should be rendered.
|
|
1834
|
+
* @returns The modified help text with grouped blocks inserted.
|
|
1802
1835
|
*/
|
|
1803
1836
|
function buildHelpInformation(base, cmd) {
|
|
1804
1837
|
const groups = renderOptionGroups(cmd);
|
|
@@ -2395,23 +2428,57 @@ const buildSpawnEnv = (base, overlay) => {
|
|
|
2395
2428
|
* Apply resolved AWS context to `process.env` and `ctx.plugins`.
|
|
2396
2429
|
* Centralizes logic shared between the plugin action and `afterResolve` hook.
|
|
2397
2430
|
*
|
|
2431
|
+
* @param out - Resolved AWS context to apply.
|
|
2432
|
+
* @param ctx - Host context to publish non-sensitive metadata into.
|
|
2398
2433
|
* @param setProcessEnv - Whether to write credentials/region to `process.env` (default true).
|
|
2434
|
+
* @returns Nothing.
|
|
2399
2435
|
*/
|
|
2400
2436
|
function applyAwsContext(out, ctx, setProcessEnv = true) {
|
|
2401
2437
|
const { profile, region, credentials } = out;
|
|
2402
2438
|
if (setProcessEnv) {
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2439
|
+
// Ensure AWS credential sources are mutually exclusive.
|
|
2440
|
+
// The AWS SDK warns (and may change precedence in future) when both
|
|
2441
|
+
// AWS_PROFILE and AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY are set.
|
|
2442
|
+
const clear = (keys) => {
|
|
2443
|
+
for (const k of keys) {
|
|
2444
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
2445
|
+
delete process.env[k];
|
|
2407
2446
|
}
|
|
2408
|
-
}
|
|
2447
|
+
};
|
|
2448
|
+
const clearProfileVars = () => {
|
|
2449
|
+
clear(['AWS_PROFILE', 'AWS_DEFAULT_PROFILE', 'AWS_SDK_LOAD_CONFIG']);
|
|
2450
|
+
};
|
|
2451
|
+
const clearStaticCreds = () => {
|
|
2452
|
+
clear([
|
|
2453
|
+
'AWS_ACCESS_KEY_ID',
|
|
2454
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
2455
|
+
'AWS_SESSION_TOKEN',
|
|
2456
|
+
]);
|
|
2457
|
+
};
|
|
2458
|
+
// Mode A: exported/static credentials (clear profile vars)
|
|
2409
2459
|
if (credentials) {
|
|
2460
|
+
clearProfileVars();
|
|
2410
2461
|
process.env.AWS_ACCESS_KEY_ID = credentials.accessKeyId;
|
|
2411
2462
|
process.env.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey;
|
|
2412
2463
|
if (credentials.sessionToken !== undefined) {
|
|
2413
2464
|
process.env.AWS_SESSION_TOKEN = credentials.sessionToken;
|
|
2414
2465
|
}
|
|
2466
|
+
else {
|
|
2467
|
+
delete process.env.AWS_SESSION_TOKEN;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
else if (profile) {
|
|
2471
|
+
// Mode B: profile-based (SSO) credentials (clear static creds)
|
|
2472
|
+
clearStaticCreds();
|
|
2473
|
+
process.env.AWS_PROFILE = profile;
|
|
2474
|
+
process.env.AWS_DEFAULT_PROFILE = profile;
|
|
2475
|
+
process.env.AWS_SDK_LOAD_CONFIG = '1';
|
|
2476
|
+
}
|
|
2477
|
+
if (region) {
|
|
2478
|
+
process.env.AWS_REGION = region;
|
|
2479
|
+
if (!process.env.AWS_DEFAULT_REGION) {
|
|
2480
|
+
process.env.AWS_DEFAULT_REGION = region;
|
|
2481
|
+
}
|
|
2415
2482
|
}
|
|
2416
2483
|
}
|
|
2417
2484
|
// Always publish minimal, non-sensitive metadata
|
|
@@ -2422,7 +2489,7 @@ function applyAwsContext(out, ctx, setProcessEnv = true) {
|
|
|
2422
2489
|
};
|
|
2423
2490
|
}
|
|
2424
2491
|
|
|
2425
|
-
const
|
|
2492
|
+
const AWS_CLI_TIMEOUT_MS = 15_000;
|
|
2426
2493
|
const trim = (s) => (typeof s === 'string' ? s.trim() : '');
|
|
2427
2494
|
const unquote = (s) => s.length >= 2 &&
|
|
2428
2495
|
((s.startsWith('"') && s.endsWith('"')) ||
|
|
@@ -2431,6 +2498,9 @@ const unquote = (s) => s.length >= 2 &&
|
|
|
2431
2498
|
: s;
|
|
2432
2499
|
/**
|
|
2433
2500
|
* Parse AWS credentials from JSON output (AWS CLI v2 export-credentials).
|
|
2501
|
+
*
|
|
2502
|
+
* @param txt - Raw stdout text from the AWS CLI.
|
|
2503
|
+
* @returns Parsed credentials, or `undefined` when the input is not recognized.
|
|
2434
2504
|
*/
|
|
2435
2505
|
const parseExportCredentialsJson = (txt) => {
|
|
2436
2506
|
try {
|
|
@@ -2454,6 +2524,10 @@ const parseExportCredentialsJson = (txt) => {
|
|
|
2454
2524
|
/**
|
|
2455
2525
|
* Parse AWS credentials from environment-export output (shell-agnostic).
|
|
2456
2526
|
* Supports POSIX `export KEY=VAL` and PowerShell `$Env:KEY=VAL`.
|
|
2527
|
+
* Also supports AWS CLI `windows-cmd` (`set KEY=VAL`) and `env-no-export` (`KEY=VAL`).
|
|
2528
|
+
*
|
|
2529
|
+
* @param txt - Raw stdout text from the AWS CLI.
|
|
2530
|
+
* @returns Parsed credentials, or `undefined` when the input is not recognized.
|
|
2457
2531
|
*/
|
|
2458
2532
|
const parseExportCredentialsEnv = (txt) => {
|
|
2459
2533
|
const lines = txt.split(/\r?\n/);
|
|
@@ -2464,12 +2538,17 @@ const parseExportCredentialsEnv = (txt) => {
|
|
|
2464
2538
|
const line = raw.trim();
|
|
2465
2539
|
if (!line)
|
|
2466
2540
|
continue;
|
|
2467
|
-
// POSIX: export AWS_ACCESS_KEY_ID=...,
|
|
2541
|
+
// POSIX: export AWS_ACCESS_KEY_ID=..., ...
|
|
2468
2542
|
let m = /^export\s+([A-Z0-9_]+)\s*=\s*(.+)$/.exec(line);
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
m = /^\$Env:([A-Z0-9_]+)\s*=\s*(.+)
|
|
2472
|
-
|
|
2543
|
+
// PowerShell: $Env:AWS_ACCESS_KEY_ID="...", etc.
|
|
2544
|
+
if (!m)
|
|
2545
|
+
m = /^\$Env:([A-Z0-9_]+)\s*=\s*(.+)$/i.exec(line);
|
|
2546
|
+
// Windows cmd: set AWS_ACCESS_KEY_ID=..., etc.
|
|
2547
|
+
if (!m)
|
|
2548
|
+
m = /^(?:set)\s+([A-Z0-9_]+)\s*=\s*(.+)$/i.exec(line);
|
|
2549
|
+
// env-no-export: AWS_ACCESS_KEY_ID=..., etc.
|
|
2550
|
+
if (!m)
|
|
2551
|
+
m = /^([A-Z0-9_]+)\s*=\s*(.+)$/.exec(line);
|
|
2473
2552
|
if (!m)
|
|
2474
2553
|
continue;
|
|
2475
2554
|
const k = m[1];
|
|
@@ -2494,7 +2573,7 @@ const parseExportCredentialsEnv = (txt) => {
|
|
|
2494
2573
|
};
|
|
2495
2574
|
return undefined;
|
|
2496
2575
|
};
|
|
2497
|
-
const getAwsConfigure = async (key, profile, timeoutMs =
|
|
2576
|
+
const getAwsConfigure = async (key, profile, timeoutMs = AWS_CLI_TIMEOUT_MS) => {
|
|
2498
2577
|
const r = await runCommandResult(['aws', 'configure', 'get', key, '--profile', profile], false, {
|
|
2499
2578
|
env: process.env,
|
|
2500
2579
|
timeoutMs,
|
|
@@ -2509,36 +2588,50 @@ const getAwsConfigure = async (key, profile, timeoutMs = DEFAULT_TIMEOUT_MS) =>
|
|
|
2509
2588
|
}
|
|
2510
2589
|
return undefined;
|
|
2511
2590
|
};
|
|
2512
|
-
const exportCredentials = async (profile, timeoutMs =
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2591
|
+
const exportCredentials = async (profile, timeoutMs = AWS_CLI_TIMEOUT_MS) => {
|
|
2592
|
+
const tryExport = async (format) => {
|
|
2593
|
+
const argv = [
|
|
2594
|
+
'aws',
|
|
2595
|
+
'configure',
|
|
2596
|
+
'export-credentials',
|
|
2597
|
+
'--profile',
|
|
2598
|
+
profile,
|
|
2599
|
+
...(format ? ['--format', format] : []),
|
|
2600
|
+
];
|
|
2601
|
+
const r = await runCommandResult(argv, false, {
|
|
2602
|
+
env: process.env,
|
|
2603
|
+
timeoutMs,
|
|
2604
|
+
});
|
|
2605
|
+
if (r.exitCode !== 0)
|
|
2606
|
+
return undefined;
|
|
2607
|
+
const out = trim(r.stdout);
|
|
2608
|
+
if (!out)
|
|
2609
|
+
return undefined;
|
|
2610
|
+
// Some formats produce JSON ("process"), some produce shell-ish env lines.
|
|
2611
|
+
return parseExportCredentialsJson(out) ?? parseExportCredentialsEnv(out);
|
|
2612
|
+
};
|
|
2613
|
+
// Prefer the default/JSON "process" format first; then fall back to shell env outputs.
|
|
2614
|
+
// Note: AWS CLI v2 supports: process | env | env-no-export | powershell | windows-cmd
|
|
2615
|
+
const formats = [
|
|
2616
|
+
'process',
|
|
2617
|
+
...(process.platform === 'win32'
|
|
2618
|
+
? ['powershell', 'windows-cmd', 'env', 'env-no-export']
|
|
2619
|
+
: ['env', 'env-no-export']),
|
|
2620
|
+
];
|
|
2621
|
+
for (const f of formats) {
|
|
2622
|
+
const creds = await tryExport(f);
|
|
2532
2623
|
if (creds)
|
|
2533
2624
|
return creds;
|
|
2534
2625
|
}
|
|
2535
|
-
|
|
2626
|
+
// Final fallback: no --format (AWS CLI default output)
|
|
2627
|
+
return tryExport(undefined);
|
|
2536
2628
|
};
|
|
2537
2629
|
/**
|
|
2538
2630
|
* Resolve AWS context (profile, region, credentials) using configuration and environment.
|
|
2539
2631
|
* Applies strategy (cli-export vs none) and handling for SSO login-on-demand.
|
|
2540
2632
|
*
|
|
2541
2633
|
* @param options - Context options including current dotenv and plugin config.
|
|
2634
|
+
* @returns A `Promise\<AwsContext\>` containing any resolved profile, region, and credentials.
|
|
2542
2635
|
*/
|
|
2543
2636
|
const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
2544
2637
|
const profileKey = cfg.profileKey ?? 'AWS_LOCAL_PROFILE';
|
|
@@ -2563,31 +2656,27 @@ const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
|
2563
2656
|
out.region = region;
|
|
2564
2657
|
return out;
|
|
2565
2658
|
}
|
|
2566
|
-
// Env-first credentials.
|
|
2567
2659
|
let credentials;
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
const envToken = trim(process.env.AWS_SESSION_TOKEN);
|
|
2571
|
-
if (envId && envSecret) {
|
|
2572
|
-
credentials = {
|
|
2573
|
-
accessKeyId: envId,
|
|
2574
|
-
secretAccessKey: envSecret,
|
|
2575
|
-
...(envToken ? { sessionToken: envToken } : {}),
|
|
2576
|
-
};
|
|
2577
|
-
}
|
|
2578
|
-
else if (profile) {
|
|
2660
|
+
// Profile wins over ambient env creds when present (from flags/config/dotenv).
|
|
2661
|
+
if (profile) {
|
|
2579
2662
|
// Try export-credentials
|
|
2580
2663
|
credentials = await exportCredentials(profile);
|
|
2581
2664
|
// On failure, detect SSO and optionally login then retry
|
|
2582
2665
|
if (!credentials) {
|
|
2583
2666
|
const ssoSession = await getAwsConfigure('sso_session', profile);
|
|
2584
|
-
|
|
2667
|
+
// Legacy SSO profiles use sso_start_url/sso_region rather than sso_session.
|
|
2668
|
+
const ssoStartUrl = await getAwsConfigure('sso_start_url', profile);
|
|
2669
|
+
const looksSSO = (typeof ssoSession === 'string' && ssoSession.length > 0) ||
|
|
2670
|
+
(typeof ssoStartUrl === 'string' && ssoStartUrl.length > 0);
|
|
2585
2671
|
if (looksSSO && cfg.loginOnDemand) {
|
|
2586
|
-
//
|
|
2587
|
-
await
|
|
2672
|
+
// Interactive login (no timeout by default), then retry export once.
|
|
2673
|
+
const exit = await runCommand(['aws', 'sso', 'login', '--profile', profile], false, {
|
|
2588
2674
|
env: process.env,
|
|
2589
|
-
|
|
2675
|
+
stdio: 'inherit',
|
|
2590
2676
|
});
|
|
2677
|
+
if (exit !== 0) {
|
|
2678
|
+
throw new Error(`aws sso login failed for profile '${profile}' (exit ${String(exit)})`);
|
|
2679
|
+
}
|
|
2591
2680
|
credentials = await exportCredentials(profile);
|
|
2592
2681
|
}
|
|
2593
2682
|
}
|
|
@@ -2605,6 +2694,19 @@ const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
|
2605
2694
|
}
|
|
2606
2695
|
}
|
|
2607
2696
|
}
|
|
2697
|
+
else {
|
|
2698
|
+
// Env-first credentials when no profile is present.
|
|
2699
|
+
const envId = trim(process.env.AWS_ACCESS_KEY_ID);
|
|
2700
|
+
const envSecret = trim(process.env.AWS_SECRET_ACCESS_KEY);
|
|
2701
|
+
const envToken = trim(process.env.AWS_SESSION_TOKEN);
|
|
2702
|
+
if (envId && envSecret) {
|
|
2703
|
+
credentials = {
|
|
2704
|
+
accessKeyId: envId,
|
|
2705
|
+
secretAccessKey: envSecret,
|
|
2706
|
+
...(envToken ? { sessionToken: envToken } : {}),
|
|
2707
|
+
};
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2608
2710
|
// Final region resolution
|
|
2609
2711
|
if (!region && profile)
|
|
2610
2712
|
region = await getAwsConfigure('region', profile);
|
|
@@ -2620,10 +2722,213 @@ const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
|
2620
2722
|
return out;
|
|
2621
2723
|
};
|
|
2622
2724
|
|
|
2725
|
+
/**
|
|
2726
|
+
* Create the AWS plugin `afterResolve` hook.
|
|
2727
|
+
*
|
|
2728
|
+
* This runs once per invocation after the host resolves dotenv context.
|
|
2729
|
+
*
|
|
2730
|
+
* @param plugin - The AWS plugin instance.
|
|
2731
|
+
* @returns An `afterResolve` hook function suitable for assigning to `plugin.afterResolve`.
|
|
2732
|
+
*
|
|
2733
|
+
* @internal
|
|
2734
|
+
*/
|
|
2735
|
+
function attachAwsAfterResolveHook(plugin) {
|
|
2736
|
+
return async (cli, ctx) => {
|
|
2737
|
+
const cfg = plugin.readConfig(cli);
|
|
2738
|
+
const out = await resolveAwsContext({
|
|
2739
|
+
dotenv: ctx.dotenv,
|
|
2740
|
+
cfg,
|
|
2741
|
+
});
|
|
2742
|
+
applyAwsContext(out, ctx, true);
|
|
2743
|
+
// Optional: low-noise breadcrumb for diagnostics
|
|
2744
|
+
if (process.env.GETDOTENV_DEBUG) {
|
|
2745
|
+
try {
|
|
2746
|
+
const msg = JSON.stringify({
|
|
2747
|
+
profile: out.profile,
|
|
2748
|
+
region: out.region,
|
|
2749
|
+
hasCreds: Boolean(out.credentials),
|
|
2750
|
+
});
|
|
2751
|
+
process.stderr.write(`[aws] afterResolve ${msg}\n`);
|
|
2752
|
+
}
|
|
2753
|
+
catch {
|
|
2754
|
+
/* ignore */
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
/** @internal */
|
|
2761
|
+
const isRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
2762
|
+
/**
|
|
2763
|
+
* Create an AWS plugin config overlay from Commander-parsed option values.
|
|
2764
|
+
*
|
|
2765
|
+
* This preserves tri-state intent:
|
|
2766
|
+
* - If a flag was not provided, it should not overwrite config-derived defaults.
|
|
2767
|
+
* - If `--no-…` was provided, it must explicitly force the boolean false.
|
|
2768
|
+
*
|
|
2769
|
+
* @param opts - Commander option values for the current invocation.
|
|
2770
|
+
* @returns A partial AWS plugin config object containing only explicit overrides.
|
|
2771
|
+
*
|
|
2772
|
+
* @internal
|
|
2773
|
+
*/
|
|
2774
|
+
function awsConfigOverridesFromCommandOpts(opts) {
|
|
2775
|
+
const o = isRecord(opts) ? opts : {};
|
|
2776
|
+
const overlay = {};
|
|
2777
|
+
// Map boolean toggles (respect explicit --no-*)
|
|
2778
|
+
if (Object.prototype.hasOwnProperty.call(o, 'loginOnDemand')) {
|
|
2779
|
+
overlay.loginOnDemand = Boolean(o.loginOnDemand);
|
|
2780
|
+
}
|
|
2781
|
+
// Strings/enums
|
|
2782
|
+
if (typeof o.profile === 'string')
|
|
2783
|
+
overlay.profile = o.profile;
|
|
2784
|
+
if (typeof o.region === 'string')
|
|
2785
|
+
overlay.region = o.region;
|
|
2786
|
+
if (typeof o.defaultRegion === 'string')
|
|
2787
|
+
overlay.defaultRegion = o.defaultRegion;
|
|
2788
|
+
if (o.strategy === 'cli-export' || o.strategy === 'none') {
|
|
2789
|
+
overlay.strategy = o.strategy;
|
|
2790
|
+
}
|
|
2791
|
+
// Advanced key overrides
|
|
2792
|
+
if (typeof o.profileKey === 'string')
|
|
2793
|
+
overlay.profileKey = o.profileKey;
|
|
2794
|
+
if (typeof o.profileFallbackKey === 'string') {
|
|
2795
|
+
overlay.profileFallbackKey = o.profileFallbackKey;
|
|
2796
|
+
}
|
|
2797
|
+
if (typeof o.regionKey === 'string')
|
|
2798
|
+
overlay.regionKey = o.regionKey;
|
|
2799
|
+
return overlay;
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
/**
|
|
2803
|
+
* Attach the default action for the AWS plugin mount.
|
|
2804
|
+
*
|
|
2805
|
+
* Behavior:
|
|
2806
|
+
* - With args: forwards to AWS CLI (`aws <args...>`) under the established session.
|
|
2807
|
+
* - Without args: session-only establishment (no forward).
|
|
2808
|
+
*
|
|
2809
|
+
* @param cli - The `aws` command mount.
|
|
2810
|
+
* @param plugin - The AWS plugin instance.
|
|
2811
|
+
*
|
|
2812
|
+
* @internal
|
|
2813
|
+
*/
|
|
2814
|
+
function attachAwsDefaultAction(cli, plugin, awsCmd) {
|
|
2815
|
+
awsCmd.action(async (args, opts, thisCommand) => {
|
|
2816
|
+
// Access merged root CLI options (installed by root hooks).
|
|
2817
|
+
const bag = readMergedOptions(thisCommand);
|
|
2818
|
+
const capture = shouldCapture(bag.capture);
|
|
2819
|
+
const underTests = process.env.GETDOTENV_TEST === '1' ||
|
|
2820
|
+
typeof process.env.VITEST_WORKER_ID === 'string';
|
|
2821
|
+
// Build overlay cfg from subcommand flags layered over discovered config.
|
|
2822
|
+
const ctx = cli.getCtx();
|
|
2823
|
+
const cfgBase = plugin.readConfig(cli);
|
|
2824
|
+
const cfg = {
|
|
2825
|
+
...cfgBase,
|
|
2826
|
+
...awsConfigOverridesFromCommandOpts(opts),
|
|
2827
|
+
};
|
|
2828
|
+
// Resolve current context with overrides
|
|
2829
|
+
const out = await resolveAwsContext({
|
|
2830
|
+
dotenv: ctx.dotenv,
|
|
2831
|
+
cfg,
|
|
2832
|
+
});
|
|
2833
|
+
// Publish env/context
|
|
2834
|
+
applyAwsContext(out, ctx, true);
|
|
2835
|
+
// Forward when positional args are present; otherwise session-only.
|
|
2836
|
+
if (args.length > 0) {
|
|
2837
|
+
const argv = ['aws', ...args];
|
|
2838
|
+
const shellSetting = resolveShell(bag.scripts, 'aws', bag.shell);
|
|
2839
|
+
const exit = await runCommand(argv, shellSetting, {
|
|
2840
|
+
env: buildSpawnEnv(process.env, ctx.dotenv),
|
|
2841
|
+
stdio: capture ? 'pipe' : 'inherit',
|
|
2842
|
+
});
|
|
2843
|
+
// Deterministic termination (suppressed under tests)
|
|
2844
|
+
if (!underTests) {
|
|
2845
|
+
process.exit(typeof exit === 'number' ? exit : 0);
|
|
2846
|
+
}
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
// Session only: low-noise breadcrumb under debug
|
|
2850
|
+
if (process.env.GETDOTENV_DEBUG) {
|
|
2851
|
+
try {
|
|
2852
|
+
const msg = JSON.stringify({
|
|
2853
|
+
profile: out.profile,
|
|
2854
|
+
region: out.region,
|
|
2855
|
+
hasCreds: Boolean(out.credentials),
|
|
2856
|
+
});
|
|
2857
|
+
process.stderr.write(`[aws] session established ${msg}\n`);
|
|
2858
|
+
}
|
|
2859
|
+
catch {
|
|
2860
|
+
/* ignore */
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
if (!underTests)
|
|
2864
|
+
process.exit(0);
|
|
2865
|
+
});
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
/**
|
|
2869
|
+
* Attach options/arguments for the AWS plugin mount.
|
|
2870
|
+
*
|
|
2871
|
+
* @param cli - The `aws` command mount.
|
|
2872
|
+
* @param plugin - The AWS plugin instance (for dynamic option descriptions).
|
|
2873
|
+
*
|
|
2874
|
+
* @internal
|
|
2875
|
+
*/
|
|
2876
|
+
function attachAwsOptions(cli, plugin) {
|
|
2877
|
+
return (cli
|
|
2878
|
+
// Description is owned by the plugin index (src/plugins/aws/index.ts).
|
|
2879
|
+
.enablePositionalOptions()
|
|
2880
|
+
.passThroughOptions()
|
|
2881
|
+
.allowUnknownOption(true)
|
|
2882
|
+
// Boolean toggles with dynamic help labels (effective defaults)
|
|
2883
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--login-on-demand', (_bag, cfg) => `attempt aws sso login on-demand${cfg.loginOnDemand ? ' (default)' : ''}`))
|
|
2884
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--no-login-on-demand', (_bag, cfg) => `disable sso login on-demand${cfg.loginOnDemand === false ? ' (default)' : ''}`))
|
|
2885
|
+
// Strings / enums
|
|
2886
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile <string>', (_bag, cfg) => `AWS profile name${cfg.profile ? ` (default: ${JSON.stringify(cfg.profile)})` : ''}`))
|
|
2887
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--region <string>', (_bag, cfg) => `AWS region${cfg.region ? ` (default: ${JSON.stringify(cfg.region)})` : ''}`))
|
|
2888
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--default-region <string>', (_bag, cfg) => `fallback region${cfg.defaultRegion ? ` (default: ${JSON.stringify(cfg.defaultRegion)})` : ''}`))
|
|
2889
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--strategy <string>', (_bag, cfg) => `credential acquisition strategy: cli-export|none${cfg.strategy ? ` (default: ${JSON.stringify(cfg.strategy)})` : ''}`))
|
|
2890
|
+
// Advanced key overrides
|
|
2891
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile-key <string>', (_bag, cfg) => `dotenv/config key for local profile${cfg.profileKey ? ` (default: ${JSON.stringify(cfg.profileKey)})` : ''}`))
|
|
2892
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile-fallback-key <string>', (_bag, cfg) => `fallback dotenv/config key for profile${cfg.profileFallbackKey ? ` (default: ${JSON.stringify(cfg.profileFallbackKey)})` : ''}`))
|
|
2893
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--region-key <string>', (_bag, cfg) => `dotenv/config key for region${cfg.regionKey ? ` (default: ${JSON.stringify(cfg.regionKey)})` : ''}`))
|
|
2894
|
+
// Accept any extra operands so Commander does not error when tokens appear after "--".
|
|
2895
|
+
.argument('[args...]'));
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
/**
|
|
2899
|
+
* Attach the AWS plugin `preSubcommand` hook.
|
|
2900
|
+
*
|
|
2901
|
+
* Ensures `aws --profile/--region <child>` applies the AWS session setup before
|
|
2902
|
+
* child subcommand execution.
|
|
2903
|
+
*
|
|
2904
|
+
* @param cli - The `aws` command mount.
|
|
2905
|
+
* @param plugin - The AWS plugin instance.
|
|
2906
|
+
*
|
|
2907
|
+
* @internal
|
|
2908
|
+
*/
|
|
2909
|
+
function attachAwsPreSubcommandHook(cli, plugin) {
|
|
2910
|
+
cli.hook('preSubcommand', async (thisCommand) => {
|
|
2911
|
+
// Avoid side effects for help rendering.
|
|
2912
|
+
if (process.argv.includes('-h') || process.argv.includes('--help'))
|
|
2913
|
+
return;
|
|
2914
|
+
const ctx = cli.getCtx();
|
|
2915
|
+
const cfgBase = plugin.readConfig(cli);
|
|
2916
|
+
const cfg = {
|
|
2917
|
+
...cfgBase,
|
|
2918
|
+
...awsConfigOverridesFromCommandOpts(thisCommand.opts()),
|
|
2919
|
+
};
|
|
2920
|
+
const out = await resolveAwsContext({
|
|
2921
|
+
dotenv: ctx.dotenv,
|
|
2922
|
+
cfg,
|
|
2923
|
+
});
|
|
2924
|
+
applyAwsContext(out, ctx, true);
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2623
2928
|
/**
|
|
2624
2929
|
* Zod schema for AWS plugin configuration.
|
|
2625
2930
|
*/
|
|
2626
|
-
const
|
|
2931
|
+
const awsPluginConfigSchema = z.object({
|
|
2627
2932
|
profile: z.string().optional(),
|
|
2628
2933
|
region: z.string().optional(),
|
|
2629
2934
|
defaultRegion: z.string().optional(),
|
|
@@ -2647,129 +2952,16 @@ const AwsPluginConfigSchema = z.object({
|
|
|
2647
2952
|
const awsPlugin = () => {
|
|
2648
2953
|
const plugin = definePlugin({
|
|
2649
2954
|
ns: 'aws',
|
|
2650
|
-
configSchema:
|
|
2651
|
-
setup
|
|
2652
|
-
|
|
2653
|
-
cli
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
.passThroughOptions()
|
|
2657
|
-
.allowUnknownOption(true)
|
|
2658
|
-
// Boolean toggles with dynamic help labels (effective defaults)
|
|
2659
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--login-on-demand', (_bag, cfg) => `attempt aws sso login on-demand${cfg.loginOnDemand ? ' (default)' : ''}`))
|
|
2660
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--no-login-on-demand', (_bag, cfg) => `disable sso login on-demand${cfg.loginOnDemand === false ? ' (default)' : ''}`))
|
|
2661
|
-
// Strings / enums
|
|
2662
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--profile <string>', (_bag, cfg) => `AWS profile name${cfg.profile ? ` (default: ${JSON.stringify(cfg.profile)})` : ''}`))
|
|
2663
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--region <string>', (_bag, cfg) => `AWS region${cfg.region ? ` (default: ${JSON.stringify(cfg.region)})` : ''}`))
|
|
2664
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--default-region <string>', (_bag, cfg) => `fallback region${cfg.defaultRegion ? ` (default: ${JSON.stringify(cfg.defaultRegion)})` : ''}`))
|
|
2665
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--strategy <string>', (_bag, cfg) => `credential acquisition strategy: cli-export|none${cfg.strategy ? ` (default: ${JSON.stringify(cfg.strategy)})` : ''}`))
|
|
2666
|
-
// Advanced key overrides
|
|
2667
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--profile-key <string>', (_bag, cfg) => `dotenv/config key for local profile${cfg.profileKey ? ` (default: ${JSON.stringify(cfg.profileKey)})` : ''}`))
|
|
2668
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--profile-fallback-key <string>', (_bag, cfg) => `fallback dotenv/config key for profile${cfg.profileFallbackKey ? ` (default: ${JSON.stringify(cfg.profileFallbackKey)})` : ''}`))
|
|
2669
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--region-key <string>', (_bag, cfg) => `dotenv/config key for region${cfg.regionKey ? ` (default: ${JSON.stringify(cfg.regionKey)})` : ''}`))
|
|
2670
|
-
// Accept any extra operands so Commander does not error when tokens appear after "--".
|
|
2671
|
-
.argument('[args...]')
|
|
2672
|
-
.action(async (args, opts, thisCommand) => {
|
|
2673
|
-
const pluginInst = plugin;
|
|
2674
|
-
// Access merged root CLI options (installed by passOptions())
|
|
2675
|
-
const bag = readMergedOptions(thisCommand);
|
|
2676
|
-
const capture = shouldCapture(bag.capture);
|
|
2677
|
-
const underTests = process.env.GETDOTENV_TEST === '1' ||
|
|
2678
|
-
typeof process.env.VITEST_WORKER_ID === 'string';
|
|
2679
|
-
// Build overlay cfg from subcommand flags layered over discovered config.
|
|
2680
|
-
const ctx = cli.getCtx();
|
|
2681
|
-
const cfgBase = pluginInst.readConfig(cli);
|
|
2682
|
-
const o = opts;
|
|
2683
|
-
const overlay = {};
|
|
2684
|
-
// Map boolean toggles (respect explicit --no-*)
|
|
2685
|
-
if (Object.prototype.hasOwnProperty.call(o, 'loginOnDemand'))
|
|
2686
|
-
overlay.loginOnDemand = Boolean(o.loginOnDemand);
|
|
2687
|
-
// Strings/enums
|
|
2688
|
-
if (typeof o.profile === 'string')
|
|
2689
|
-
overlay.profile = o.profile;
|
|
2690
|
-
if (typeof o.region === 'string')
|
|
2691
|
-
overlay.region = o.region;
|
|
2692
|
-
if (typeof o.defaultRegion === 'string')
|
|
2693
|
-
overlay.defaultRegion = o.defaultRegion;
|
|
2694
|
-
if (typeof o.strategy === 'string')
|
|
2695
|
-
overlay.strategy = o.strategy;
|
|
2696
|
-
// Advanced key overrides
|
|
2697
|
-
if (typeof o.profileKey === 'string')
|
|
2698
|
-
overlay.profileKey = o.profileKey;
|
|
2699
|
-
if (typeof o.profileFallbackKey === 'string')
|
|
2700
|
-
overlay.profileFallbackKey = o.profileFallbackKey;
|
|
2701
|
-
if (typeof o.regionKey === 'string')
|
|
2702
|
-
overlay.regionKey = o.regionKey;
|
|
2703
|
-
const cfg = {
|
|
2704
|
-
...cfgBase,
|
|
2705
|
-
...overlay,
|
|
2706
|
-
};
|
|
2707
|
-
// Resolve current context with overrides
|
|
2708
|
-
const out = await resolveAwsContext({
|
|
2709
|
-
dotenv: ctx.dotenv,
|
|
2710
|
-
cfg,
|
|
2711
|
-
});
|
|
2712
|
-
// Publish env/context
|
|
2713
|
-
applyAwsContext(out, ctx, true);
|
|
2714
|
-
// Forward when positional args are present; otherwise session-only.
|
|
2715
|
-
if (Array.isArray(args) && args.length > 0) {
|
|
2716
|
-
const argv = ['aws', ...args];
|
|
2717
|
-
const shellSetting = resolveShell(bag.scripts, 'aws', bag.shell);
|
|
2718
|
-
const exit = await runCommand(argv, shellSetting, {
|
|
2719
|
-
env: buildSpawnEnv(process.env, ctx.dotenv),
|
|
2720
|
-
stdio: capture ? 'pipe' : 'inherit',
|
|
2721
|
-
});
|
|
2722
|
-
// Deterministic termination (suppressed under tests)
|
|
2723
|
-
if (!underTests) {
|
|
2724
|
-
process.exit(typeof exit === 'number' ? exit : 0);
|
|
2725
|
-
}
|
|
2726
|
-
return;
|
|
2727
|
-
}
|
|
2728
|
-
else {
|
|
2729
|
-
// Session only: low-noise breadcrumb under debug
|
|
2730
|
-
if (process.env.GETDOTENV_DEBUG) {
|
|
2731
|
-
try {
|
|
2732
|
-
const msg = JSON.stringify({
|
|
2733
|
-
profile: out.profile,
|
|
2734
|
-
region: out.region,
|
|
2735
|
-
hasCreds: Boolean(out.credentials),
|
|
2736
|
-
});
|
|
2737
|
-
process.stderr.write(`[aws] session established ${msg}\n`);
|
|
2738
|
-
}
|
|
2739
|
-
catch {
|
|
2740
|
-
/* ignore */
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
|
-
if (!underTests)
|
|
2744
|
-
process.exit(0);
|
|
2745
|
-
return;
|
|
2746
|
-
}
|
|
2747
|
-
});
|
|
2955
|
+
configSchema: awsPluginConfigSchema,
|
|
2956
|
+
setup(cli) {
|
|
2957
|
+
cli.description('Establish an AWS session and optionally forward to the AWS CLI');
|
|
2958
|
+
const awsCmd = attachAwsOptions(cli, plugin);
|
|
2959
|
+
attachAwsPreSubcommandHook(cli, plugin);
|
|
2960
|
+
attachAwsDefaultAction(cli, plugin, awsCmd);
|
|
2748
2961
|
return undefined;
|
|
2749
2962
|
},
|
|
2750
|
-
afterResolve: async (_cli, ctx) => {
|
|
2751
|
-
const cfg = plugin.readConfig(_cli);
|
|
2752
|
-
const out = await resolveAwsContext({
|
|
2753
|
-
dotenv: ctx.dotenv,
|
|
2754
|
-
cfg,
|
|
2755
|
-
});
|
|
2756
|
-
applyAwsContext(out, ctx, true);
|
|
2757
|
-
// Optional: low-noise breadcrumb for diagnostics
|
|
2758
|
-
if (process.env.GETDOTENV_DEBUG) {
|
|
2759
|
-
try {
|
|
2760
|
-
const msg = JSON.stringify({
|
|
2761
|
-
profile: out.profile,
|
|
2762
|
-
region: out.region,
|
|
2763
|
-
hasCreds: Boolean(out.credentials),
|
|
2764
|
-
});
|
|
2765
|
-
process.stderr.write(`[aws] afterResolve ${msg}\n`);
|
|
2766
|
-
}
|
|
2767
|
-
catch {
|
|
2768
|
-
/* ignore */
|
|
2769
|
-
}
|
|
2770
|
-
}
|
|
2771
|
-
},
|
|
2772
2963
|
});
|
|
2964
|
+
plugin.afterResolve = attachAwsAfterResolveHook(plugin);
|
|
2773
2965
|
return plugin;
|
|
2774
2966
|
};
|
|
2775
2967
|
|