@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/index.mjs
CHANGED
|
@@ -170,6 +170,10 @@ function defaultsDeep(...layers) {
|
|
|
170
170
|
/**
|
|
171
171
|
* Serialize a dotenv record to a file with minimal quoting (multiline values are quoted).
|
|
172
172
|
* Future-proofs for ordering/sorting changes (currently insertion order).
|
|
173
|
+
*
|
|
174
|
+
* @param filename - Destination dotenv file path.
|
|
175
|
+
* @param data - Env-like map of values to write (values may be `undefined`).
|
|
176
|
+
* @returns A `Promise\<void\>` which resolves when the file has been written.
|
|
173
177
|
*/
|
|
174
178
|
async function writeDotenvFile(filename, data) {
|
|
175
179
|
// Serialize: key=value with quotes only for multiline values.
|
|
@@ -405,14 +409,20 @@ const cleanupOldCacheFiles = async (cacheDir, baseName, keep = Math.max(1, Numbe
|
|
|
405
409
|
}
|
|
406
410
|
};
|
|
407
411
|
/**
|
|
408
|
-
* Load a module default export from a JS/TS file with robust fallbacks
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
412
|
+
* Load a module default export from a JS/TS file with robust fallbacks.
|
|
413
|
+
*
|
|
414
|
+
* Behavior by extension:
|
|
415
|
+
*
|
|
416
|
+
* - `.js`/`.mjs`/`.cjs`: direct dynamic import.
|
|
417
|
+
* - `.ts`/`.mts`/`.cts`/`.tsx`:
|
|
418
|
+
* - try direct dynamic import (when a TS loader is active),
|
|
419
|
+
* - else compile via `esbuild` to a cached `.mjs` file and import,
|
|
420
|
+
* - else fallback to `typescript.transpileModule` for simple modules.
|
|
413
421
|
*
|
|
414
|
-
* @
|
|
415
|
-
* @param
|
|
422
|
+
* @typeParam T - Type of the expected default export.
|
|
423
|
+
* @param absPath - Absolute path to the source file.
|
|
424
|
+
* @param cacheDirName - Cache subfolder under `.tsbuild/`.
|
|
425
|
+
* @returns A `Promise\<T | undefined\>` resolving to the default export (if any).
|
|
416
426
|
*/
|
|
417
427
|
const loadModuleDefault = async (absPath, cacheDirName) => {
|
|
418
428
|
const ext = path.extname(absPath).toLowerCase();
|
|
@@ -484,6 +494,10 @@ const loadModuleDefault = async (absPath, cacheDirName) => {
|
|
|
484
494
|
/**
|
|
485
495
|
* Omit keys whose runtime value is undefined from a shallow object.
|
|
486
496
|
* Returns a Partial with non-undefined value types preserved.
|
|
497
|
+
*
|
|
498
|
+
* @typeParam T - Input object shape.
|
|
499
|
+
* @param obj - Object to filter.
|
|
500
|
+
* @returns A shallow copy of `obj` without keys whose value is `undefined`.
|
|
487
501
|
*/
|
|
488
502
|
function omitUndefined(obj) {
|
|
489
503
|
const out = {};
|
|
@@ -495,6 +509,10 @@ function omitUndefined(obj) {
|
|
|
495
509
|
}
|
|
496
510
|
/**
|
|
497
511
|
* Specialized helper for env-like maps: drop undefined and return string-only.
|
|
512
|
+
*
|
|
513
|
+
* @typeParam V - Value type for present entries (must extend `string`).
|
|
514
|
+
* @param obj - Env-like record containing `string | undefined` values.
|
|
515
|
+
* @returns A new record containing only the keys with defined values.
|
|
498
516
|
*/
|
|
499
517
|
function omitUndefinedRecord(obj) {
|
|
500
518
|
const out = {};
|
|
@@ -712,6 +730,10 @@ const resolveGetDotenvConfigSources = async (importMetaUrl) => {
|
|
|
712
730
|
* - If a JS/TS `schema` is present, use schema.safeParse(finalEnv).
|
|
713
731
|
* - Else if `requiredKeys` is present, check presence (value !== undefined).
|
|
714
732
|
* - Returns a flat list of issue strings; caller decides warn vs fail.
|
|
733
|
+
*
|
|
734
|
+
* @param finalEnv - Final composed environment to validate.
|
|
735
|
+
* @param sources - Resolved config sources providing `schema` and/or `requiredKeys`.
|
|
736
|
+
* @returns A list of human-readable issue strings (empty when valid).
|
|
715
737
|
*/
|
|
716
738
|
const validateEnvAgainstSources = (finalEnv, sources) => {
|
|
717
739
|
const pick = (getter) => {
|
|
@@ -771,6 +793,11 @@ const validateEnvAgainstSources = (finalEnv, sources) => {
|
|
|
771
793
|
* Apply a dynamic map to the target progressively.
|
|
772
794
|
* - Functions receive (target, env) and may return string | undefined.
|
|
773
795
|
* - Literals are assigned directly (including undefined).
|
|
796
|
+
*
|
|
797
|
+
* @param target - Mutable target environment to assign into.
|
|
798
|
+
* @param map - Dynamic map to apply (functions and/or literal values).
|
|
799
|
+
* @param env - Selected environment name (if any) passed through to dynamic functions.
|
|
800
|
+
* @returns Nothing.
|
|
774
801
|
*/
|
|
775
802
|
function applyDynamicMap(target, map, env) {
|
|
776
803
|
if (!map)
|
|
@@ -790,6 +817,12 @@ function applyDynamicMap(target, map, env) {
|
|
|
790
817
|
* Error behavior:
|
|
791
818
|
* - On failure to load/compile/evaluate the module, throws a unified message:
|
|
792
819
|
* "Unable to load dynamic TypeScript file: <absPath>. Install 'esbuild'..."
|
|
820
|
+
*
|
|
821
|
+
* @param target - Mutable target environment to assign into.
|
|
822
|
+
* @param absPath - Absolute path to the dynamic module file.
|
|
823
|
+
* @param env - Selected environment name (if any).
|
|
824
|
+
* @param cacheDirName - Cache subdirectory under `.tsbuild/` for compiled artifacts.
|
|
825
|
+
* @returns A `Promise\<void\>` which resolves after the module (if present) has been applied.
|
|
793
826
|
*/
|
|
794
827
|
async function loadAndApplyDynamic(target, absPath, env, cacheDirName) {
|
|
795
828
|
if (!(await fs.exists(absPath)))
|
|
@@ -896,6 +929,18 @@ const DEFAULT_PATTERNS = [
|
|
|
896
929
|
const compile = (patterns) => (patterns && patterns.length > 0 ? patterns : DEFAULT_PATTERNS).map((p) => typeof p === 'string' ? new RegExp(p, 'i') : p);
|
|
897
930
|
const shouldRedactKey = (key, regs) => regs.some((re) => re.test(key));
|
|
898
931
|
const MASK = '[redacted]';
|
|
932
|
+
/**
|
|
933
|
+
* Redact a single displayed value according to key/patterns.
|
|
934
|
+
* Returns the original value when redaction is disabled or key is not matched.
|
|
935
|
+
*/
|
|
936
|
+
const redactDisplay = (key, value, opts) => {
|
|
937
|
+
if (!value)
|
|
938
|
+
return value;
|
|
939
|
+
if (!opts?.redact)
|
|
940
|
+
return value;
|
|
941
|
+
const regs = compile(opts.redactPatterns);
|
|
942
|
+
return shouldRedactKey(key, regs) ? MASK : value;
|
|
943
|
+
};
|
|
899
944
|
/**
|
|
900
945
|
* Produce a shallow redacted copy of an env-like object for display.
|
|
901
946
|
*/
|
|
@@ -1973,6 +2018,10 @@ function renderOptionGroups(cmd) {
|
|
|
1973
2018
|
/**
|
|
1974
2019
|
* Compose root/parent help output by inserting grouped sections between
|
|
1975
2020
|
* Options and Commands, ensuring a trailing blank line.
|
|
2021
|
+
*
|
|
2022
|
+
* @param base - Base help text produced by Commander.
|
|
2023
|
+
* @param cmd - Command instance whose grouped options should be rendered.
|
|
2024
|
+
* @returns The modified help text with grouped blocks inserted.
|
|
1976
2025
|
*/
|
|
1977
2026
|
function buildHelpInformation(base, cmd) {
|
|
1978
2027
|
const groups = renderOptionGroups(cmd);
|
|
@@ -2485,6 +2534,10 @@ const toHelpConfig = (merged, plugins) => {
|
|
|
2485
2534
|
/**
|
|
2486
2535
|
* Compose a child-process env overlay from dotenv and the merged CLI options bag.
|
|
2487
2536
|
* Returns a shallow object including getDotenvCliOptions when serializable.
|
|
2537
|
+
*
|
|
2538
|
+
* @param merged - Resolved CLI options bag (or a JSON-serializable subset).
|
|
2539
|
+
* @param dotenv - Composed dotenv variables for the current invocation.
|
|
2540
|
+
* @returns A string-only env overlay suitable for child process spawning.
|
|
2488
2541
|
*/
|
|
2489
2542
|
function composeNestedEnv(merged, dotenv) {
|
|
2490
2543
|
const out = {};
|
|
@@ -2507,6 +2560,7 @@ function composeNestedEnv(merged, dotenv) {
|
|
|
2507
2560
|
* Strip one layer of symmetric outer quotes (single or double) from a string.
|
|
2508
2561
|
*
|
|
2509
2562
|
* @param s - Input string.
|
|
2563
|
+
* @returns `s` without one symmetric outer quote pair (when present).
|
|
2510
2564
|
*/
|
|
2511
2565
|
const stripOne = (s) => {
|
|
2512
2566
|
if (s.length < 2)
|
|
@@ -2519,6 +2573,9 @@ const stripOne = (s) => {
|
|
|
2519
2573
|
/**
|
|
2520
2574
|
* Preserve argv array for Node -e/--eval payloads under shell-off and
|
|
2521
2575
|
* peel one symmetric outer quote layer from the code argument.
|
|
2576
|
+
*
|
|
2577
|
+
* @param args - Argument vector intended for direct execution (shell-off).
|
|
2578
|
+
* @returns Either the original `args` or a modified copy with a normalized eval payload.
|
|
2522
2579
|
*/
|
|
2523
2580
|
function maybePreserveNodeEvalArgv(args) {
|
|
2524
2581
|
if (args.length >= 3) {
|
|
@@ -2991,23 +3048,57 @@ function applyRootVisibility(program, visibility) {
|
|
|
2991
3048
|
* Apply resolved AWS context to `process.env` and `ctx.plugins`.
|
|
2992
3049
|
* Centralizes logic shared between the plugin action and `afterResolve` hook.
|
|
2993
3050
|
*
|
|
3051
|
+
* @param out - Resolved AWS context to apply.
|
|
3052
|
+
* @param ctx - Host context to publish non-sensitive metadata into.
|
|
2994
3053
|
* @param setProcessEnv - Whether to write credentials/region to `process.env` (default true).
|
|
3054
|
+
* @returns Nothing.
|
|
2995
3055
|
*/
|
|
2996
3056
|
function applyAwsContext(out, ctx, setProcessEnv = true) {
|
|
2997
3057
|
const { profile, region, credentials } = out;
|
|
2998
3058
|
if (setProcessEnv) {
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3059
|
+
// Ensure AWS credential sources are mutually exclusive.
|
|
3060
|
+
// The AWS SDK warns (and may change precedence in future) when both
|
|
3061
|
+
// AWS_PROFILE and AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY are set.
|
|
3062
|
+
const clear = (keys) => {
|
|
3063
|
+
for (const k of keys) {
|
|
3064
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
3065
|
+
delete process.env[k];
|
|
3003
3066
|
}
|
|
3004
|
-
}
|
|
3067
|
+
};
|
|
3068
|
+
const clearProfileVars = () => {
|
|
3069
|
+
clear(['AWS_PROFILE', 'AWS_DEFAULT_PROFILE', 'AWS_SDK_LOAD_CONFIG']);
|
|
3070
|
+
};
|
|
3071
|
+
const clearStaticCreds = () => {
|
|
3072
|
+
clear([
|
|
3073
|
+
'AWS_ACCESS_KEY_ID',
|
|
3074
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
3075
|
+
'AWS_SESSION_TOKEN',
|
|
3076
|
+
]);
|
|
3077
|
+
};
|
|
3078
|
+
// Mode A: exported/static credentials (clear profile vars)
|
|
3005
3079
|
if (credentials) {
|
|
3080
|
+
clearProfileVars();
|
|
3006
3081
|
process.env.AWS_ACCESS_KEY_ID = credentials.accessKeyId;
|
|
3007
3082
|
process.env.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey;
|
|
3008
3083
|
if (credentials.sessionToken !== undefined) {
|
|
3009
3084
|
process.env.AWS_SESSION_TOKEN = credentials.sessionToken;
|
|
3010
3085
|
}
|
|
3086
|
+
else {
|
|
3087
|
+
delete process.env.AWS_SESSION_TOKEN;
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
else if (profile) {
|
|
3091
|
+
// Mode B: profile-based (SSO) credentials (clear static creds)
|
|
3092
|
+
clearStaticCreds();
|
|
3093
|
+
process.env.AWS_PROFILE = profile;
|
|
3094
|
+
process.env.AWS_DEFAULT_PROFILE = profile;
|
|
3095
|
+
process.env.AWS_SDK_LOAD_CONFIG = '1';
|
|
3096
|
+
}
|
|
3097
|
+
if (region) {
|
|
3098
|
+
process.env.AWS_REGION = region;
|
|
3099
|
+
if (!process.env.AWS_DEFAULT_REGION) {
|
|
3100
|
+
process.env.AWS_DEFAULT_REGION = region;
|
|
3101
|
+
}
|
|
3011
3102
|
}
|
|
3012
3103
|
}
|
|
3013
3104
|
// Always publish minimal, non-sensitive metadata
|
|
@@ -3018,7 +3109,7 @@ function applyAwsContext(out, ctx, setProcessEnv = true) {
|
|
|
3018
3109
|
};
|
|
3019
3110
|
}
|
|
3020
3111
|
|
|
3021
|
-
const
|
|
3112
|
+
const AWS_CLI_TIMEOUT_MS = 15_000;
|
|
3022
3113
|
const trim = (s) => (typeof s === 'string' ? s.trim() : '');
|
|
3023
3114
|
const unquote = (s) => s.length >= 2 &&
|
|
3024
3115
|
((s.startsWith('"') && s.endsWith('"')) ||
|
|
@@ -3027,6 +3118,9 @@ const unquote = (s) => s.length >= 2 &&
|
|
|
3027
3118
|
: s;
|
|
3028
3119
|
/**
|
|
3029
3120
|
* Parse AWS credentials from JSON output (AWS CLI v2 export-credentials).
|
|
3121
|
+
*
|
|
3122
|
+
* @param txt - Raw stdout text from the AWS CLI.
|
|
3123
|
+
* @returns Parsed credentials, or `undefined` when the input is not recognized.
|
|
3030
3124
|
*/
|
|
3031
3125
|
const parseExportCredentialsJson = (txt) => {
|
|
3032
3126
|
try {
|
|
@@ -3050,6 +3144,10 @@ const parseExportCredentialsJson = (txt) => {
|
|
|
3050
3144
|
/**
|
|
3051
3145
|
* Parse AWS credentials from environment-export output (shell-agnostic).
|
|
3052
3146
|
* Supports POSIX `export KEY=VAL` and PowerShell `$Env:KEY=VAL`.
|
|
3147
|
+
* Also supports AWS CLI `windows-cmd` (`set KEY=VAL`) and `env-no-export` (`KEY=VAL`).
|
|
3148
|
+
*
|
|
3149
|
+
* @param txt - Raw stdout text from the AWS CLI.
|
|
3150
|
+
* @returns Parsed credentials, or `undefined` when the input is not recognized.
|
|
3053
3151
|
*/
|
|
3054
3152
|
const parseExportCredentialsEnv = (txt) => {
|
|
3055
3153
|
const lines = txt.split(/\r?\n/);
|
|
@@ -3060,12 +3158,17 @@ const parseExportCredentialsEnv = (txt) => {
|
|
|
3060
3158
|
const line = raw.trim();
|
|
3061
3159
|
if (!line)
|
|
3062
3160
|
continue;
|
|
3063
|
-
// POSIX: export AWS_ACCESS_KEY_ID=...,
|
|
3161
|
+
// POSIX: export AWS_ACCESS_KEY_ID=..., ...
|
|
3064
3162
|
let m = /^export\s+([A-Z0-9_]+)\s*=\s*(.+)$/.exec(line);
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
m = /^\$Env:([A-Z0-9_]+)\s*=\s*(.+)
|
|
3068
|
-
|
|
3163
|
+
// PowerShell: $Env:AWS_ACCESS_KEY_ID="...", etc.
|
|
3164
|
+
if (!m)
|
|
3165
|
+
m = /^\$Env:([A-Z0-9_]+)\s*=\s*(.+)$/i.exec(line);
|
|
3166
|
+
// Windows cmd: set AWS_ACCESS_KEY_ID=..., etc.
|
|
3167
|
+
if (!m)
|
|
3168
|
+
m = /^(?:set)\s+([A-Z0-9_]+)\s*=\s*(.+)$/i.exec(line);
|
|
3169
|
+
// env-no-export: AWS_ACCESS_KEY_ID=..., etc.
|
|
3170
|
+
if (!m)
|
|
3171
|
+
m = /^([A-Z0-9_]+)\s*=\s*(.+)$/.exec(line);
|
|
3069
3172
|
if (!m)
|
|
3070
3173
|
continue;
|
|
3071
3174
|
const k = m[1];
|
|
@@ -3090,7 +3193,7 @@ const parseExportCredentialsEnv = (txt) => {
|
|
|
3090
3193
|
};
|
|
3091
3194
|
return undefined;
|
|
3092
3195
|
};
|
|
3093
|
-
const getAwsConfigure = async (key, profile, timeoutMs =
|
|
3196
|
+
const getAwsConfigure = async (key, profile, timeoutMs = AWS_CLI_TIMEOUT_MS) => {
|
|
3094
3197
|
const r = await runCommandResult(['aws', 'configure', 'get', key, '--profile', profile], false, {
|
|
3095
3198
|
env: process.env,
|
|
3096
3199
|
timeoutMs,
|
|
@@ -3105,36 +3208,50 @@ const getAwsConfigure = async (key, profile, timeoutMs = DEFAULT_TIMEOUT_MS) =>
|
|
|
3105
3208
|
}
|
|
3106
3209
|
return undefined;
|
|
3107
3210
|
};
|
|
3108
|
-
const exportCredentials = async (profile, timeoutMs =
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3211
|
+
const exportCredentials = async (profile, timeoutMs = AWS_CLI_TIMEOUT_MS) => {
|
|
3212
|
+
const tryExport = async (format) => {
|
|
3213
|
+
const argv = [
|
|
3214
|
+
'aws',
|
|
3215
|
+
'configure',
|
|
3216
|
+
'export-credentials',
|
|
3217
|
+
'--profile',
|
|
3218
|
+
profile,
|
|
3219
|
+
...(format ? ['--format', format] : []),
|
|
3220
|
+
];
|
|
3221
|
+
const r = await runCommandResult(argv, false, {
|
|
3222
|
+
env: process.env,
|
|
3223
|
+
timeoutMs,
|
|
3224
|
+
});
|
|
3225
|
+
if (r.exitCode !== 0)
|
|
3226
|
+
return undefined;
|
|
3227
|
+
const out = trim(r.stdout);
|
|
3228
|
+
if (!out)
|
|
3229
|
+
return undefined;
|
|
3230
|
+
// Some formats produce JSON ("process"), some produce shell-ish env lines.
|
|
3231
|
+
return parseExportCredentialsJson(out) ?? parseExportCredentialsEnv(out);
|
|
3232
|
+
};
|
|
3233
|
+
// Prefer the default/JSON "process" format first; then fall back to shell env outputs.
|
|
3234
|
+
// Note: AWS CLI v2 supports: process | env | env-no-export | powershell | windows-cmd
|
|
3235
|
+
const formats = [
|
|
3236
|
+
'process',
|
|
3237
|
+
...(process.platform === 'win32'
|
|
3238
|
+
? ['powershell', 'windows-cmd', 'env', 'env-no-export']
|
|
3239
|
+
: ['env', 'env-no-export']),
|
|
3240
|
+
];
|
|
3241
|
+
for (const f of formats) {
|
|
3242
|
+
const creds = await tryExport(f);
|
|
3128
3243
|
if (creds)
|
|
3129
3244
|
return creds;
|
|
3130
3245
|
}
|
|
3131
|
-
|
|
3246
|
+
// Final fallback: no --format (AWS CLI default output)
|
|
3247
|
+
return tryExport(undefined);
|
|
3132
3248
|
};
|
|
3133
3249
|
/**
|
|
3134
3250
|
* Resolve AWS context (profile, region, credentials) using configuration and environment.
|
|
3135
3251
|
* Applies strategy (cli-export vs none) and handling for SSO login-on-demand.
|
|
3136
3252
|
*
|
|
3137
3253
|
* @param options - Context options including current dotenv and plugin config.
|
|
3254
|
+
* @returns A `Promise\<AwsContext\>` containing any resolved profile, region, and credentials.
|
|
3138
3255
|
*/
|
|
3139
3256
|
const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
3140
3257
|
const profileKey = cfg.profileKey ?? 'AWS_LOCAL_PROFILE';
|
|
@@ -3159,31 +3276,27 @@ const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
|
3159
3276
|
out.region = region;
|
|
3160
3277
|
return out;
|
|
3161
3278
|
}
|
|
3162
|
-
// Env-first credentials.
|
|
3163
3279
|
let credentials;
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
const envToken = trim(process.env.AWS_SESSION_TOKEN);
|
|
3167
|
-
if (envId && envSecret) {
|
|
3168
|
-
credentials = {
|
|
3169
|
-
accessKeyId: envId,
|
|
3170
|
-
secretAccessKey: envSecret,
|
|
3171
|
-
...(envToken ? { sessionToken: envToken } : {}),
|
|
3172
|
-
};
|
|
3173
|
-
}
|
|
3174
|
-
else if (profile) {
|
|
3280
|
+
// Profile wins over ambient env creds when present (from flags/config/dotenv).
|
|
3281
|
+
if (profile) {
|
|
3175
3282
|
// Try export-credentials
|
|
3176
3283
|
credentials = await exportCredentials(profile);
|
|
3177
3284
|
// On failure, detect SSO and optionally login then retry
|
|
3178
3285
|
if (!credentials) {
|
|
3179
3286
|
const ssoSession = await getAwsConfigure('sso_session', profile);
|
|
3180
|
-
|
|
3287
|
+
// Legacy SSO profiles use sso_start_url/sso_region rather than sso_session.
|
|
3288
|
+
const ssoStartUrl = await getAwsConfigure('sso_start_url', profile);
|
|
3289
|
+
const looksSSO = (typeof ssoSession === 'string' && ssoSession.length > 0) ||
|
|
3290
|
+
(typeof ssoStartUrl === 'string' && ssoStartUrl.length > 0);
|
|
3181
3291
|
if (looksSSO && cfg.loginOnDemand) {
|
|
3182
|
-
//
|
|
3183
|
-
await
|
|
3292
|
+
// Interactive login (no timeout by default), then retry export once.
|
|
3293
|
+
const exit = await runCommand(['aws', 'sso', 'login', '--profile', profile], false, {
|
|
3184
3294
|
env: process.env,
|
|
3185
|
-
|
|
3295
|
+
stdio: 'inherit',
|
|
3186
3296
|
});
|
|
3297
|
+
if (exit !== 0) {
|
|
3298
|
+
throw new Error(`aws sso login failed for profile '${profile}' (exit ${String(exit)})`);
|
|
3299
|
+
}
|
|
3187
3300
|
credentials = await exportCredentials(profile);
|
|
3188
3301
|
}
|
|
3189
3302
|
}
|
|
@@ -3201,6 +3314,19 @@ const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
|
3201
3314
|
}
|
|
3202
3315
|
}
|
|
3203
3316
|
}
|
|
3317
|
+
else {
|
|
3318
|
+
// Env-first credentials when no profile is present.
|
|
3319
|
+
const envId = trim(process.env.AWS_ACCESS_KEY_ID);
|
|
3320
|
+
const envSecret = trim(process.env.AWS_SECRET_ACCESS_KEY);
|
|
3321
|
+
const envToken = trim(process.env.AWS_SESSION_TOKEN);
|
|
3322
|
+
if (envId && envSecret) {
|
|
3323
|
+
credentials = {
|
|
3324
|
+
accessKeyId: envId,
|
|
3325
|
+
secretAccessKey: envSecret,
|
|
3326
|
+
...(envToken ? { sessionToken: envToken } : {}),
|
|
3327
|
+
};
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3204
3330
|
// Final region resolution
|
|
3205
3331
|
if (!region && profile)
|
|
3206
3332
|
region = await getAwsConfigure('region', profile);
|
|
@@ -3216,10 +3342,213 @@ const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
|
3216
3342
|
return out;
|
|
3217
3343
|
};
|
|
3218
3344
|
|
|
3345
|
+
/**
|
|
3346
|
+
* Create the AWS plugin `afterResolve` hook.
|
|
3347
|
+
*
|
|
3348
|
+
* This runs once per invocation after the host resolves dotenv context.
|
|
3349
|
+
*
|
|
3350
|
+
* @param plugin - The AWS plugin instance.
|
|
3351
|
+
* @returns An `afterResolve` hook function suitable for assigning to `plugin.afterResolve`.
|
|
3352
|
+
*
|
|
3353
|
+
* @internal
|
|
3354
|
+
*/
|
|
3355
|
+
function attachAwsAfterResolveHook(plugin) {
|
|
3356
|
+
return async (cli, ctx) => {
|
|
3357
|
+
const cfg = plugin.readConfig(cli);
|
|
3358
|
+
const out = await resolveAwsContext({
|
|
3359
|
+
dotenv: ctx.dotenv,
|
|
3360
|
+
cfg,
|
|
3361
|
+
});
|
|
3362
|
+
applyAwsContext(out, ctx, true);
|
|
3363
|
+
// Optional: low-noise breadcrumb for diagnostics
|
|
3364
|
+
if (process.env.GETDOTENV_DEBUG) {
|
|
3365
|
+
try {
|
|
3366
|
+
const msg = JSON.stringify({
|
|
3367
|
+
profile: out.profile,
|
|
3368
|
+
region: out.region,
|
|
3369
|
+
hasCreds: Boolean(out.credentials),
|
|
3370
|
+
});
|
|
3371
|
+
process.stderr.write(`[aws] afterResolve ${msg}\n`);
|
|
3372
|
+
}
|
|
3373
|
+
catch {
|
|
3374
|
+
/* ignore */
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
};
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
/** @internal */
|
|
3381
|
+
const isRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
3382
|
+
/**
|
|
3383
|
+
* Create an AWS plugin config overlay from Commander-parsed option values.
|
|
3384
|
+
*
|
|
3385
|
+
* This preserves tri-state intent:
|
|
3386
|
+
* - If a flag was not provided, it should not overwrite config-derived defaults.
|
|
3387
|
+
* - If `--no-…` was provided, it must explicitly force the boolean false.
|
|
3388
|
+
*
|
|
3389
|
+
* @param opts - Commander option values for the current invocation.
|
|
3390
|
+
* @returns A partial AWS plugin config object containing only explicit overrides.
|
|
3391
|
+
*
|
|
3392
|
+
* @internal
|
|
3393
|
+
*/
|
|
3394
|
+
function awsConfigOverridesFromCommandOpts(opts) {
|
|
3395
|
+
const o = isRecord(opts) ? opts : {};
|
|
3396
|
+
const overlay = {};
|
|
3397
|
+
// Map boolean toggles (respect explicit --no-*)
|
|
3398
|
+
if (Object.prototype.hasOwnProperty.call(o, 'loginOnDemand')) {
|
|
3399
|
+
overlay.loginOnDemand = Boolean(o.loginOnDemand);
|
|
3400
|
+
}
|
|
3401
|
+
// Strings/enums
|
|
3402
|
+
if (typeof o.profile === 'string')
|
|
3403
|
+
overlay.profile = o.profile;
|
|
3404
|
+
if (typeof o.region === 'string')
|
|
3405
|
+
overlay.region = o.region;
|
|
3406
|
+
if (typeof o.defaultRegion === 'string')
|
|
3407
|
+
overlay.defaultRegion = o.defaultRegion;
|
|
3408
|
+
if (o.strategy === 'cli-export' || o.strategy === 'none') {
|
|
3409
|
+
overlay.strategy = o.strategy;
|
|
3410
|
+
}
|
|
3411
|
+
// Advanced key overrides
|
|
3412
|
+
if (typeof o.profileKey === 'string')
|
|
3413
|
+
overlay.profileKey = o.profileKey;
|
|
3414
|
+
if (typeof o.profileFallbackKey === 'string') {
|
|
3415
|
+
overlay.profileFallbackKey = o.profileFallbackKey;
|
|
3416
|
+
}
|
|
3417
|
+
if (typeof o.regionKey === 'string')
|
|
3418
|
+
overlay.regionKey = o.regionKey;
|
|
3419
|
+
return overlay;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
/**
|
|
3423
|
+
* Attach the default action for the AWS plugin mount.
|
|
3424
|
+
*
|
|
3425
|
+
* Behavior:
|
|
3426
|
+
* - With args: forwards to AWS CLI (`aws <args...>`) under the established session.
|
|
3427
|
+
* - Without args: session-only establishment (no forward).
|
|
3428
|
+
*
|
|
3429
|
+
* @param cli - The `aws` command mount.
|
|
3430
|
+
* @param plugin - The AWS plugin instance.
|
|
3431
|
+
*
|
|
3432
|
+
* @internal
|
|
3433
|
+
*/
|
|
3434
|
+
function attachAwsDefaultAction(cli, plugin, awsCmd) {
|
|
3435
|
+
awsCmd.action(async (args, opts, thisCommand) => {
|
|
3436
|
+
// Access merged root CLI options (installed by root hooks).
|
|
3437
|
+
const bag = readMergedOptions(thisCommand);
|
|
3438
|
+
const capture = shouldCapture(bag.capture);
|
|
3439
|
+
const underTests = process.env.GETDOTENV_TEST === '1' ||
|
|
3440
|
+
typeof process.env.VITEST_WORKER_ID === 'string';
|
|
3441
|
+
// Build overlay cfg from subcommand flags layered over discovered config.
|
|
3442
|
+
const ctx = cli.getCtx();
|
|
3443
|
+
const cfgBase = plugin.readConfig(cli);
|
|
3444
|
+
const cfg = {
|
|
3445
|
+
...cfgBase,
|
|
3446
|
+
...awsConfigOverridesFromCommandOpts(opts),
|
|
3447
|
+
};
|
|
3448
|
+
// Resolve current context with overrides
|
|
3449
|
+
const out = await resolveAwsContext({
|
|
3450
|
+
dotenv: ctx.dotenv,
|
|
3451
|
+
cfg,
|
|
3452
|
+
});
|
|
3453
|
+
// Publish env/context
|
|
3454
|
+
applyAwsContext(out, ctx, true);
|
|
3455
|
+
// Forward when positional args are present; otherwise session-only.
|
|
3456
|
+
if (args.length > 0) {
|
|
3457
|
+
const argv = ['aws', ...args];
|
|
3458
|
+
const shellSetting = resolveShell(bag.scripts, 'aws', bag.shell);
|
|
3459
|
+
const exit = await runCommand(argv, shellSetting, {
|
|
3460
|
+
env: buildSpawnEnv(process.env, ctx.dotenv),
|
|
3461
|
+
stdio: capture ? 'pipe' : 'inherit',
|
|
3462
|
+
});
|
|
3463
|
+
// Deterministic termination (suppressed under tests)
|
|
3464
|
+
if (!underTests) {
|
|
3465
|
+
process.exit(typeof exit === 'number' ? exit : 0);
|
|
3466
|
+
}
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
// Session only: low-noise breadcrumb under debug
|
|
3470
|
+
if (process.env.GETDOTENV_DEBUG) {
|
|
3471
|
+
try {
|
|
3472
|
+
const msg = JSON.stringify({
|
|
3473
|
+
profile: out.profile,
|
|
3474
|
+
region: out.region,
|
|
3475
|
+
hasCreds: Boolean(out.credentials),
|
|
3476
|
+
});
|
|
3477
|
+
process.stderr.write(`[aws] session established ${msg}\n`);
|
|
3478
|
+
}
|
|
3479
|
+
catch {
|
|
3480
|
+
/* ignore */
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
if (!underTests)
|
|
3484
|
+
process.exit(0);
|
|
3485
|
+
});
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
/**
|
|
3489
|
+
* Attach options/arguments for the AWS plugin mount.
|
|
3490
|
+
*
|
|
3491
|
+
* @param cli - The `aws` command mount.
|
|
3492
|
+
* @param plugin - The AWS plugin instance (for dynamic option descriptions).
|
|
3493
|
+
*
|
|
3494
|
+
* @internal
|
|
3495
|
+
*/
|
|
3496
|
+
function attachAwsOptions(cli, plugin) {
|
|
3497
|
+
return (cli
|
|
3498
|
+
// Description is owned by the plugin index (src/plugins/aws/index.ts).
|
|
3499
|
+
.enablePositionalOptions()
|
|
3500
|
+
.passThroughOptions()
|
|
3501
|
+
.allowUnknownOption(true)
|
|
3502
|
+
// Boolean toggles with dynamic help labels (effective defaults)
|
|
3503
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--login-on-demand', (_bag, cfg) => `attempt aws sso login on-demand${cfg.loginOnDemand ? ' (default)' : ''}`))
|
|
3504
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--no-login-on-demand', (_bag, cfg) => `disable sso login on-demand${cfg.loginOnDemand === false ? ' (default)' : ''}`))
|
|
3505
|
+
// Strings / enums
|
|
3506
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile <string>', (_bag, cfg) => `AWS profile name${cfg.profile ? ` (default: ${JSON.stringify(cfg.profile)})` : ''}`))
|
|
3507
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--region <string>', (_bag, cfg) => `AWS region${cfg.region ? ` (default: ${JSON.stringify(cfg.region)})` : ''}`))
|
|
3508
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--default-region <string>', (_bag, cfg) => `fallback region${cfg.defaultRegion ? ` (default: ${JSON.stringify(cfg.defaultRegion)})` : ''}`))
|
|
3509
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--strategy <string>', (_bag, cfg) => `credential acquisition strategy: cli-export|none${cfg.strategy ? ` (default: ${JSON.stringify(cfg.strategy)})` : ''}`))
|
|
3510
|
+
// Advanced key overrides
|
|
3511
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile-key <string>', (_bag, cfg) => `dotenv/config key for local profile${cfg.profileKey ? ` (default: ${JSON.stringify(cfg.profileKey)})` : ''}`))
|
|
3512
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile-fallback-key <string>', (_bag, cfg) => `fallback dotenv/config key for profile${cfg.profileFallbackKey ? ` (default: ${JSON.stringify(cfg.profileFallbackKey)})` : ''}`))
|
|
3513
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--region-key <string>', (_bag, cfg) => `dotenv/config key for region${cfg.regionKey ? ` (default: ${JSON.stringify(cfg.regionKey)})` : ''}`))
|
|
3514
|
+
// Accept any extra operands so Commander does not error when tokens appear after "--".
|
|
3515
|
+
.argument('[args...]'));
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
/**
|
|
3519
|
+
* Attach the AWS plugin `preSubcommand` hook.
|
|
3520
|
+
*
|
|
3521
|
+
* Ensures `aws --profile/--region <child>` applies the AWS session setup before
|
|
3522
|
+
* child subcommand execution.
|
|
3523
|
+
*
|
|
3524
|
+
* @param cli - The `aws` command mount.
|
|
3525
|
+
* @param plugin - The AWS plugin instance.
|
|
3526
|
+
*
|
|
3527
|
+
* @internal
|
|
3528
|
+
*/
|
|
3529
|
+
function attachAwsPreSubcommandHook(cli, plugin) {
|
|
3530
|
+
cli.hook('preSubcommand', async (thisCommand) => {
|
|
3531
|
+
// Avoid side effects for help rendering.
|
|
3532
|
+
if (process.argv.includes('-h') || process.argv.includes('--help'))
|
|
3533
|
+
return;
|
|
3534
|
+
const ctx = cli.getCtx();
|
|
3535
|
+
const cfgBase = plugin.readConfig(cli);
|
|
3536
|
+
const cfg = {
|
|
3537
|
+
...cfgBase,
|
|
3538
|
+
...awsConfigOverridesFromCommandOpts(thisCommand.opts()),
|
|
3539
|
+
};
|
|
3540
|
+
const out = await resolveAwsContext({
|
|
3541
|
+
dotenv: ctx.dotenv,
|
|
3542
|
+
cfg,
|
|
3543
|
+
});
|
|
3544
|
+
applyAwsContext(out, ctx, true);
|
|
3545
|
+
});
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3219
3548
|
/**
|
|
3220
3549
|
* Zod schema for AWS plugin configuration.
|
|
3221
3550
|
*/
|
|
3222
|
-
const
|
|
3551
|
+
const awsPluginConfigSchema = z$2.object({
|
|
3223
3552
|
profile: z$2.string().optional(),
|
|
3224
3553
|
region: z$2.string().optional(),
|
|
3225
3554
|
defaultRegion: z$2.string().optional(),
|
|
@@ -3243,129 +3572,16 @@ const AwsPluginConfigSchema = z$2.object({
|
|
|
3243
3572
|
const awsPlugin = () => {
|
|
3244
3573
|
const plugin = definePlugin({
|
|
3245
3574
|
ns: 'aws',
|
|
3246
|
-
configSchema:
|
|
3247
|
-
setup
|
|
3248
|
-
|
|
3249
|
-
cli
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
.passThroughOptions()
|
|
3253
|
-
.allowUnknownOption(true)
|
|
3254
|
-
// Boolean toggles with dynamic help labels (effective defaults)
|
|
3255
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--login-on-demand', (_bag, cfg) => `attempt aws sso login on-demand${cfg.loginOnDemand ? ' (default)' : ''}`))
|
|
3256
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--no-login-on-demand', (_bag, cfg) => `disable sso login on-demand${cfg.loginOnDemand === false ? ' (default)' : ''}`))
|
|
3257
|
-
// Strings / enums
|
|
3258
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--profile <string>', (_bag, cfg) => `AWS profile name${cfg.profile ? ` (default: ${JSON.stringify(cfg.profile)})` : ''}`))
|
|
3259
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--region <string>', (_bag, cfg) => `AWS region${cfg.region ? ` (default: ${JSON.stringify(cfg.region)})` : ''}`))
|
|
3260
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--default-region <string>', (_bag, cfg) => `fallback region${cfg.defaultRegion ? ` (default: ${JSON.stringify(cfg.defaultRegion)})` : ''}`))
|
|
3261
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--strategy <string>', (_bag, cfg) => `credential acquisition strategy: cli-export|none${cfg.strategy ? ` (default: ${JSON.stringify(cfg.strategy)})` : ''}`))
|
|
3262
|
-
// Advanced key overrides
|
|
3263
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--profile-key <string>', (_bag, cfg) => `dotenv/config key for local profile${cfg.profileKey ? ` (default: ${JSON.stringify(cfg.profileKey)})` : ''}`))
|
|
3264
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--profile-fallback-key <string>', (_bag, cfg) => `fallback dotenv/config key for profile${cfg.profileFallbackKey ? ` (default: ${JSON.stringify(cfg.profileFallbackKey)})` : ''}`))
|
|
3265
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--region-key <string>', (_bag, cfg) => `dotenv/config key for region${cfg.regionKey ? ` (default: ${JSON.stringify(cfg.regionKey)})` : ''}`))
|
|
3266
|
-
// Accept any extra operands so Commander does not error when tokens appear after "--".
|
|
3267
|
-
.argument('[args...]')
|
|
3268
|
-
.action(async (args, opts, thisCommand) => {
|
|
3269
|
-
const pluginInst = plugin;
|
|
3270
|
-
// Access merged root CLI options (installed by passOptions())
|
|
3271
|
-
const bag = readMergedOptions(thisCommand);
|
|
3272
|
-
const capture = shouldCapture(bag.capture);
|
|
3273
|
-
const underTests = process.env.GETDOTENV_TEST === '1' ||
|
|
3274
|
-
typeof process.env.VITEST_WORKER_ID === 'string';
|
|
3275
|
-
// Build overlay cfg from subcommand flags layered over discovered config.
|
|
3276
|
-
const ctx = cli.getCtx();
|
|
3277
|
-
const cfgBase = pluginInst.readConfig(cli);
|
|
3278
|
-
const o = opts;
|
|
3279
|
-
const overlay = {};
|
|
3280
|
-
// Map boolean toggles (respect explicit --no-*)
|
|
3281
|
-
if (Object.prototype.hasOwnProperty.call(o, 'loginOnDemand'))
|
|
3282
|
-
overlay.loginOnDemand = Boolean(o.loginOnDemand);
|
|
3283
|
-
// Strings/enums
|
|
3284
|
-
if (typeof o.profile === 'string')
|
|
3285
|
-
overlay.profile = o.profile;
|
|
3286
|
-
if (typeof o.region === 'string')
|
|
3287
|
-
overlay.region = o.region;
|
|
3288
|
-
if (typeof o.defaultRegion === 'string')
|
|
3289
|
-
overlay.defaultRegion = o.defaultRegion;
|
|
3290
|
-
if (typeof o.strategy === 'string')
|
|
3291
|
-
overlay.strategy = o.strategy;
|
|
3292
|
-
// Advanced key overrides
|
|
3293
|
-
if (typeof o.profileKey === 'string')
|
|
3294
|
-
overlay.profileKey = o.profileKey;
|
|
3295
|
-
if (typeof o.profileFallbackKey === 'string')
|
|
3296
|
-
overlay.profileFallbackKey = o.profileFallbackKey;
|
|
3297
|
-
if (typeof o.regionKey === 'string')
|
|
3298
|
-
overlay.regionKey = o.regionKey;
|
|
3299
|
-
const cfg = {
|
|
3300
|
-
...cfgBase,
|
|
3301
|
-
...overlay,
|
|
3302
|
-
};
|
|
3303
|
-
// Resolve current context with overrides
|
|
3304
|
-
const out = await resolveAwsContext({
|
|
3305
|
-
dotenv: ctx.dotenv,
|
|
3306
|
-
cfg,
|
|
3307
|
-
});
|
|
3308
|
-
// Publish env/context
|
|
3309
|
-
applyAwsContext(out, ctx, true);
|
|
3310
|
-
// Forward when positional args are present; otherwise session-only.
|
|
3311
|
-
if (Array.isArray(args) && args.length > 0) {
|
|
3312
|
-
const argv = ['aws', ...args];
|
|
3313
|
-
const shellSetting = resolveShell(bag.scripts, 'aws', bag.shell);
|
|
3314
|
-
const exit = await runCommand(argv, shellSetting, {
|
|
3315
|
-
env: buildSpawnEnv(process.env, ctx.dotenv),
|
|
3316
|
-
stdio: capture ? 'pipe' : 'inherit',
|
|
3317
|
-
});
|
|
3318
|
-
// Deterministic termination (suppressed under tests)
|
|
3319
|
-
if (!underTests) {
|
|
3320
|
-
process.exit(typeof exit === 'number' ? exit : 0);
|
|
3321
|
-
}
|
|
3322
|
-
return;
|
|
3323
|
-
}
|
|
3324
|
-
else {
|
|
3325
|
-
// Session only: low-noise breadcrumb under debug
|
|
3326
|
-
if (process.env.GETDOTENV_DEBUG) {
|
|
3327
|
-
try {
|
|
3328
|
-
const msg = JSON.stringify({
|
|
3329
|
-
profile: out.profile,
|
|
3330
|
-
region: out.region,
|
|
3331
|
-
hasCreds: Boolean(out.credentials),
|
|
3332
|
-
});
|
|
3333
|
-
process.stderr.write(`[aws] session established ${msg}\n`);
|
|
3334
|
-
}
|
|
3335
|
-
catch {
|
|
3336
|
-
/* ignore */
|
|
3337
|
-
}
|
|
3338
|
-
}
|
|
3339
|
-
if (!underTests)
|
|
3340
|
-
process.exit(0);
|
|
3341
|
-
return;
|
|
3342
|
-
}
|
|
3343
|
-
});
|
|
3575
|
+
configSchema: awsPluginConfigSchema,
|
|
3576
|
+
setup(cli) {
|
|
3577
|
+
cli.description('Establish an AWS session and optionally forward to the AWS CLI');
|
|
3578
|
+
const awsCmd = attachAwsOptions(cli, plugin);
|
|
3579
|
+
attachAwsPreSubcommandHook(cli, plugin);
|
|
3580
|
+
attachAwsDefaultAction(cli, plugin, awsCmd);
|
|
3344
3581
|
return undefined;
|
|
3345
3582
|
},
|
|
3346
|
-
afterResolve: async (_cli, ctx) => {
|
|
3347
|
-
const cfg = plugin.readConfig(_cli);
|
|
3348
|
-
const out = await resolveAwsContext({
|
|
3349
|
-
dotenv: ctx.dotenv,
|
|
3350
|
-
cfg,
|
|
3351
|
-
});
|
|
3352
|
-
applyAwsContext(out, ctx, true);
|
|
3353
|
-
// Optional: low-noise breadcrumb for diagnostics
|
|
3354
|
-
if (process.env.GETDOTENV_DEBUG) {
|
|
3355
|
-
try {
|
|
3356
|
-
const msg = JSON.stringify({
|
|
3357
|
-
profile: out.profile,
|
|
3358
|
-
region: out.region,
|
|
3359
|
-
hasCreds: Boolean(out.credentials),
|
|
3360
|
-
});
|
|
3361
|
-
process.stderr.write(`[aws] afterResolve ${msg}\n`);
|
|
3362
|
-
}
|
|
3363
|
-
catch {
|
|
3364
|
-
/* ignore */
|
|
3365
|
-
}
|
|
3366
|
-
}
|
|
3367
|
-
},
|
|
3368
3583
|
});
|
|
3584
|
+
plugin.afterResolve = attachAwsAfterResolveHook(plugin);
|
|
3369
3585
|
return plugin;
|
|
3370
3586
|
};
|
|
3371
3587
|
|
|
@@ -13827,21 +14043,78 @@ class GetCallerIdentityCommand extends Command
|
|
|
13827
14043
|
}
|
|
13828
14044
|
|
|
13829
14045
|
/**
|
|
13830
|
-
*
|
|
13831
|
-
*
|
|
14046
|
+
* Attach the default action for the `aws whoami` command.
|
|
14047
|
+
*
|
|
14048
|
+
* This behavior executes only when `aws whoami` is invoked without a subcommand.
|
|
14049
|
+
*
|
|
14050
|
+
* @param cli - The `whoami` command mount.
|
|
14051
|
+
* @returns Nothing.
|
|
14052
|
+
*/
|
|
14053
|
+
function attachWhoamiDefaultAction(cli) {
|
|
14054
|
+
cli.action(async () => {
|
|
14055
|
+
// The AWS SDK default providers will read credentials from process.env,
|
|
14056
|
+
// which the aws parent has already populated.
|
|
14057
|
+
const client = new STSClient$1();
|
|
14058
|
+
const result = await client.send(new GetCallerIdentityCommand());
|
|
14059
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14060
|
+
});
|
|
14061
|
+
}
|
|
14062
|
+
|
|
14063
|
+
/**
|
|
14064
|
+
* Attach options/arguments for the `aws whoami` plugin mount.
|
|
14065
|
+
*
|
|
14066
|
+
* This subcommand currently takes no flags/args; this module exists to keep the
|
|
14067
|
+
* wiring layout consistent across shipped plugins (options vs actions).
|
|
14068
|
+
*
|
|
14069
|
+
* Note: the plugin description is owned by `src/plugins/aws/whoami/index.ts` and
|
|
14070
|
+
* must not be set here.
|
|
14071
|
+
*
|
|
14072
|
+
* @param cli - The `whoami` command mount under `aws`.
|
|
14073
|
+
* @returns The same `cli` instance for chaining.
|
|
14074
|
+
*
|
|
14075
|
+
* @internal
|
|
14076
|
+
*/
|
|
14077
|
+
function attachWhoamiOptions(cli) {
|
|
14078
|
+
return cli;
|
|
14079
|
+
}
|
|
14080
|
+
|
|
14081
|
+
/**
|
|
14082
|
+
* Attach the `really` subcommand under `aws whoami`.
|
|
14083
|
+
*
|
|
14084
|
+
* Reads `SECRET_IDENTITY` from the resolved get-dotenv context (`cli.getCtx().dotenv`).
|
|
14085
|
+
*
|
|
14086
|
+
* @param cli - The `whoami` command mount.
|
|
14087
|
+
* @returns Nothing.
|
|
14088
|
+
*/
|
|
14089
|
+
function attachWhoamiReallyAction(cli) {
|
|
14090
|
+
const really = cli
|
|
14091
|
+
.ns('really')
|
|
14092
|
+
.description('Print SECRET_IDENTITY from the resolved dotenv context');
|
|
14093
|
+
really.action(() => {
|
|
14094
|
+
const secretIdentity = really.getCtx().dotenv.SECRET_IDENTITY;
|
|
14095
|
+
console.log(`Your secret identity is ${secretIdentity ?? 'still a secret'}.`);
|
|
14096
|
+
});
|
|
14097
|
+
}
|
|
14098
|
+
|
|
14099
|
+
/**
|
|
14100
|
+
* AWS Whoami plugin factory.
|
|
14101
|
+
*
|
|
14102
|
+
* This plugin demonstrates a “bucket of subcommands” pattern:
|
|
14103
|
+
* - Subcommand behavior is articulated in separate modules as `attach*` helpers.
|
|
14104
|
+
* - Those helpers are not individually composable plugins; they are internal wiring for one plugin instance.
|
|
14105
|
+
*
|
|
14106
|
+
* @returns A plugin instance mounted at `aws whoami`.
|
|
13832
14107
|
*/
|
|
13833
14108
|
const awsWhoamiPlugin = () => definePlugin({
|
|
13834
14109
|
ns: 'whoami',
|
|
13835
14110
|
setup(cli) {
|
|
13836
|
-
cli
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
console.log(JSON.stringify(result, null, 2));
|
|
13844
|
-
});
|
|
14111
|
+
cli.description('Print AWS caller identity (uses parent aws session)');
|
|
14112
|
+
// Options/args (none today, but keep layout consistent with other plugins).
|
|
14113
|
+
const whoami = attachWhoamiOptions(cli);
|
|
14114
|
+
// Default behavior: `getdotenv aws whoami`
|
|
14115
|
+
attachWhoamiDefaultAction(whoami);
|
|
14116
|
+
// Subcommand behavior: `getdotenv aws whoami really`
|
|
14117
|
+
attachWhoamiReallyAction(whoami);
|
|
13845
14118
|
return undefined;
|
|
13846
14119
|
},
|
|
13847
14120
|
});
|
|
@@ -13949,7 +14222,7 @@ const execShellCommandBatch = async ({ command, getDotenvCliOptions, dotenvEnv,
|
|
|
13949
14222
|
/**
|
|
13950
14223
|
* Attach the default "cmd" subcommand action with contextual typing.
|
|
13951
14224
|
*/
|
|
13952
|
-
const
|
|
14225
|
+
const attachBatchCmdAction = (plugin, cli, batchCmd, pluginOpts, cmd) => {
|
|
13953
14226
|
cmd.action(async (commandParts, _subOpts, thisCommand) => {
|
|
13954
14227
|
const mergedBag = readMergedOptions(batchCmd);
|
|
13955
14228
|
const logger = mergedBag.logger;
|
|
@@ -14058,11 +14331,37 @@ const attachDefaultCmdAction$1 = (plugin, cli, batchCmd, pluginOpts, cmd) => {
|
|
|
14058
14331
|
});
|
|
14059
14332
|
};
|
|
14060
14333
|
|
|
14334
|
+
/**
|
|
14335
|
+
* Attach the default `cmd` subcommand under the `batch` command.
|
|
14336
|
+
*
|
|
14337
|
+
* This encapsulates:
|
|
14338
|
+
* - Subcommand construction (`new Command().name('cmd')…`)
|
|
14339
|
+
* - Action wiring
|
|
14340
|
+
* - Mounting as the default subcommand for `batch`
|
|
14341
|
+
*
|
|
14342
|
+
* @param plugin - The batch plugin instance.
|
|
14343
|
+
* @param cli - The batch command mount.
|
|
14344
|
+
* @param batchCmd - The `batch` command (same as `cli` mount).
|
|
14345
|
+
* @param pluginOpts - Batch plugin factory options.
|
|
14346
|
+
*
|
|
14347
|
+
* @internal
|
|
14348
|
+
*/
|
|
14349
|
+
const attachBatchCmdSubcommand = (plugin, cli, batchCmd, pluginOpts) => {
|
|
14350
|
+
const cmdSub = new Command$1()
|
|
14351
|
+
.name('cmd')
|
|
14352
|
+
.description('execute command, conflicts with --command option (default subcommand)')
|
|
14353
|
+
.enablePositionalOptions()
|
|
14354
|
+
.passThroughOptions()
|
|
14355
|
+
.argument('[command...]');
|
|
14356
|
+
attachBatchCmdAction(plugin, cli, batchCmd, pluginOpts, cmdSub);
|
|
14357
|
+
batchCmd.addCommand(cmdSub, { isDefault: true });
|
|
14358
|
+
};
|
|
14359
|
+
|
|
14061
14360
|
/**
|
|
14062
14361
|
* Attach the parent-level action for the batch plugin.
|
|
14063
14362
|
* Handles parent flags (e.g. `getdotenv batch -l`) and delegates to the batch executor.
|
|
14064
14363
|
*/
|
|
14065
|
-
const
|
|
14364
|
+
const attachBatchDefaultAction = (plugin, cli, pluginOpts, parent) => {
|
|
14066
14365
|
parent.action(async function (...args) {
|
|
14067
14366
|
// Commander Unknown generics: [...unknown[], OptionValues, thisCommand]
|
|
14068
14367
|
const thisCommand = args[args.length - 1];
|
|
@@ -14163,6 +14462,45 @@ const attachParentInvoker$1 = (plugin, cli, pluginOpts, parent) => {
|
|
|
14163
14462
|
});
|
|
14164
14463
|
};
|
|
14165
14464
|
|
|
14465
|
+
/**
|
|
14466
|
+
* Attach options/arguments for the batch plugin mount.
|
|
14467
|
+
*
|
|
14468
|
+
* Note: the plugin description is owned by `src/plugins/batch/index.ts` and
|
|
14469
|
+
* must not be set here.
|
|
14470
|
+
*
|
|
14471
|
+
* @param plugin - Batch plugin instance (for dynamic option descriptions).
|
|
14472
|
+
* @param cli - The `batch` command mount.
|
|
14473
|
+
* @returns The same `cli` instance for chaining.
|
|
14474
|
+
*
|
|
14475
|
+
* @internal
|
|
14476
|
+
*/
|
|
14477
|
+
function attachBatchOptions(plugin, cli) {
|
|
14478
|
+
const GROUP = `plugin:${cli.name()}`;
|
|
14479
|
+
return (cli
|
|
14480
|
+
.enablePositionalOptions()
|
|
14481
|
+
.passThroughOptions()
|
|
14482
|
+
// Dynamic help: show effective defaults from the merged/interpolated plugin config slice.
|
|
14483
|
+
.addOption((() => {
|
|
14484
|
+
const opt = plugin.createPluginDynamicOption(cli, '-p, --pkg-cwd', (_bag, cfg) => `use nearest package directory as current working directory${cfg.pkgCwd ? ' (default)' : ''}`);
|
|
14485
|
+
cli.setOptionGroup(opt, GROUP);
|
|
14486
|
+
return opt;
|
|
14487
|
+
})())
|
|
14488
|
+
.addOption((() => {
|
|
14489
|
+
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 || './')})`);
|
|
14490
|
+
cli.setOptionGroup(opt, GROUP);
|
|
14491
|
+
return opt;
|
|
14492
|
+
})())
|
|
14493
|
+
.addOption((() => {
|
|
14494
|
+
const opt = plugin.createPluginDynamicOption(cli, '-g, --globs <string>', (_bag, cfg) => `space-delimited globs from root path (default: ${JSON.stringify(cfg.globs || '*')})`);
|
|
14495
|
+
cli.setOptionGroup(opt, GROUP);
|
|
14496
|
+
return opt;
|
|
14497
|
+
})())
|
|
14498
|
+
.option('-c, --command <string>', 'command executed according to the base shell resolution')
|
|
14499
|
+
.option('-l, --list', 'list working directories without executing command')
|
|
14500
|
+
.option('-e, --ignore-errors', 'ignore errors and continue with next path')
|
|
14501
|
+
.argument('[command...]'));
|
|
14502
|
+
}
|
|
14503
|
+
|
|
14166
14504
|
/**
|
|
14167
14505
|
* Zod schema for a single script entry (string or object).
|
|
14168
14506
|
*/
|
|
@@ -14176,7 +14514,7 @@ const ScriptSchema = z$2.union([
|
|
|
14176
14514
|
/**
|
|
14177
14515
|
* Zod schema for batch plugin configuration.
|
|
14178
14516
|
*/
|
|
14179
|
-
const
|
|
14517
|
+
const batchPluginConfigSchema = z$2.object({
|
|
14180
14518
|
scripts: z$2.record(z$2.string(), ScriptSchema).optional(),
|
|
14181
14519
|
shell: z$2.union([z$2.string(), z$2.boolean()]).optional(),
|
|
14182
14520
|
rootPath: z$2.string().optional(),
|
|
@@ -14202,45 +14540,15 @@ const batchPlugin = (opts = {}) => {
|
|
|
14202
14540
|
ns: 'batch',
|
|
14203
14541
|
// Host validates this when config-loader is enabled; plugins may also
|
|
14204
14542
|
// re-validate at action time as a safety belt.
|
|
14205
|
-
configSchema:
|
|
14543
|
+
configSchema: batchPluginConfigSchema,
|
|
14206
14544
|
setup(cli) {
|
|
14207
14545
|
const batchCmd = cli; // mount provided by host
|
|
14208
|
-
|
|
14209
|
-
batchCmd
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14214
|
-
.addOption((() => {
|
|
14215
|
-
const opt = plugin.createPluginDynamicOption(cli, '-p, --pkg-cwd', (_bag, cfg) => `use nearest package directory as current working directory${cfg.pkgCwd ? ' (default)' : ''}`);
|
|
14216
|
-
cli.setOptionGroup(opt, GROUP);
|
|
14217
|
-
return opt;
|
|
14218
|
-
})())
|
|
14219
|
-
.addOption((() => {
|
|
14220
|
-
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 || './')})`);
|
|
14221
|
-
cli.setOptionGroup(opt, GROUP);
|
|
14222
|
-
return opt;
|
|
14223
|
-
})())
|
|
14224
|
-
.addOption((() => {
|
|
14225
|
-
const opt = plugin.createPluginDynamicOption(cli, '-g, --globs <string>', (_bag, cfg) => `space-delimited globs from root path (default: ${JSON.stringify(cfg.globs || '*')})`);
|
|
14226
|
-
cli.setOptionGroup(opt, GROUP);
|
|
14227
|
-
return opt;
|
|
14228
|
-
})())
|
|
14229
|
-
.option('-c, --command <string>', 'command executed according to the base shell resolution')
|
|
14230
|
-
.option('-l, --list', 'list working directories without executing command')
|
|
14231
|
-
.option('-e, --ignore-errors', 'ignore errors and continue with next path')
|
|
14232
|
-
.argument('[command...]');
|
|
14233
|
-
// Default subcommand "cmd" with contextual typing for args/opts
|
|
14234
|
-
const cmdSub = new Command$1()
|
|
14235
|
-
.name('cmd')
|
|
14236
|
-
.description('execute command, conflicts with --command option (default subcommand)')
|
|
14237
|
-
.enablePositionalOptions()
|
|
14238
|
-
.passThroughOptions()
|
|
14239
|
-
.argument('[command...]');
|
|
14240
|
-
attachDefaultCmdAction$1(plugin, cli, batchCmd, opts, cmdSub);
|
|
14241
|
-
batchCmd.addCommand(cmdSub, { isDefault: true });
|
|
14242
|
-
// Parent invoker (unified naming)
|
|
14243
|
-
attachParentInvoker$1(plugin, cli, opts, batchCmd);
|
|
14546
|
+
batchCmd.description('Batch command execution across multiple working directories.');
|
|
14547
|
+
attachBatchOptions(plugin, batchCmd);
|
|
14548
|
+
// Default subcommand `cmd` (mounted as batch default subcommand)
|
|
14549
|
+
attachBatchCmdSubcommand(plugin, cli, batchCmd, opts);
|
|
14550
|
+
// Default action for the batch command mount (parent flags and positional form)
|
|
14551
|
+
attachBatchDefaultAction(plugin, cli, opts, batchCmd);
|
|
14244
14552
|
return undefined;
|
|
14245
14553
|
},
|
|
14246
14554
|
});
|
|
@@ -14334,14 +14642,11 @@ async function runCmdWithContext(cli, merged, command, _opts) {
|
|
|
14334
14642
|
}
|
|
14335
14643
|
|
|
14336
14644
|
/**
|
|
14337
|
-
* Attach the default "cmd" subcommand action
|
|
14645
|
+
* Attach the default "cmd" subcommand action.
|
|
14338
14646
|
* Mirrors the prior inline implementation in cmd/index.ts.
|
|
14339
14647
|
*/
|
|
14340
|
-
const
|
|
14341
|
-
cmd
|
|
14342
|
-
.enablePositionalOptions()
|
|
14343
|
-
.passThroughOptions()
|
|
14344
|
-
.action(async function (...allArgs) {
|
|
14648
|
+
const attachCmdDefaultAction = (cli, cmd, aliasKey) => {
|
|
14649
|
+
cmd.action(async function (...allArgs) {
|
|
14345
14650
|
// Commander passes: [...positionals, options, thisCommand]
|
|
14346
14651
|
const thisCommand = allArgs[allArgs.length - 1];
|
|
14347
14652
|
const commandParts = allArgs[0];
|
|
@@ -14374,11 +14679,29 @@ const attachDefaultCmdAction = (cli, cmd, aliasKey) => {
|
|
|
14374
14679
|
});
|
|
14375
14680
|
};
|
|
14376
14681
|
|
|
14682
|
+
/**
|
|
14683
|
+
* Attach options/arguments for the cmd plugin mount.
|
|
14684
|
+
*
|
|
14685
|
+
* Note: the plugin description is owned by `src/plugins/cmd/index.ts` and must
|
|
14686
|
+
* not be set here.
|
|
14687
|
+
*
|
|
14688
|
+
* @param cli - The `cmd` command mount.
|
|
14689
|
+
* @returns The same `cli` instance for chaining.
|
|
14690
|
+
*
|
|
14691
|
+
* @internal
|
|
14692
|
+
*/
|
|
14693
|
+
function attachCmdOptions(cli) {
|
|
14694
|
+
return cli
|
|
14695
|
+
.enablePositionalOptions()
|
|
14696
|
+
.passThroughOptions()
|
|
14697
|
+
.argument('[command...]');
|
|
14698
|
+
}
|
|
14699
|
+
|
|
14377
14700
|
/**
|
|
14378
14701
|
* Install the parent-level invoker (alias) for the cmd plugin.
|
|
14379
14702
|
* Unifies naming with batch attachParentInvoker; behavior unchanged.
|
|
14380
14703
|
*/
|
|
14381
|
-
const
|
|
14704
|
+
const attachCmdParentInvoker = (cli, options, plugin) => {
|
|
14382
14705
|
const dbg = (...args) => {
|
|
14383
14706
|
if (process.env.GETDOTENV_DEBUG) {
|
|
14384
14707
|
try {
|
|
@@ -14489,7 +14812,7 @@ const attachParentInvoker = (cli, options, _cmd, plugin) => {
|
|
|
14489
14812
|
/**
|
|
14490
14813
|
* Zod schema for cmd plugin configuration.
|
|
14491
14814
|
*/
|
|
14492
|
-
const
|
|
14815
|
+
const cmdPluginConfigSchema = z$2
|
|
14493
14816
|
.object({
|
|
14494
14817
|
expand: z$2.boolean().optional(),
|
|
14495
14818
|
})
|
|
@@ -14509,7 +14832,7 @@ const CmdConfigSchema = z$2
|
|
|
14509
14832
|
const cmdPlugin = (options = {}) => {
|
|
14510
14833
|
const plugin = definePlugin({
|
|
14511
14834
|
ns: 'cmd',
|
|
14512
|
-
configSchema:
|
|
14835
|
+
configSchema: cmdPluginConfigSchema,
|
|
14513
14836
|
setup(cli) {
|
|
14514
14837
|
const aliasSpec = typeof options.optionAlias === 'string'
|
|
14515
14838
|
? { flags: options.optionAlias}
|
|
@@ -14521,14 +14844,13 @@ const cmdPlugin = (options = {}) => {
|
|
|
14521
14844
|
};
|
|
14522
14845
|
const aliasKey = aliasSpec ? deriveKey(aliasSpec.flags) : undefined;
|
|
14523
14846
|
// Mount is the command ('cmd'); attach default action.
|
|
14524
|
-
cli
|
|
14525
|
-
|
|
14526
|
-
|
|
14527
|
-
|
|
14528
|
-
attachDefaultCmdAction(cli, cli, aliasKey);
|
|
14847
|
+
cli.description('Execute command according to the --shell option, conflicts with --command option (default subcommand)');
|
|
14848
|
+
// Options/arguments (positional payload, argv routing) are attached separately.
|
|
14849
|
+
attachCmdOptions(cli);
|
|
14850
|
+
attachCmdDefaultAction(cli, cli, aliasKey);
|
|
14529
14851
|
// Parent-attached option alias (optional, unified naming).
|
|
14530
14852
|
if (aliasSpec !== undefined) {
|
|
14531
|
-
|
|
14853
|
+
attachCmdParentInvoker(cli, options, plugin);
|
|
14532
14854
|
}
|
|
14533
14855
|
return undefined;
|
|
14534
14856
|
},
|
|
@@ -14537,14 +14859,21 @@ const cmdPlugin = (options = {}) => {
|
|
|
14537
14859
|
};
|
|
14538
14860
|
|
|
14539
14861
|
/**
|
|
14540
|
-
* Ensure a directory exists.
|
|
14862
|
+
* Ensure a directory exists (parents included).
|
|
14863
|
+
*
|
|
14864
|
+
* @param p - Directory path to create.
|
|
14865
|
+
* @returns A `Promise\<string\>` resolving to the provided `p` value.
|
|
14541
14866
|
*/
|
|
14542
14867
|
const ensureDir = async (p) => {
|
|
14543
14868
|
await fs.ensureDir(p);
|
|
14544
14869
|
return p;
|
|
14545
14870
|
};
|
|
14546
14871
|
/**
|
|
14547
|
-
* Write text content to a file, ensuring the parent directory exists.
|
|
14872
|
+
* Write UTF-8 text content to a file, ensuring the parent directory exists.
|
|
14873
|
+
*
|
|
14874
|
+
* @param dest - Destination file path.
|
|
14875
|
+
* @param data - File contents to write.
|
|
14876
|
+
* @returns A `Promise\<void\>` which resolves when the file is written.
|
|
14548
14877
|
*/
|
|
14549
14878
|
const writeFile$1 = async (dest, data) => {
|
|
14550
14879
|
await ensureDir(path.dirname(dest));
|
|
@@ -14556,6 +14885,7 @@ const writeFile$1 = async (dest, data) => {
|
|
|
14556
14885
|
* @param src - Source file path.
|
|
14557
14886
|
* @param dest - Destination file path.
|
|
14558
14887
|
* @param substitutions - Map of token literals to replacement strings.
|
|
14888
|
+
* @returns A `Promise\<void\>` which resolves when the file has been copied.
|
|
14559
14889
|
*/
|
|
14560
14890
|
const copyTextFile = async (src, dest, substitutions) => {
|
|
14561
14891
|
const contents = await fs.readFile(src, 'utf-8');
|
|
@@ -14567,6 +14897,10 @@ const copyTextFile = async (src, dest, substitutions) => {
|
|
|
14567
14897
|
/**
|
|
14568
14898
|
* Ensure a set of lines exist (exact match) in a file. Creates the file
|
|
14569
14899
|
* when missing. Returns whether it was created or changed.
|
|
14900
|
+
*
|
|
14901
|
+
* @param filePath - Target file path to create/update.
|
|
14902
|
+
* @param lines - Lines which must be present (exact string match).
|
|
14903
|
+
* @returns A `Promise\<object\>` describing whether the file was created and/or changed.
|
|
14570
14904
|
*/
|
|
14571
14905
|
const ensureLines = async (filePath, lines) => {
|
|
14572
14906
|
const exists = await fs.pathExists(filePath);
|
|
@@ -14594,11 +14928,23 @@ const ensureLines = async (filePath, lines) => {
|
|
|
14594
14928
|
return { created: false, changed: false };
|
|
14595
14929
|
};
|
|
14596
14930
|
|
|
14597
|
-
|
|
14931
|
+
/**
|
|
14932
|
+
* Absolute path to the shipped templates directory.
|
|
14933
|
+
*
|
|
14934
|
+
* Used by the init scaffolder to locate files under `templates/` at runtime.
|
|
14935
|
+
*
|
|
14936
|
+
* @remarks
|
|
14937
|
+
* This path is resolved relative to the current working directory. It assumes
|
|
14938
|
+
* the `templates/` folder is present alongside the installed package (or in the
|
|
14939
|
+
* repository when running from source).
|
|
14940
|
+
*/
|
|
14598
14941
|
const TEMPLATES_ROOT = path.resolve('templates');
|
|
14599
14942
|
|
|
14600
14943
|
/**
|
|
14601
14944
|
* Plan the copy operations for configuration files.
|
|
14945
|
+
*
|
|
14946
|
+
* @param options - Planning options for config scaffolding.
|
|
14947
|
+
* @returns An array of copy operations to perform.
|
|
14602
14948
|
*/
|
|
14603
14949
|
const planConfigCopies = ({ format, withLocal, destRoot, }) => {
|
|
14604
14950
|
const copies = [];
|
|
@@ -14642,10 +14988,14 @@ const planConfigCopies = ({ format, withLocal, destRoot, }) => {
|
|
|
14642
14988
|
};
|
|
14643
14989
|
/**
|
|
14644
14990
|
* Plan the copy operations for the CLI skeleton.
|
|
14991
|
+
*
|
|
14992
|
+
* @param options - Planning options for CLI scaffolding.
|
|
14993
|
+
* @returns An array of copy operations to perform.
|
|
14645
14994
|
*/
|
|
14646
14995
|
const planCliCopies = ({ cliName, destRoot, }) => {
|
|
14647
14996
|
const subs = { __CLI_NAME__: cliName };
|
|
14648
14997
|
const base = path.join(destRoot, 'src', 'cli', cliName);
|
|
14998
|
+
const helloBase = path.join(base, 'plugins', 'hello');
|
|
14649
14999
|
return [
|
|
14650
15000
|
{
|
|
14651
15001
|
src: path.join(TEMPLATES_ROOT, 'cli', 'index.ts'),
|
|
@@ -14653,8 +15003,28 @@ const planCliCopies = ({ cliName, destRoot, }) => {
|
|
|
14653
15003
|
subs,
|
|
14654
15004
|
},
|
|
14655
15005
|
{
|
|
14656
|
-
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello.ts'),
|
|
14657
|
-
dest: path.join(
|
|
15006
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'index.ts'),
|
|
15007
|
+
dest: path.join(helloBase, 'index.ts'),
|
|
15008
|
+
subs,
|
|
15009
|
+
},
|
|
15010
|
+
{
|
|
15011
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'options.ts'),
|
|
15012
|
+
dest: path.join(helloBase, 'options.ts'),
|
|
15013
|
+
subs,
|
|
15014
|
+
},
|
|
15015
|
+
{
|
|
15016
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'defaultAction.ts'),
|
|
15017
|
+
dest: path.join(helloBase, 'defaultAction.ts'),
|
|
15018
|
+
subs,
|
|
15019
|
+
},
|
|
15020
|
+
{
|
|
15021
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'strangerAction.ts'),
|
|
15022
|
+
dest: path.join(helloBase, 'strangerAction.ts'),
|
|
15023
|
+
subs,
|
|
15024
|
+
},
|
|
15025
|
+
{
|
|
15026
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'types.ts'),
|
|
15027
|
+
dest: path.join(helloBase, 'types.ts'),
|
|
14658
15028
|
subs,
|
|
14659
15029
|
},
|
|
14660
15030
|
];
|
|
@@ -14663,6 +15033,8 @@ const planCliCopies = ({ cliName, destRoot, }) => {
|
|
|
14663
15033
|
/**
|
|
14664
15034
|
* Determine whether the current environment should be treated as non-interactive.
|
|
14665
15035
|
* CI heuristics include: CI, GITHUB_ACTIONS, BUILDKITE, TEAMCITY_VERSION, TF_BUILD.
|
|
15036
|
+
*
|
|
15037
|
+
* @returns `true` when running in a CI-like environment or when stdin/stdout are not TTYs.
|
|
14666
15038
|
*/
|
|
14667
15039
|
const isNonInteractive = () => {
|
|
14668
15040
|
const ciLike = process.env.CI ||
|
|
@@ -14675,6 +15047,11 @@ const isNonInteractive = () => {
|
|
|
14675
15047
|
/**
|
|
14676
15048
|
* Prompt the user for a file collision decision.
|
|
14677
15049
|
* Returns a single-character code representing overwrite/example/skip (or 'all' variants).
|
|
15050
|
+
*
|
|
15051
|
+
* @param filePath - Path of the colliding file (for display).
|
|
15052
|
+
* @param logger - Logger used for user-facing messages.
|
|
15053
|
+
* @param rl - Readline interface used to capture user input.
|
|
15054
|
+
* @returns A single-character decision code.
|
|
14678
15055
|
*/
|
|
14679
15056
|
const promptDecision = async (filePath, logger, rl) => {
|
|
14680
15057
|
logger.log(`File exists: ${filePath}\nChoose: [o]verwrite, [e]xample, [s]kip, [O]verwrite All, [E]xample All, [S]kip All`);
|
|
@@ -14689,129 +15066,148 @@ const promptDecision = async (filePath, logger, rl) => {
|
|
|
14689
15066
|
};
|
|
14690
15067
|
|
|
14691
15068
|
/**
|
|
14692
|
-
*
|
|
14693
|
-
*
|
|
14694
|
-
*
|
|
14695
|
-
|
|
14696
|
-
|
|
14697
|
-
* Init plugin: scaffolds configuration files and a CLI skeleton for get-dotenv.
|
|
14698
|
-
* Supports collision detection, interactive prompts, and CI bypass.
|
|
15069
|
+
* Attach the init plugin default action.
|
|
15070
|
+
*
|
|
15071
|
+
* @param cli - The `init` command mount (with args/options attached).
|
|
15072
|
+
*
|
|
15073
|
+
* @internal
|
|
14699
15074
|
*/
|
|
14700
|
-
|
|
14701
|
-
|
|
14702
|
-
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14707
|
-
|
|
14708
|
-
|
|
14709
|
-
|
|
14710
|
-
|
|
14711
|
-
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
14717
|
-
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14733
|
-
|
|
14734
|
-
|
|
14735
|
-
|
|
14736
|
-
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
await copyTextFile(item.src, item.dest, subs);
|
|
14756
|
-
logger.log(`Overwrote ${path.relative(cwd, item.dest)}`);
|
|
14757
|
-
continue;
|
|
14758
|
-
}
|
|
14759
|
-
if (yes) {
|
|
14760
|
-
logger.log(`Skipped ${path.relative(cwd, item.dest)}`);
|
|
14761
|
-
continue;
|
|
14762
|
-
}
|
|
14763
|
-
let decision = globalDecision;
|
|
14764
|
-
if (!decision) {
|
|
14765
|
-
const a = await promptDecision(item.dest, logger, rl);
|
|
14766
|
-
if (a === 'O') {
|
|
14767
|
-
globalDecision = 'overwrite';
|
|
14768
|
-
decision = 'overwrite';
|
|
14769
|
-
}
|
|
14770
|
-
else if (a === 'E') {
|
|
14771
|
-
globalDecision = 'example';
|
|
14772
|
-
decision = 'example';
|
|
14773
|
-
}
|
|
14774
|
-
else if (a === 'S') {
|
|
14775
|
-
globalDecision = 'skip';
|
|
14776
|
-
decision = 'skip';
|
|
14777
|
-
}
|
|
14778
|
-
else {
|
|
14779
|
-
decision =
|
|
14780
|
-
a === 'o' ? 'overwrite' : a === 'e' ? 'example' : 'skip';
|
|
14781
|
-
}
|
|
15075
|
+
function attachInitDefaultAction(cli) {
|
|
15076
|
+
cli.action(async (destArg, opts, thisCommand) => {
|
|
15077
|
+
// Inherit logger from merged root options (base).
|
|
15078
|
+
const bag = readMergedOptions(thisCommand);
|
|
15079
|
+
const logger = bag.logger;
|
|
15080
|
+
const destRel = typeof destArg === 'string' && destArg.length > 0 ? destArg : '.';
|
|
15081
|
+
const cwd = process.cwd();
|
|
15082
|
+
const destRoot = path.resolve(cwd, destRel);
|
|
15083
|
+
const formatInput = opts['configFormat'];
|
|
15084
|
+
const formatRaw = typeof formatInput === 'string' ? formatInput.toLowerCase() : 'json';
|
|
15085
|
+
const format = (['json', 'yaml', 'js', 'ts'].includes(formatRaw) ? formatRaw : 'json');
|
|
15086
|
+
const withLocal = Boolean(opts['withLocal']);
|
|
15087
|
+
// dynamic flag reserved for future template variants; present for UX compatibility
|
|
15088
|
+
void opts['dynamic'];
|
|
15089
|
+
// CLI name default: --cli-name | basename(dest) | 'mycli'
|
|
15090
|
+
const cliNameInput = opts['cliName'];
|
|
15091
|
+
const cliName = (typeof cliNameInput === 'string' && cliNameInput.length > 0
|
|
15092
|
+
? cliNameInput
|
|
15093
|
+
: path.basename(destRoot) || 'mycli') || 'mycli';
|
|
15094
|
+
// Precedence: --force > --yes > auto-detect(non-interactive => yes)
|
|
15095
|
+
const force = Boolean(opts['force']);
|
|
15096
|
+
const yes = Boolean(opts['yes']) || (!force && isNonInteractive());
|
|
15097
|
+
// Build copy plan
|
|
15098
|
+
const cfgCopies = planConfigCopies({ format, withLocal, destRoot });
|
|
15099
|
+
const cliCopies = planCliCopies({ cliName, destRoot });
|
|
15100
|
+
const copies = [...cfgCopies, ...cliCopies];
|
|
15101
|
+
// Interactive state
|
|
15102
|
+
let globalDecision;
|
|
15103
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
15104
|
+
try {
|
|
15105
|
+
for (const item of copies) {
|
|
15106
|
+
const exists = await fs.pathExists(item.dest);
|
|
15107
|
+
if (!exists) {
|
|
15108
|
+
const subs = item.subs ?? {};
|
|
15109
|
+
await copyTextFile(item.src, item.dest, subs);
|
|
15110
|
+
logger.log(`Created ${path.relative(cwd, item.dest)}`);
|
|
15111
|
+
continue;
|
|
15112
|
+
}
|
|
15113
|
+
// Collision
|
|
15114
|
+
if (force) {
|
|
15115
|
+
const subs = item.subs ?? {};
|
|
15116
|
+
await copyTextFile(item.src, item.dest, subs);
|
|
15117
|
+
logger.log(`Overwrote ${path.relative(cwd, item.dest)}`);
|
|
15118
|
+
continue;
|
|
15119
|
+
}
|
|
15120
|
+
if (yes) {
|
|
15121
|
+
logger.log(`Skipped ${path.relative(cwd, item.dest)}`);
|
|
15122
|
+
continue;
|
|
15123
|
+
}
|
|
15124
|
+
let decision = globalDecision;
|
|
15125
|
+
if (!decision) {
|
|
15126
|
+
const a = await promptDecision(item.dest, logger, rl);
|
|
15127
|
+
if (a === 'O') {
|
|
15128
|
+
globalDecision = 'overwrite';
|
|
15129
|
+
decision = 'overwrite';
|
|
14782
15130
|
}
|
|
14783
|
-
if (
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
logger.log(`Overwrote ${path.relative(cwd, item.dest)}`);
|
|
15131
|
+
else if (a === 'E') {
|
|
15132
|
+
globalDecision = 'example';
|
|
15133
|
+
decision = 'example';
|
|
14787
15134
|
}
|
|
14788
|
-
else if (
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
await copyTextFile(item.src, destEx, subs);
|
|
14792
|
-
logger.log(`Wrote example ${path.relative(cwd, destEx)}`);
|
|
15135
|
+
else if (a === 'S') {
|
|
15136
|
+
globalDecision = 'skip';
|
|
15137
|
+
decision = 'skip';
|
|
14793
15138
|
}
|
|
14794
15139
|
else {
|
|
14795
|
-
|
|
15140
|
+
decision = a === 'o' ? 'overwrite' : a === 'e' ? 'example' : 'skip';
|
|
14796
15141
|
}
|
|
14797
15142
|
}
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
'*.local',
|
|
14803
|
-
]);
|
|
14804
|
-
if (created) {
|
|
14805
|
-
logger.log(`Created ${path.relative(cwd, giPath)}`);
|
|
15143
|
+
if (decision === 'overwrite') {
|
|
15144
|
+
const subs = item.subs ?? {};
|
|
15145
|
+
await copyTextFile(item.src, item.dest, subs);
|
|
15146
|
+
logger.log(`Overwrote ${path.relative(cwd, item.dest)}`);
|
|
14806
15147
|
}
|
|
14807
|
-
else if (
|
|
14808
|
-
|
|
15148
|
+
else if (decision === 'example') {
|
|
15149
|
+
const destEx = `${item.dest}.example`;
|
|
15150
|
+
const subs = item.subs ?? {};
|
|
15151
|
+
await copyTextFile(item.src, destEx, subs);
|
|
15152
|
+
logger.log(`Wrote example ${path.relative(cwd, destEx)}`);
|
|
15153
|
+
}
|
|
15154
|
+
else {
|
|
15155
|
+
logger.log(`Skipped ${path.relative(cwd, item.dest)}`);
|
|
14809
15156
|
}
|
|
14810
15157
|
}
|
|
14811
|
-
|
|
14812
|
-
|
|
15158
|
+
// Ensure .gitignore includes local config patterns.
|
|
15159
|
+
const giPath = path.join(destRoot, '.gitignore');
|
|
15160
|
+
const { created, changed } = await ensureLines(giPath, [
|
|
15161
|
+
'getdotenv.config.local.*',
|
|
15162
|
+
'*.local',
|
|
15163
|
+
]);
|
|
15164
|
+
if (created) {
|
|
15165
|
+
logger.log(`Created ${path.relative(cwd, giPath)}`);
|
|
14813
15166
|
}
|
|
14814
|
-
|
|
15167
|
+
else if (changed) {
|
|
15168
|
+
logger.log(`Updated ${path.relative(cwd, giPath)}`);
|
|
15169
|
+
}
|
|
15170
|
+
}
|
|
15171
|
+
finally {
|
|
15172
|
+
rl.close();
|
|
15173
|
+
}
|
|
15174
|
+
});
|
|
15175
|
+
}
|
|
15176
|
+
|
|
15177
|
+
/**
|
|
15178
|
+
* Attach options/arguments for the init plugin mount.
|
|
15179
|
+
*
|
|
15180
|
+
* @param cli - The `init` command mount.
|
|
15181
|
+
*
|
|
15182
|
+
* @internal
|
|
15183
|
+
*/
|
|
15184
|
+
function attachInitOptions(cli) {
|
|
15185
|
+
return (cli
|
|
15186
|
+
// Description is owned by the plugin index (src/plugins/init/index.ts).
|
|
15187
|
+
.argument('[dest]', 'destination path (default: ./)', '.')
|
|
15188
|
+
.option('--config-format <format>', 'config format: json|yaml|js|ts', 'json')
|
|
15189
|
+
.option('--with-local', 'include .local config variant')
|
|
15190
|
+
.option('--dynamic', 'include dynamic examples (JS/TS configs)')
|
|
15191
|
+
.option('--cli-name <string>', 'CLI name for skeleton and tokens')
|
|
15192
|
+
.option('--force', 'overwrite all existing files')
|
|
15193
|
+
.option('--yes', 'skip all collisions (no overwrite)'));
|
|
15194
|
+
}
|
|
15195
|
+
|
|
15196
|
+
/**
|
|
15197
|
+
* @packageDocumentation
|
|
15198
|
+
* Init plugin subpath. Scaffolds get‑dotenv configuration files and a simple
|
|
15199
|
+
* host‑based CLI skeleton with collision handling and CI‑safe defaults.
|
|
15200
|
+
*/
|
|
15201
|
+
/**
|
|
15202
|
+
* Init plugin: scaffolds configuration files and a CLI skeleton for get-dotenv.
|
|
15203
|
+
* Supports collision detection, interactive prompts, and CI bypass.
|
|
15204
|
+
*/
|
|
15205
|
+
const initPlugin = () => definePlugin({
|
|
15206
|
+
ns: 'init',
|
|
15207
|
+
setup(cli) {
|
|
15208
|
+
cli.description('Scaffold getdotenv config files and a host-based CLI skeleton.');
|
|
15209
|
+
const initCmd = attachInitOptions(cli);
|
|
15210
|
+
attachInitDefaultAction(initCmd);
|
|
14815
15211
|
return undefined;
|
|
14816
15212
|
},
|
|
14817
15213
|
});
|
|
@@ -18813,4 +19209,4 @@ var index = /*#__PURE__*/Object.freeze({
|
|
|
18813
19209
|
__Client: Client
|
|
18814
19210
|
});
|
|
18815
19211
|
|
|
18816
|
-
export { GetDotenvCli, baseRootOptionDefaults, buildSpawnEnv, createCli, defineDynamic, defineGetDotenvConfig, definePlugin, defineScripts, dotenvExpand, dotenvExpandAll, dotenvExpandFromProcessEnv, getDotenv, getDotenvCliOptions2Options, interpolateDeep, readMergedOptions };
|
|
19212
|
+
export { GetDotenvCli, baseRootOptionDefaults, buildSpawnEnv, createCli, defineDynamic, defineGetDotenvConfig, definePlugin, defineScripts, dotenvExpand, dotenvExpandAll, dotenvExpandFromProcessEnv, getDotenv, getDotenvCliOptions2Options, interpolateDeep, maybeWarnEntropy, readMergedOptions, redactDisplay, redactObject, shouldCapture, traceChildEnv };
|