@karmaniverous/get-dotenv 6.1.0 → 6.2.0
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 +20 -14
- package/dist/cli.d.ts +58 -2
- package/dist/cli.mjs +800 -364
- package/dist/cliHost.d.ts +216 -17
- package/dist/cliHost.mjs +178 -14
- package/dist/config.d.ts +12 -0
- package/dist/config.mjs +79 -2
- package/dist/env-overlay.d.ts +8 -0
- package/dist/getdotenv.cli.mjs +800 -364
- package/dist/index.d.ts +220 -35
- package/dist/index.mjs +851 -365
- package/dist/plugins-aws.d.ts +94 -6
- package/dist/plugins-aws.mjs +462 -184
- package/dist/plugins-batch.d.ts +85 -3
- package/dist/plugins-batch.mjs +203 -54
- package/dist/plugins-cmd.d.ts +85 -3
- package/dist/plugins-cmd.mjs +150 -28
- package/dist/plugins-init.d.ts +85 -3
- package/dist/plugins-init.mjs +270 -131
- package/dist/plugins.d.ts +85 -4
- package/dist/plugins.mjs +800 -364
- 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/defaultAction.ts +27 -0
- 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 +23 -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/types.ts +13 -0
- package/package.json +3 -4
- 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/dist/templates/cli/plugins/hello.ts +0 -42
- package/dist/templates/hello.ts +0 -42
- package/dist/templates/plugins/hello.ts +0 -42
- package/templates/cli/plugins/hello.ts +0 -42
package/dist/cli.mjs
CHANGED
|
@@ -38,26 +38,58 @@ import { versions, env } from 'process';
|
|
|
38
38
|
* Minimal process env representation used by options and helpers.
|
|
39
39
|
* Values may be `undefined` to indicate "unset".
|
|
40
40
|
*/
|
|
41
|
+
/**
|
|
42
|
+
* Schema for an env-like record.
|
|
43
|
+
*
|
|
44
|
+
* Keys are environment variable names and values are either strings or `undefined`
|
|
45
|
+
* (to represent “unset”).
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
41
49
|
const processEnvSchema = z$2.record(z$2.string(), z$2.string().optional());
|
|
42
50
|
// RAW: all fields optional — undefined means "inherit" from lower layers.
|
|
51
|
+
/**
|
|
52
|
+
* Programmatic options schema (raw).
|
|
53
|
+
*
|
|
54
|
+
* This schema is the canonical runtime source of truth for the `getDotenv()` programmatic API.
|
|
55
|
+
* All fields are optional; `undefined` generally means “inherit default/lower layer”.
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
43
59
|
const getDotenvOptionsSchemaRaw = z$2.object({
|
|
60
|
+
/** Default environment name when `env` is not provided. */
|
|
44
61
|
defaultEnv: z$2.string().optional(),
|
|
62
|
+
/** Base dotenv filename token (default `.env`). */
|
|
45
63
|
dotenvToken: z$2.string().optional(),
|
|
64
|
+
/** Path to a dynamic variables module (JS/TS) to load and apply. */
|
|
46
65
|
dynamicPath: z$2.string().optional(),
|
|
47
|
-
|
|
66
|
+
/** Dynamic map is intentionally wide for now; refine once sources are normalized. */
|
|
48
67
|
dynamic: z$2.record(z$2.string(), z$2.unknown()).optional(),
|
|
68
|
+
/** Selected environment name for this invocation (for env-scoped files and overlays). */
|
|
49
69
|
env: z$2.string().optional(),
|
|
70
|
+
/** When true, skip applying dynamic variables. */
|
|
50
71
|
excludeDynamic: z$2.boolean().optional(),
|
|
72
|
+
/** When true, skip environment-scoped dotenv files. */
|
|
51
73
|
excludeEnv: z$2.boolean().optional(),
|
|
74
|
+
/** When true, skip global dotenv files. */
|
|
52
75
|
excludeGlobal: z$2.boolean().optional(),
|
|
76
|
+
/** When true, skip private dotenv files. */
|
|
53
77
|
excludePrivate: z$2.boolean().optional(),
|
|
78
|
+
/** When true, skip public dotenv files. */
|
|
54
79
|
excludePublic: z$2.boolean().optional(),
|
|
80
|
+
/** When true, merge the final composed environment into `process.env`. */
|
|
55
81
|
loadProcess: z$2.boolean().optional(),
|
|
82
|
+
/** When true, log the final environment map via `logger`. */
|
|
56
83
|
log: z$2.boolean().optional(),
|
|
84
|
+
/** Logger used when `log` is enabled (console-compatible). */
|
|
57
85
|
logger: z$2.unknown().default(console),
|
|
86
|
+
/** Optional output dotenv file path to write after composition. */
|
|
58
87
|
outputPath: z$2.string().optional(),
|
|
88
|
+
/** Dotenv search paths (ordered). */
|
|
59
89
|
paths: z$2.array(z$2.string()).optional(),
|
|
90
|
+
/** Private token suffix for private dotenv files (default `local`). */
|
|
60
91
|
privateToken: z$2.string().optional(),
|
|
92
|
+
/** Explicit variables to overlay onto the composed dotenv map. */
|
|
61
93
|
vars: processEnvSchema.optional(),
|
|
62
94
|
});
|
|
63
95
|
/**
|
|
@@ -65,6 +97,14 @@ const getDotenvOptionsSchemaRaw = z$2.object({
|
|
|
65
97
|
* For now, this mirrors the RAW schema; future stages may materialize defaults
|
|
66
98
|
* and narrow shapes as resolution is wired into the host.
|
|
67
99
|
*/
|
|
100
|
+
/**
|
|
101
|
+
* Programmatic options schema (resolved).
|
|
102
|
+
*
|
|
103
|
+
* Today this mirrors {@link getDotenvOptionsSchemaRaw}, but is kept as a distinct export
|
|
104
|
+
* so future resolution steps can narrow or materialize defaults without breaking the API.
|
|
105
|
+
*
|
|
106
|
+
* @public
|
|
107
|
+
*/
|
|
68
108
|
const getDotenvOptionsSchemaResolved = getDotenvOptionsSchemaRaw;
|
|
69
109
|
|
|
70
110
|
/**
|
|
@@ -74,27 +114,55 @@ const getDotenvOptionsSchemaResolved = getDotenvOptionsSchemaRaw;
|
|
|
74
114
|
* reflect normalized types (paths: string[], vars: ProcessEnv), applied in the
|
|
75
115
|
* CLI resolution pipeline.
|
|
76
116
|
*/
|
|
117
|
+
/**
|
|
118
|
+
* CLI options schema (raw).
|
|
119
|
+
*
|
|
120
|
+
* Extends the programmatic options schema with CLI-only flags and stringly inputs
|
|
121
|
+
* which are normalized later by the host resolution pipeline.
|
|
122
|
+
*
|
|
123
|
+
* @public
|
|
124
|
+
*/
|
|
77
125
|
const getDotenvCliOptionsSchemaRaw = getDotenvOptionsSchemaRaw.extend({
|
|
78
126
|
// CLI-specific fields (stringly inputs before preprocessing)
|
|
127
|
+
/** Enable verbose debug output (host-specific). */
|
|
79
128
|
debug: z$2.boolean().optional(),
|
|
129
|
+
/** Fail on validation errors (schema/requiredKeys). */
|
|
80
130
|
strict: z$2.boolean().optional(),
|
|
131
|
+
/** Capture child process stdio (useful for CI/tests). */
|
|
81
132
|
capture: z$2.boolean().optional(),
|
|
133
|
+
/** Emit child env diagnostics (boolean or selected keys). */
|
|
82
134
|
trace: z$2.union([z$2.boolean(), z$2.array(z$2.string())]).optional(),
|
|
135
|
+
/** Enable presentation-time redaction in trace/log output. */
|
|
83
136
|
redact: z$2.boolean().optional(),
|
|
137
|
+
/** Enable entropy warnings in trace/log output. */
|
|
84
138
|
warnEntropy: z$2.boolean().optional(),
|
|
139
|
+
/** Entropy threshold (bits/char) for warnings. */
|
|
85
140
|
entropyThreshold: z$2.number().optional(),
|
|
141
|
+
/** Minimum value length to consider for entropy warnings. */
|
|
86
142
|
entropyMinLength: z$2.number().optional(),
|
|
143
|
+
/** Regex patterns (strings) to suppress entropy warnings by key. */
|
|
87
144
|
entropyWhitelist: z$2.array(z$2.string()).optional(),
|
|
145
|
+
/** Additional key-match patterns (strings) for redaction. */
|
|
88
146
|
redactPatterns: z$2.array(z$2.string()).optional(),
|
|
147
|
+
/** Dotenv search paths provided as a single delimited string. */
|
|
89
148
|
paths: z$2.string().optional(),
|
|
149
|
+
/** Delimiter string used to split `paths`. */
|
|
90
150
|
pathsDelimiter: z$2.string().optional(),
|
|
151
|
+
/** Regex pattern used to split `paths` (takes precedence over delimiter). */
|
|
91
152
|
pathsDelimiterPattern: z$2.string().optional(),
|
|
153
|
+
/** Scripts table in a permissive shape at parse time (validated elsewhere). */
|
|
92
154
|
scripts: z$2.record(z$2.string(), z$2.unknown()).optional(),
|
|
155
|
+
/** Shell selection (`false` for shell-off, string for explicit shell). */
|
|
93
156
|
shell: z$2.union([z$2.boolean(), z$2.string()]).optional(),
|
|
157
|
+
/** Extra variables expressed as a single delimited string of assignments. */
|
|
94
158
|
vars: z$2.string().optional(),
|
|
159
|
+
/** Assignment operator used when parsing `vars`. */
|
|
95
160
|
varsAssignor: z$2.string().optional(),
|
|
161
|
+
/** Regex pattern used as the assignment operator for `vars` parsing. */
|
|
96
162
|
varsAssignorPattern: z$2.string().optional(),
|
|
163
|
+
/** Delimiter string used to split `vars`. */
|
|
97
164
|
varsDelimiter: z$2.string().optional(),
|
|
165
|
+
/** Regex pattern used to split `vars` (takes precedence over delimiter). */
|
|
98
166
|
varsDelimiterPattern: z$2.string().optional(),
|
|
99
167
|
});
|
|
100
168
|
|
|
@@ -118,17 +186,34 @@ const envStringMap = z$2.record(z$2.string(), stringMap);
|
|
|
118
186
|
* Raw configuration schema for get‑dotenv config files (JSON/YAML/JS/TS).
|
|
119
187
|
* Validates allowed top‑level keys without performing path normalization.
|
|
120
188
|
*/
|
|
189
|
+
/**
|
|
190
|
+
* Config schema for discovered get-dotenv configuration documents (raw).
|
|
191
|
+
*
|
|
192
|
+
* This schema validates the allowed top-level keys for configuration files.
|
|
193
|
+
* It does not normalize paths or coerce types beyond Zod’s parsing.
|
|
194
|
+
*
|
|
195
|
+
* @public
|
|
196
|
+
*/
|
|
121
197
|
const getDotenvConfigSchemaRaw = z$2.object({
|
|
198
|
+
/** Root option defaults applied by the host (CLI-like, collapsed families). */
|
|
122
199
|
rootOptionDefaults: getDotenvCliOptionsSchemaRaw.optional(),
|
|
200
|
+
/** Help-time visibility map for root flags (false hides). */
|
|
123
201
|
rootOptionVisibility: visibilityMap.optional(),
|
|
124
|
-
|
|
202
|
+
/** Scripts table used by cmd/batch resolution (validation intentionally permissive here). */
|
|
203
|
+
scripts: z$2.record(z$2.string(), z$2.unknown()).optional(),
|
|
204
|
+
/** Keys required to be present in the final composed environment. */
|
|
125
205
|
requiredKeys: z$2.array(z$2.string()).optional(),
|
|
206
|
+
/** Validation schema (JS/TS only; JSON/YAML loader rejects). */
|
|
126
207
|
schema: z$2.unknown().optional(), // JS/TS-only; loader rejects in JSON/YAML
|
|
208
|
+
/** Public global variables (string-only). */
|
|
127
209
|
vars: stringMap.optional(), // public, global
|
|
210
|
+
/** Public per-environment variables (string-only). */
|
|
128
211
|
envVars: envStringMap.optional(), // public, per-env
|
|
129
212
|
// Dynamic in config (JS/TS only). JSON/YAML loader will reject if set.
|
|
213
|
+
/** Dynamic variable definitions (JS/TS only). */
|
|
130
214
|
dynamic: z$2.unknown().optional(),
|
|
131
215
|
// Per-plugin config bag; validated by plugins/host when used.
|
|
216
|
+
/** Per-plugin config slices keyed by realized mount path (for example, `aws/whoami`). */
|
|
132
217
|
plugins: z$2.record(z$2.string(), z$2.unknown()).optional(),
|
|
133
218
|
});
|
|
134
219
|
/**
|
|
@@ -1018,13 +1103,21 @@ function traceChildEnv(opts) {
|
|
|
1018
1103
|
* Base root CLI defaults (shared; kept untyped here to avoid cross-layer deps).
|
|
1019
1104
|
* Used as the bottom layer for CLI option resolution.
|
|
1020
1105
|
*/
|
|
1106
|
+
const baseScripts = {
|
|
1107
|
+
'git-status': {
|
|
1108
|
+
cmd: 'git branch --show-current && git status -s -u',
|
|
1109
|
+
shell: true,
|
|
1110
|
+
},
|
|
1111
|
+
};
|
|
1021
1112
|
/**
|
|
1022
|
-
* Default values for root CLI options used by the host and helpers as the
|
|
1023
|
-
* baseline layer during option resolution.
|
|
1113
|
+
* Default values for root CLI options used by the host and helpers as the baseline layer during option resolution.
|
|
1024
1114
|
*
|
|
1025
|
-
* These defaults correspond to the
|
|
1026
|
-
*
|
|
1027
|
-
* configuration `rootOptionDefaults`
|
|
1115
|
+
* These defaults correspond to the “stringly” root surface (see `RootOptionsShape`) and are merged by precedence with:
|
|
1116
|
+
* - create-time overrides
|
|
1117
|
+
* - any discovered configuration `rootOptionDefaults`
|
|
1118
|
+
* - and finally CLI flags at runtime
|
|
1119
|
+
*
|
|
1120
|
+
* @public
|
|
1028
1121
|
*/
|
|
1029
1122
|
const baseRootOptionDefaults = {
|
|
1030
1123
|
dotenvToken: '.env',
|
|
@@ -1038,12 +1131,7 @@ const baseRootOptionDefaults = {
|
|
|
1038
1131
|
paths: './',
|
|
1039
1132
|
pathsDelimiter: ' ',
|
|
1040
1133
|
privateToken: 'local',
|
|
1041
|
-
scripts:
|
|
1042
|
-
'git-status': {
|
|
1043
|
-
cmd: 'git branch --show-current && git status -s -u',
|
|
1044
|
-
shell: true,
|
|
1045
|
-
},
|
|
1046
|
-
},
|
|
1134
|
+
scripts: baseScripts,
|
|
1047
1135
|
shell: true,
|
|
1048
1136
|
vars: '',
|
|
1049
1137
|
varsAssignor: '=',
|
|
@@ -1590,6 +1678,14 @@ async function _execNormalized(command, shell, opts = {}) {
|
|
|
1590
1678
|
return out;
|
|
1591
1679
|
}
|
|
1592
1680
|
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Execute a command and capture stdout/stderr (buffered).
|
|
1683
|
+
*
|
|
1684
|
+
* @param command - Command string (shell) or argv array (shell-off supported).
|
|
1685
|
+
* @param shell - Shell setting (false for plain execution).
|
|
1686
|
+
* @param opts - Execution options (cwd/env/timeout).
|
|
1687
|
+
* @returns A promise resolving to the captured result.
|
|
1688
|
+
*/
|
|
1593
1689
|
async function runCommandResult(command, shell, opts = {}) {
|
|
1594
1690
|
// Build opts without injecting undefined (exactOptionalPropertyTypes-safe)
|
|
1595
1691
|
const coreOpts = { stdio: 'pipe' };
|
|
@@ -1604,6 +1700,14 @@ async function runCommandResult(command, shell, opts = {}) {
|
|
|
1604
1700
|
}
|
|
1605
1701
|
return _execNormalized(command, shell, coreOpts);
|
|
1606
1702
|
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Execute a command and return its exit code.
|
|
1705
|
+
*
|
|
1706
|
+
* @param command - Command string (shell) or argv array (shell-off supported).
|
|
1707
|
+
* @param shell - Shell setting (false for plain execution).
|
|
1708
|
+
* @param opts - Execution options (cwd/env/stdio).
|
|
1709
|
+
* @returns A promise resolving to the process exit code.
|
|
1710
|
+
*/
|
|
1607
1711
|
async function runCommand(command, shell, opts) {
|
|
1608
1712
|
// Build opts without injecting undefined (exactOptionalPropertyTypes-safe)
|
|
1609
1713
|
const callOpts = {};
|
|
@@ -2063,6 +2167,16 @@ function evaluateDynamicOptions(root, resolved) {
|
|
|
2063
2167
|
visit(root);
|
|
2064
2168
|
}
|
|
2065
2169
|
|
|
2170
|
+
/**
|
|
2171
|
+
* Initialize a {@link GetDotenvCli} instance with help configuration and safe defaults.
|
|
2172
|
+
*
|
|
2173
|
+
* @remarks
|
|
2174
|
+
* This is a low-level initializer used by the host constructor to keep `GetDotenvCli.ts`
|
|
2175
|
+
* small and to centralize help/output behavior.
|
|
2176
|
+
*
|
|
2177
|
+
* @param cli - The CLI instance to initialize.
|
|
2178
|
+
* @param headerGetter - Callback returning an optional help header string.
|
|
2179
|
+
*/
|
|
2066
2180
|
function initializeInstance(cli, headerGetter) {
|
|
2067
2181
|
// Configure grouped help: show only base options in default "Options";
|
|
2068
2182
|
// subcommands show all of their own options.
|
|
@@ -3016,18 +3130,49 @@ function applyRootVisibility(program, visibility) {
|
|
|
3016
3130
|
function applyAwsContext(out, ctx, setProcessEnv = true) {
|
|
3017
3131
|
const { profile, region, credentials } = out;
|
|
3018
3132
|
if (setProcessEnv) {
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3133
|
+
// Ensure AWS credential sources are mutually exclusive.
|
|
3134
|
+
// The AWS SDK warns (and may change precedence in future) when both
|
|
3135
|
+
// AWS_PROFILE and AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY are set.
|
|
3136
|
+
const clear = (keys) => {
|
|
3137
|
+
for (const k of keys) {
|
|
3138
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
3139
|
+
delete process.env[k];
|
|
3023
3140
|
}
|
|
3024
|
-
}
|
|
3141
|
+
};
|
|
3142
|
+
const clearProfileVars = () => {
|
|
3143
|
+
clear(['AWS_PROFILE', 'AWS_DEFAULT_PROFILE', 'AWS_SDK_LOAD_CONFIG']);
|
|
3144
|
+
};
|
|
3145
|
+
const clearStaticCreds = () => {
|
|
3146
|
+
clear([
|
|
3147
|
+
'AWS_ACCESS_KEY_ID',
|
|
3148
|
+
'AWS_SECRET_ACCESS_KEY',
|
|
3149
|
+
'AWS_SESSION_TOKEN',
|
|
3150
|
+
]);
|
|
3151
|
+
};
|
|
3152
|
+
// Mode A: exported/static credentials (clear profile vars)
|
|
3025
3153
|
if (credentials) {
|
|
3154
|
+
clearProfileVars();
|
|
3026
3155
|
process.env.AWS_ACCESS_KEY_ID = credentials.accessKeyId;
|
|
3027
3156
|
process.env.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey;
|
|
3028
3157
|
if (credentials.sessionToken !== undefined) {
|
|
3029
3158
|
process.env.AWS_SESSION_TOKEN = credentials.sessionToken;
|
|
3030
3159
|
}
|
|
3160
|
+
else {
|
|
3161
|
+
delete process.env.AWS_SESSION_TOKEN;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
else if (profile) {
|
|
3165
|
+
// Mode B: profile-based (SSO) credentials (clear static creds)
|
|
3166
|
+
clearStaticCreds();
|
|
3167
|
+
process.env.AWS_PROFILE = profile;
|
|
3168
|
+
process.env.AWS_DEFAULT_PROFILE = profile;
|
|
3169
|
+
process.env.AWS_SDK_LOAD_CONFIG = '1';
|
|
3170
|
+
}
|
|
3171
|
+
if (region) {
|
|
3172
|
+
process.env.AWS_REGION = region;
|
|
3173
|
+
if (!process.env.AWS_DEFAULT_REGION) {
|
|
3174
|
+
process.env.AWS_DEFAULT_REGION = region;
|
|
3175
|
+
}
|
|
3031
3176
|
}
|
|
3032
3177
|
}
|
|
3033
3178
|
// Always publish minimal, non-sensitive metadata
|
|
@@ -3038,7 +3183,7 @@ function applyAwsContext(out, ctx, setProcessEnv = true) {
|
|
|
3038
3183
|
};
|
|
3039
3184
|
}
|
|
3040
3185
|
|
|
3041
|
-
const
|
|
3186
|
+
const AWS_CLI_TIMEOUT_MS = 15_000;
|
|
3042
3187
|
const trim = (s) => (typeof s === 'string' ? s.trim() : '');
|
|
3043
3188
|
const unquote = (s) => s.length >= 2 &&
|
|
3044
3189
|
((s.startsWith('"') && s.endsWith('"')) ||
|
|
@@ -3073,6 +3218,7 @@ const parseExportCredentialsJson = (txt) => {
|
|
|
3073
3218
|
/**
|
|
3074
3219
|
* Parse AWS credentials from environment-export output (shell-agnostic).
|
|
3075
3220
|
* Supports POSIX `export KEY=VAL` and PowerShell `$Env:KEY=VAL`.
|
|
3221
|
+
* Also supports AWS CLI `windows-cmd` (`set KEY=VAL`) and `env-no-export` (`KEY=VAL`).
|
|
3076
3222
|
*
|
|
3077
3223
|
* @param txt - Raw stdout text from the AWS CLI.
|
|
3078
3224
|
* @returns Parsed credentials, or `undefined` when the input is not recognized.
|
|
@@ -3086,12 +3232,17 @@ const parseExportCredentialsEnv = (txt) => {
|
|
|
3086
3232
|
const line = raw.trim();
|
|
3087
3233
|
if (!line)
|
|
3088
3234
|
continue;
|
|
3089
|
-
// POSIX: export AWS_ACCESS_KEY_ID=...,
|
|
3235
|
+
// POSIX: export AWS_ACCESS_KEY_ID=..., ...
|
|
3090
3236
|
let m = /^export\s+([A-Z0-9_]+)\s*=\s*(.+)$/.exec(line);
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
m = /^\$Env:([A-Z0-9_]+)\s*=\s*(.+)
|
|
3094
|
-
|
|
3237
|
+
// PowerShell: $Env:AWS_ACCESS_KEY_ID="...", etc.
|
|
3238
|
+
if (!m)
|
|
3239
|
+
m = /^\$Env:([A-Z0-9_]+)\s*=\s*(.+)$/i.exec(line);
|
|
3240
|
+
// Windows cmd: set AWS_ACCESS_KEY_ID=..., etc.
|
|
3241
|
+
if (!m)
|
|
3242
|
+
m = /^(?:set)\s+([A-Z0-9_]+)\s*=\s*(.+)$/i.exec(line);
|
|
3243
|
+
// env-no-export: AWS_ACCESS_KEY_ID=..., etc.
|
|
3244
|
+
if (!m)
|
|
3245
|
+
m = /^([A-Z0-9_]+)\s*=\s*(.+)$/.exec(line);
|
|
3095
3246
|
if (!m)
|
|
3096
3247
|
continue;
|
|
3097
3248
|
const k = m[1];
|
|
@@ -3116,7 +3267,7 @@ const parseExportCredentialsEnv = (txt) => {
|
|
|
3116
3267
|
};
|
|
3117
3268
|
return undefined;
|
|
3118
3269
|
};
|
|
3119
|
-
const getAwsConfigure = async (key, profile, timeoutMs =
|
|
3270
|
+
const getAwsConfigure = async (key, profile, timeoutMs = AWS_CLI_TIMEOUT_MS) => {
|
|
3120
3271
|
const r = await runCommandResult(['aws', 'configure', 'get', key, '--profile', profile], false, {
|
|
3121
3272
|
env: process.env,
|
|
3122
3273
|
timeoutMs,
|
|
@@ -3131,30 +3282,43 @@ const getAwsConfigure = async (key, profile, timeoutMs = DEFAULT_TIMEOUT_MS) =>
|
|
|
3131
3282
|
}
|
|
3132
3283
|
return undefined;
|
|
3133
3284
|
};
|
|
3134
|
-
const exportCredentials = async (profile, timeoutMs =
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3285
|
+
const exportCredentials = async (profile, timeoutMs = AWS_CLI_TIMEOUT_MS) => {
|
|
3286
|
+
const tryExport = async (format) => {
|
|
3287
|
+
const argv = [
|
|
3288
|
+
'aws',
|
|
3289
|
+
'configure',
|
|
3290
|
+
'export-credentials',
|
|
3291
|
+
'--profile',
|
|
3292
|
+
profile,
|
|
3293
|
+
...(format ? ['--format', format] : []),
|
|
3294
|
+
];
|
|
3295
|
+
const r = await runCommandResult(argv, false, {
|
|
3296
|
+
env: process.env,
|
|
3297
|
+
timeoutMs,
|
|
3298
|
+
});
|
|
3299
|
+
if (r.exitCode !== 0)
|
|
3300
|
+
return undefined;
|
|
3301
|
+
const out = trim(r.stdout);
|
|
3302
|
+
if (!out)
|
|
3303
|
+
return undefined;
|
|
3304
|
+
// Some formats produce JSON ("process"), some produce shell-ish env lines.
|
|
3305
|
+
return parseExportCredentialsJson(out) ?? parseExportCredentialsEnv(out);
|
|
3306
|
+
};
|
|
3307
|
+
// Prefer the default/JSON "process" format first; then fall back to shell env outputs.
|
|
3308
|
+
// Note: AWS CLI v2 supports: process | env | env-no-export | powershell | windows-cmd
|
|
3309
|
+
const formats = [
|
|
3310
|
+
'process',
|
|
3311
|
+
...(process.platform === 'win32'
|
|
3312
|
+
? ['powershell', 'windows-cmd', 'env', 'env-no-export']
|
|
3313
|
+
: ['env', 'env-no-export']),
|
|
3314
|
+
];
|
|
3315
|
+
for (const f of formats) {
|
|
3316
|
+
const creds = await tryExport(f);
|
|
3154
3317
|
if (creds)
|
|
3155
3318
|
return creds;
|
|
3156
3319
|
}
|
|
3157
|
-
|
|
3320
|
+
// Final fallback: no --format (AWS CLI default output)
|
|
3321
|
+
return tryExport(undefined);
|
|
3158
3322
|
};
|
|
3159
3323
|
/**
|
|
3160
3324
|
* Resolve AWS context (profile, region, credentials) using configuration and environment.
|
|
@@ -3186,31 +3350,27 @@ const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
|
3186
3350
|
out.region = region;
|
|
3187
3351
|
return out;
|
|
3188
3352
|
}
|
|
3189
|
-
// Env-first credentials.
|
|
3190
3353
|
let credentials;
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
const envToken = trim(process.env.AWS_SESSION_TOKEN);
|
|
3194
|
-
if (envId && envSecret) {
|
|
3195
|
-
credentials = {
|
|
3196
|
-
accessKeyId: envId,
|
|
3197
|
-
secretAccessKey: envSecret,
|
|
3198
|
-
...(envToken ? { sessionToken: envToken } : {}),
|
|
3199
|
-
};
|
|
3200
|
-
}
|
|
3201
|
-
else if (profile) {
|
|
3354
|
+
// Profile wins over ambient env creds when present (from flags/config/dotenv).
|
|
3355
|
+
if (profile) {
|
|
3202
3356
|
// Try export-credentials
|
|
3203
3357
|
credentials = await exportCredentials(profile);
|
|
3204
3358
|
// On failure, detect SSO and optionally login then retry
|
|
3205
3359
|
if (!credentials) {
|
|
3206
3360
|
const ssoSession = await getAwsConfigure('sso_session', profile);
|
|
3207
|
-
|
|
3361
|
+
// Legacy SSO profiles use sso_start_url/sso_region rather than sso_session.
|
|
3362
|
+
const ssoStartUrl = await getAwsConfigure('sso_start_url', profile);
|
|
3363
|
+
const looksSSO = (typeof ssoSession === 'string' && ssoSession.length > 0) ||
|
|
3364
|
+
(typeof ssoStartUrl === 'string' && ssoStartUrl.length > 0);
|
|
3208
3365
|
if (looksSSO && cfg.loginOnDemand) {
|
|
3209
|
-
//
|
|
3210
|
-
await
|
|
3366
|
+
// Interactive login (no timeout by default), then retry export once.
|
|
3367
|
+
const exit = await runCommand(['aws', 'sso', 'login', '--profile', profile], false, {
|
|
3211
3368
|
env: process.env,
|
|
3212
|
-
|
|
3369
|
+
stdio: 'inherit',
|
|
3213
3370
|
});
|
|
3371
|
+
if (exit !== 0) {
|
|
3372
|
+
throw new Error(`aws sso login failed for profile '${profile}' (exit ${String(exit)})`);
|
|
3373
|
+
}
|
|
3214
3374
|
credentials = await exportCredentials(profile);
|
|
3215
3375
|
}
|
|
3216
3376
|
}
|
|
@@ -3228,6 +3388,19 @@ const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
|
3228
3388
|
}
|
|
3229
3389
|
}
|
|
3230
3390
|
}
|
|
3391
|
+
else {
|
|
3392
|
+
// Env-first credentials when no profile is present.
|
|
3393
|
+
const envId = trim(process.env.AWS_ACCESS_KEY_ID);
|
|
3394
|
+
const envSecret = trim(process.env.AWS_SECRET_ACCESS_KEY);
|
|
3395
|
+
const envToken = trim(process.env.AWS_SESSION_TOKEN);
|
|
3396
|
+
if (envId && envSecret) {
|
|
3397
|
+
credentials = {
|
|
3398
|
+
accessKeyId: envId,
|
|
3399
|
+
secretAccessKey: envSecret,
|
|
3400
|
+
...(envToken ? { sessionToken: envToken } : {}),
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3231
3404
|
// Final region resolution
|
|
3232
3405
|
if (!region && profile)
|
|
3233
3406
|
region = await getAwsConfigure('region', profile);
|
|
@@ -3244,16 +3417,234 @@ const resolveAwsContext = async ({ dotenv, cfg, }) => {
|
|
|
3244
3417
|
};
|
|
3245
3418
|
|
|
3246
3419
|
/**
|
|
3247
|
-
*
|
|
3420
|
+
* Create the AWS plugin `afterResolve` hook.
|
|
3421
|
+
*
|
|
3422
|
+
* This runs once per invocation after the host resolves dotenv context.
|
|
3423
|
+
*
|
|
3424
|
+
* @param plugin - The AWS plugin instance.
|
|
3425
|
+
* @returns An `afterResolve` hook function suitable for assigning to `plugin.afterResolve`.
|
|
3426
|
+
*
|
|
3427
|
+
* @internal
|
|
3428
|
+
*/
|
|
3429
|
+
function attachAwsAfterResolveHook(plugin) {
|
|
3430
|
+
return async (cli, ctx) => {
|
|
3431
|
+
const cfg = plugin.readConfig(cli);
|
|
3432
|
+
const out = await resolveAwsContext({
|
|
3433
|
+
dotenv: ctx.dotenv,
|
|
3434
|
+
cfg,
|
|
3435
|
+
});
|
|
3436
|
+
applyAwsContext(out, ctx, true);
|
|
3437
|
+
// Optional: low-noise breadcrumb for diagnostics
|
|
3438
|
+
if (process.env.GETDOTENV_DEBUG) {
|
|
3439
|
+
try {
|
|
3440
|
+
const msg = JSON.stringify({
|
|
3441
|
+
profile: out.profile,
|
|
3442
|
+
region: out.region,
|
|
3443
|
+
hasCreds: Boolean(out.credentials),
|
|
3444
|
+
});
|
|
3445
|
+
process.stderr.write(`[aws] afterResolve ${msg}\n`);
|
|
3446
|
+
}
|
|
3447
|
+
catch {
|
|
3448
|
+
/* ignore */
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
};
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
/** @internal */
|
|
3455
|
+
const isRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
3456
|
+
/**
|
|
3457
|
+
* Create an AWS plugin config overlay from Commander-parsed option values.
|
|
3458
|
+
*
|
|
3459
|
+
* This preserves tri-state intent:
|
|
3460
|
+
* - If a flag was not provided, it should not overwrite config-derived defaults.
|
|
3461
|
+
* - If `--no-…` was provided, it must explicitly force the boolean false.
|
|
3462
|
+
*
|
|
3463
|
+
* @param opts - Commander option values for the current invocation.
|
|
3464
|
+
* @returns A partial AWS plugin config object containing only explicit overrides.
|
|
3465
|
+
*
|
|
3466
|
+
* @internal
|
|
3467
|
+
*/
|
|
3468
|
+
function awsConfigOverridesFromCommandOpts(opts) {
|
|
3469
|
+
const o = isRecord(opts) ? opts : {};
|
|
3470
|
+
const overlay = {};
|
|
3471
|
+
// Map boolean toggles (respect explicit --no-*)
|
|
3472
|
+
if (Object.prototype.hasOwnProperty.call(o, 'loginOnDemand')) {
|
|
3473
|
+
overlay.loginOnDemand = Boolean(o.loginOnDemand);
|
|
3474
|
+
}
|
|
3475
|
+
// Strings/enums
|
|
3476
|
+
if (typeof o.profile === 'string')
|
|
3477
|
+
overlay.profile = o.profile;
|
|
3478
|
+
if (typeof o.region === 'string')
|
|
3479
|
+
overlay.region = o.region;
|
|
3480
|
+
if (typeof o.defaultRegion === 'string')
|
|
3481
|
+
overlay.defaultRegion = o.defaultRegion;
|
|
3482
|
+
if (o.strategy === 'cli-export' || o.strategy === 'none') {
|
|
3483
|
+
overlay.strategy = o.strategy;
|
|
3484
|
+
}
|
|
3485
|
+
// Advanced key overrides
|
|
3486
|
+
if (typeof o.profileKey === 'string')
|
|
3487
|
+
overlay.profileKey = o.profileKey;
|
|
3488
|
+
if (typeof o.profileFallbackKey === 'string') {
|
|
3489
|
+
overlay.profileFallbackKey = o.profileFallbackKey;
|
|
3490
|
+
}
|
|
3491
|
+
if (typeof o.regionKey === 'string')
|
|
3492
|
+
overlay.regionKey = o.regionKey;
|
|
3493
|
+
return overlay;
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
/**
|
|
3497
|
+
* Attach the default action for the AWS plugin mount.
|
|
3498
|
+
*
|
|
3499
|
+
* Behavior:
|
|
3500
|
+
* - With args: forwards to AWS CLI (`aws <args...>`) under the established session.
|
|
3501
|
+
* - Without args: session-only establishment (no forward).
|
|
3502
|
+
*
|
|
3503
|
+
* @param cli - The `aws` command mount.
|
|
3504
|
+
* @param plugin - The AWS plugin instance.
|
|
3505
|
+
*
|
|
3506
|
+
* @internal
|
|
3507
|
+
*/
|
|
3508
|
+
function attachAwsDefaultAction(cli, plugin, awsCmd) {
|
|
3509
|
+
awsCmd.action(async (args, opts, thisCommand) => {
|
|
3510
|
+
// Access merged root CLI options (installed by root hooks).
|
|
3511
|
+
const bag = readMergedOptions(thisCommand);
|
|
3512
|
+
const capture = shouldCapture(bag.capture);
|
|
3513
|
+
const underTests = process.env.GETDOTENV_TEST === '1' ||
|
|
3514
|
+
typeof process.env.VITEST_WORKER_ID === 'string';
|
|
3515
|
+
// Build overlay cfg from subcommand flags layered over discovered config.
|
|
3516
|
+
const ctx = cli.getCtx();
|
|
3517
|
+
const cfgBase = plugin.readConfig(cli);
|
|
3518
|
+
const cfg = {
|
|
3519
|
+
...cfgBase,
|
|
3520
|
+
...awsConfigOverridesFromCommandOpts(opts),
|
|
3521
|
+
};
|
|
3522
|
+
// Resolve current context with overrides
|
|
3523
|
+
const out = await resolveAwsContext({
|
|
3524
|
+
dotenv: ctx.dotenv,
|
|
3525
|
+
cfg,
|
|
3526
|
+
});
|
|
3527
|
+
// Publish env/context
|
|
3528
|
+
applyAwsContext(out, ctx, true);
|
|
3529
|
+
// Forward when positional args are present; otherwise session-only.
|
|
3530
|
+
if (args.length > 0) {
|
|
3531
|
+
const argv = ['aws', ...args];
|
|
3532
|
+
const shellSetting = resolveShell(bag.scripts, 'aws', bag.shell);
|
|
3533
|
+
const exit = await runCommand(argv, shellSetting, {
|
|
3534
|
+
env: buildSpawnEnv(process.env, ctx.dotenv),
|
|
3535
|
+
stdio: capture ? 'pipe' : 'inherit',
|
|
3536
|
+
});
|
|
3537
|
+
// Deterministic termination (suppressed under tests)
|
|
3538
|
+
if (!underTests) {
|
|
3539
|
+
process.exit(typeof exit === 'number' ? exit : 0);
|
|
3540
|
+
}
|
|
3541
|
+
return;
|
|
3542
|
+
}
|
|
3543
|
+
// Session only: low-noise breadcrumb under debug
|
|
3544
|
+
if (process.env.GETDOTENV_DEBUG) {
|
|
3545
|
+
try {
|
|
3546
|
+
const msg = JSON.stringify({
|
|
3547
|
+
profile: out.profile,
|
|
3548
|
+
region: out.region,
|
|
3549
|
+
hasCreds: Boolean(out.credentials),
|
|
3550
|
+
});
|
|
3551
|
+
process.stderr.write(`[aws] session established ${msg}\n`);
|
|
3552
|
+
}
|
|
3553
|
+
catch {
|
|
3554
|
+
/* ignore */
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
if (!underTests)
|
|
3558
|
+
process.exit(0);
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
/**
|
|
3563
|
+
* Attach options/arguments for the AWS plugin mount.
|
|
3564
|
+
*
|
|
3565
|
+
* @param cli - The `aws` command mount.
|
|
3566
|
+
* @param plugin - The AWS plugin instance (for dynamic option descriptions).
|
|
3567
|
+
*
|
|
3568
|
+
* @internal
|
|
3248
3569
|
*/
|
|
3249
|
-
|
|
3570
|
+
/** @hidden */
|
|
3571
|
+
function attachAwsOptions(cli, plugin) {
|
|
3572
|
+
return (cli
|
|
3573
|
+
// Description is owned by the plugin index (src/plugins/aws/index.ts).
|
|
3574
|
+
.enablePositionalOptions()
|
|
3575
|
+
.passThroughOptions()
|
|
3576
|
+
.allowUnknownOption(true)
|
|
3577
|
+
// Boolean toggles with dynamic help labels (effective defaults)
|
|
3578
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--login-on-demand', (_bag, cfg) => `attempt aws sso login on-demand${cfg.loginOnDemand ? ' (default)' : ''}`))
|
|
3579
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--no-login-on-demand', (_bag, cfg) => `disable sso login on-demand${cfg.loginOnDemand === false ? ' (default)' : ''}`))
|
|
3580
|
+
// Strings / enums
|
|
3581
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile <string>', (_bag, cfg) => `AWS profile name${cfg.profile ? ` (default: ${JSON.stringify(cfg.profile)})` : ''}`))
|
|
3582
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--region <string>', (_bag, cfg) => `AWS region${cfg.region ? ` (default: ${JSON.stringify(cfg.region)})` : ''}`))
|
|
3583
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--default-region <string>', (_bag, cfg) => `fallback region${cfg.defaultRegion ? ` (default: ${JSON.stringify(cfg.defaultRegion)})` : ''}`))
|
|
3584
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--strategy <string>', (_bag, cfg) => `credential acquisition strategy: cli-export|none${cfg.strategy ? ` (default: ${JSON.stringify(cfg.strategy)})` : ''}`))
|
|
3585
|
+
// Advanced key overrides
|
|
3586
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile-key <string>', (_bag, cfg) => `dotenv/config key for local profile${cfg.profileKey ? ` (default: ${JSON.stringify(cfg.profileKey)})` : ''}`))
|
|
3587
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--profile-fallback-key <string>', (_bag, cfg) => `fallback dotenv/config key for profile${cfg.profileFallbackKey ? ` (default: ${JSON.stringify(cfg.profileFallbackKey)})` : ''}`))
|
|
3588
|
+
.addOption(plugin.createPluginDynamicOption(cli, '--region-key <string>', (_bag, cfg) => `dotenv/config key for region${cfg.regionKey ? ` (default: ${JSON.stringify(cfg.regionKey)})` : ''}`))
|
|
3589
|
+
// Accept any extra operands so Commander does not error when tokens appear after "--".
|
|
3590
|
+
.argument('[args...]'));
|
|
3591
|
+
}
|
|
3592
|
+
|
|
3593
|
+
/**
|
|
3594
|
+
* Attach the AWS plugin `preSubcommand` hook.
|
|
3595
|
+
*
|
|
3596
|
+
* Ensures `aws --profile/--region <child>` applies the AWS session setup before
|
|
3597
|
+
* child subcommand execution.
|
|
3598
|
+
*
|
|
3599
|
+
* @param cli - The `aws` command mount.
|
|
3600
|
+
* @param plugin - The AWS plugin instance.
|
|
3601
|
+
*
|
|
3602
|
+
* @internal
|
|
3603
|
+
*/
|
|
3604
|
+
function attachAwsPreSubcommandHook(cli, plugin) {
|
|
3605
|
+
cli.hook('preSubcommand', async (thisCommand) => {
|
|
3606
|
+
// Avoid side effects for help rendering.
|
|
3607
|
+
if (process.argv.includes('-h') || process.argv.includes('--help'))
|
|
3608
|
+
return;
|
|
3609
|
+
const ctx = cli.getCtx();
|
|
3610
|
+
const cfgBase = plugin.readConfig(cli);
|
|
3611
|
+
const cfg = {
|
|
3612
|
+
...cfgBase,
|
|
3613
|
+
...awsConfigOverridesFromCommandOpts(thisCommand.opts()),
|
|
3614
|
+
};
|
|
3615
|
+
const out = await resolveAwsContext({
|
|
3616
|
+
dotenv: ctx.dotenv,
|
|
3617
|
+
cfg,
|
|
3618
|
+
});
|
|
3619
|
+
applyAwsContext(out, ctx, true);
|
|
3620
|
+
});
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3623
|
+
/**
|
|
3624
|
+
* AWS plugin configuration schema.
|
|
3625
|
+
*
|
|
3626
|
+
* @remarks
|
|
3627
|
+
* This Zod schema is used by the host to validate the `plugins.aws` config slice.
|
|
3628
|
+
*
|
|
3629
|
+
* @public
|
|
3630
|
+
* @hidden
|
|
3631
|
+
*/
|
|
3632
|
+
const awsPluginConfigSchema = z$2.object({
|
|
3633
|
+
/** Preferred AWS profile name (overrides dotenv-derived profile keys when set). */
|
|
3250
3634
|
profile: z$2.string().optional(),
|
|
3635
|
+
/** Preferred AWS region (overrides dotenv-derived region key when set). */
|
|
3251
3636
|
region: z$2.string().optional(),
|
|
3637
|
+
/** Fallback region when region cannot be resolved from config/dotenv/AWS CLI. */
|
|
3252
3638
|
defaultRegion: z$2.string().optional(),
|
|
3639
|
+
/** Dotenv/config key for local profile lookup (default `AWS_LOCAL_PROFILE`). */
|
|
3253
3640
|
profileKey: z$2.string().default('AWS_LOCAL_PROFILE').optional(),
|
|
3641
|
+
/** Dotenv/config fallback key for profile lookup (default `AWS_PROFILE`). */
|
|
3254
3642
|
profileFallbackKey: z$2.string().default('AWS_PROFILE').optional(),
|
|
3643
|
+
/** Dotenv/config key for region lookup (default `AWS_REGION`). */
|
|
3255
3644
|
regionKey: z$2.string().default('AWS_REGION').optional(),
|
|
3645
|
+
/** Credential acquisition strategy (`cli-export` to resolve via AWS CLI, or `none` to skip). */
|
|
3256
3646
|
strategy: z$2.enum(['cli-export', 'none']).default('cli-export').optional(),
|
|
3647
|
+
/** When true, attempt `aws sso login` on-demand when credential export fails for an SSO profile. */
|
|
3257
3648
|
loginOnDemand: z$2.boolean().default(false).optional(),
|
|
3258
3649
|
});
|
|
3259
3650
|
|
|
@@ -3270,129 +3661,16 @@ const AwsPluginConfigSchema = z$2.object({
|
|
|
3270
3661
|
const awsPlugin = () => {
|
|
3271
3662
|
const plugin = definePlugin({
|
|
3272
3663
|
ns: 'aws',
|
|
3273
|
-
configSchema:
|
|
3274
|
-
setup
|
|
3275
|
-
|
|
3276
|
-
cli
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
.passThroughOptions()
|
|
3280
|
-
.allowUnknownOption(true)
|
|
3281
|
-
// Boolean toggles with dynamic help labels (effective defaults)
|
|
3282
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--login-on-demand', (_bag, cfg) => `attempt aws sso login on-demand${cfg.loginOnDemand ? ' (default)' : ''}`))
|
|
3283
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--no-login-on-demand', (_bag, cfg) => `disable sso login on-demand${cfg.loginOnDemand === false ? ' (default)' : ''}`))
|
|
3284
|
-
// Strings / enums
|
|
3285
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--profile <string>', (_bag, cfg) => `AWS profile name${cfg.profile ? ` (default: ${JSON.stringify(cfg.profile)})` : ''}`))
|
|
3286
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--region <string>', (_bag, cfg) => `AWS region${cfg.region ? ` (default: ${JSON.stringify(cfg.region)})` : ''}`))
|
|
3287
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--default-region <string>', (_bag, cfg) => `fallback region${cfg.defaultRegion ? ` (default: ${JSON.stringify(cfg.defaultRegion)})` : ''}`))
|
|
3288
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--strategy <string>', (_bag, cfg) => `credential acquisition strategy: cli-export|none${cfg.strategy ? ` (default: ${JSON.stringify(cfg.strategy)})` : ''}`))
|
|
3289
|
-
// Advanced key overrides
|
|
3290
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--profile-key <string>', (_bag, cfg) => `dotenv/config key for local profile${cfg.profileKey ? ` (default: ${JSON.stringify(cfg.profileKey)})` : ''}`))
|
|
3291
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--profile-fallback-key <string>', (_bag, cfg) => `fallback dotenv/config key for profile${cfg.profileFallbackKey ? ` (default: ${JSON.stringify(cfg.profileFallbackKey)})` : ''}`))
|
|
3292
|
-
.addOption(plugin.createPluginDynamicOption(cli, '--region-key <string>', (_bag, cfg) => `dotenv/config key for region${cfg.regionKey ? ` (default: ${JSON.stringify(cfg.regionKey)})` : ''}`))
|
|
3293
|
-
// Accept any extra operands so Commander does not error when tokens appear after "--".
|
|
3294
|
-
.argument('[args...]')
|
|
3295
|
-
.action(async (args, opts, thisCommand) => {
|
|
3296
|
-
const pluginInst = plugin;
|
|
3297
|
-
// Access merged root CLI options (installed by passOptions())
|
|
3298
|
-
const bag = readMergedOptions(thisCommand);
|
|
3299
|
-
const capture = shouldCapture(bag.capture);
|
|
3300
|
-
const underTests = process.env.GETDOTENV_TEST === '1' ||
|
|
3301
|
-
typeof process.env.VITEST_WORKER_ID === 'string';
|
|
3302
|
-
// Build overlay cfg from subcommand flags layered over discovered config.
|
|
3303
|
-
const ctx = cli.getCtx();
|
|
3304
|
-
const cfgBase = pluginInst.readConfig(cli);
|
|
3305
|
-
const o = opts;
|
|
3306
|
-
const overlay = {};
|
|
3307
|
-
// Map boolean toggles (respect explicit --no-*)
|
|
3308
|
-
if (Object.prototype.hasOwnProperty.call(o, 'loginOnDemand'))
|
|
3309
|
-
overlay.loginOnDemand = Boolean(o.loginOnDemand);
|
|
3310
|
-
// Strings/enums
|
|
3311
|
-
if (typeof o.profile === 'string')
|
|
3312
|
-
overlay.profile = o.profile;
|
|
3313
|
-
if (typeof o.region === 'string')
|
|
3314
|
-
overlay.region = o.region;
|
|
3315
|
-
if (typeof o.defaultRegion === 'string')
|
|
3316
|
-
overlay.defaultRegion = o.defaultRegion;
|
|
3317
|
-
if (typeof o.strategy === 'string')
|
|
3318
|
-
overlay.strategy = o.strategy;
|
|
3319
|
-
// Advanced key overrides
|
|
3320
|
-
if (typeof o.profileKey === 'string')
|
|
3321
|
-
overlay.profileKey = o.profileKey;
|
|
3322
|
-
if (typeof o.profileFallbackKey === 'string')
|
|
3323
|
-
overlay.profileFallbackKey = o.profileFallbackKey;
|
|
3324
|
-
if (typeof o.regionKey === 'string')
|
|
3325
|
-
overlay.regionKey = o.regionKey;
|
|
3326
|
-
const cfg = {
|
|
3327
|
-
...cfgBase,
|
|
3328
|
-
...overlay,
|
|
3329
|
-
};
|
|
3330
|
-
// Resolve current context with overrides
|
|
3331
|
-
const out = await resolveAwsContext({
|
|
3332
|
-
dotenv: ctx.dotenv,
|
|
3333
|
-
cfg,
|
|
3334
|
-
});
|
|
3335
|
-
// Publish env/context
|
|
3336
|
-
applyAwsContext(out, ctx, true);
|
|
3337
|
-
// Forward when positional args are present; otherwise session-only.
|
|
3338
|
-
if (Array.isArray(args) && args.length > 0) {
|
|
3339
|
-
const argv = ['aws', ...args];
|
|
3340
|
-
const shellSetting = resolveShell(bag.scripts, 'aws', bag.shell);
|
|
3341
|
-
const exit = await runCommand(argv, shellSetting, {
|
|
3342
|
-
env: buildSpawnEnv(process.env, ctx.dotenv),
|
|
3343
|
-
stdio: capture ? 'pipe' : 'inherit',
|
|
3344
|
-
});
|
|
3345
|
-
// Deterministic termination (suppressed under tests)
|
|
3346
|
-
if (!underTests) {
|
|
3347
|
-
process.exit(typeof exit === 'number' ? exit : 0);
|
|
3348
|
-
}
|
|
3349
|
-
return;
|
|
3350
|
-
}
|
|
3351
|
-
else {
|
|
3352
|
-
// Session only: low-noise breadcrumb under debug
|
|
3353
|
-
if (process.env.GETDOTENV_DEBUG) {
|
|
3354
|
-
try {
|
|
3355
|
-
const msg = JSON.stringify({
|
|
3356
|
-
profile: out.profile,
|
|
3357
|
-
region: out.region,
|
|
3358
|
-
hasCreds: Boolean(out.credentials),
|
|
3359
|
-
});
|
|
3360
|
-
process.stderr.write(`[aws] session established ${msg}\n`);
|
|
3361
|
-
}
|
|
3362
|
-
catch {
|
|
3363
|
-
/* ignore */
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
if (!underTests)
|
|
3367
|
-
process.exit(0);
|
|
3368
|
-
return;
|
|
3369
|
-
}
|
|
3370
|
-
});
|
|
3664
|
+
configSchema: awsPluginConfigSchema,
|
|
3665
|
+
setup(cli) {
|
|
3666
|
+
cli.description('Establish an AWS session and optionally forward to the AWS CLI');
|
|
3667
|
+
const awsCmd = attachAwsOptions(cli, plugin);
|
|
3668
|
+
attachAwsPreSubcommandHook(cli, plugin);
|
|
3669
|
+
attachAwsDefaultAction(cli, plugin, awsCmd);
|
|
3371
3670
|
return undefined;
|
|
3372
3671
|
},
|
|
3373
|
-
afterResolve: async (_cli, ctx) => {
|
|
3374
|
-
const cfg = plugin.readConfig(_cli);
|
|
3375
|
-
const out = await resolveAwsContext({
|
|
3376
|
-
dotenv: ctx.dotenv,
|
|
3377
|
-
cfg,
|
|
3378
|
-
});
|
|
3379
|
-
applyAwsContext(out, ctx, true);
|
|
3380
|
-
// Optional: low-noise breadcrumb for diagnostics
|
|
3381
|
-
if (process.env.GETDOTENV_DEBUG) {
|
|
3382
|
-
try {
|
|
3383
|
-
const msg = JSON.stringify({
|
|
3384
|
-
profile: out.profile,
|
|
3385
|
-
region: out.region,
|
|
3386
|
-
hasCreds: Boolean(out.credentials),
|
|
3387
|
-
});
|
|
3388
|
-
process.stderr.write(`[aws] afterResolve ${msg}\n`);
|
|
3389
|
-
}
|
|
3390
|
-
catch {
|
|
3391
|
-
/* ignore */
|
|
3392
|
-
}
|
|
3393
|
-
}
|
|
3394
|
-
},
|
|
3395
3672
|
});
|
|
3673
|
+
plugin.afterResolve = attachAwsAfterResolveHook(plugin);
|
|
3396
3674
|
return plugin;
|
|
3397
3675
|
};
|
|
3398
3676
|
|
|
@@ -13854,21 +14132,79 @@ class GetCallerIdentityCommand extends Command
|
|
|
13854
14132
|
}
|
|
13855
14133
|
|
|
13856
14134
|
/**
|
|
13857
|
-
*
|
|
13858
|
-
*
|
|
14135
|
+
* Attach the default action for the `aws whoami` command.
|
|
14136
|
+
*
|
|
14137
|
+
* This behavior executes only when `aws whoami` is invoked without a subcommand.
|
|
14138
|
+
*
|
|
14139
|
+
* @param cli - The `whoami` command mount.
|
|
14140
|
+
* @returns Nothing.
|
|
14141
|
+
*/
|
|
14142
|
+
function attachWhoamiDefaultAction(cli) {
|
|
14143
|
+
cli.action(async () => {
|
|
14144
|
+
// The AWS SDK default providers will read credentials from process.env,
|
|
14145
|
+
// which the aws parent has already populated.
|
|
14146
|
+
const client = new STSClient$1();
|
|
14147
|
+
const result = await client.send(new GetCallerIdentityCommand());
|
|
14148
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14149
|
+
});
|
|
14150
|
+
}
|
|
14151
|
+
|
|
14152
|
+
/**
|
|
14153
|
+
* Attach options/arguments for the `aws whoami` plugin mount.
|
|
14154
|
+
*
|
|
14155
|
+
* This subcommand currently takes no flags/args; this module exists to keep the
|
|
14156
|
+
* wiring layout consistent across shipped plugins (options vs actions).
|
|
14157
|
+
*
|
|
14158
|
+
* Note: the plugin description is owned by `src/plugins/aws/whoami/index.ts` and
|
|
14159
|
+
* must not be set here.
|
|
14160
|
+
*
|
|
14161
|
+
* @param cli - The `whoami` command mount under `aws`.
|
|
14162
|
+
* @returns The same `cli` instance for chaining.
|
|
14163
|
+
*
|
|
14164
|
+
* @internal
|
|
14165
|
+
*/
|
|
14166
|
+
/** @hidden */
|
|
14167
|
+
function attachWhoamiOptions(cli) {
|
|
14168
|
+
return cli;
|
|
14169
|
+
}
|
|
14170
|
+
|
|
14171
|
+
/**
|
|
14172
|
+
* Attach the `really` subcommand under `aws whoami`.
|
|
14173
|
+
*
|
|
14174
|
+
* Reads `SECRET_IDENTITY` from the resolved get-dotenv context (`cli.getCtx().dotenv`).
|
|
14175
|
+
*
|
|
14176
|
+
* @param cli - The `whoami` command mount.
|
|
14177
|
+
* @returns Nothing.
|
|
14178
|
+
*/
|
|
14179
|
+
function attachWhoamiReallyAction(cli) {
|
|
14180
|
+
const really = cli
|
|
14181
|
+
.ns('really')
|
|
14182
|
+
.description('Print SECRET_IDENTITY from the resolved dotenv context');
|
|
14183
|
+
really.action(() => {
|
|
14184
|
+
const secretIdentity = really.getCtx().dotenv.SECRET_IDENTITY;
|
|
14185
|
+
console.log(`Your secret identity is ${secretIdentity ?? 'still a secret'}.`);
|
|
14186
|
+
});
|
|
14187
|
+
}
|
|
14188
|
+
|
|
14189
|
+
/**
|
|
14190
|
+
* AWS Whoami plugin factory.
|
|
14191
|
+
*
|
|
14192
|
+
* This plugin demonstrates a “bucket of subcommands” pattern:
|
|
14193
|
+
* - Subcommand behavior is articulated in separate modules as `attach*` helpers.
|
|
14194
|
+
* - Those helpers are not individually composable plugins; they are internal wiring for one plugin instance.
|
|
14195
|
+
*
|
|
14196
|
+
* @returns A plugin instance mounted at `aws whoami`.
|
|
13859
14197
|
*/
|
|
13860
14198
|
const awsWhoamiPlugin = () => definePlugin({
|
|
13861
14199
|
ns: 'whoami',
|
|
13862
14200
|
setup(cli) {
|
|
13863
|
-
cli
|
|
13864
|
-
|
|
13865
|
-
|
|
13866
|
-
|
|
13867
|
-
|
|
13868
|
-
|
|
13869
|
-
|
|
13870
|
-
console.log(JSON.stringify(result, null, 2));
|
|
13871
|
-
});
|
|
14201
|
+
cli.description('Print AWS caller identity (uses parent aws session)');
|
|
14202
|
+
// Options/args (none today, but keep layout consistent with other plugins).
|
|
14203
|
+
const whoami = attachWhoamiOptions(cli);
|
|
14204
|
+
// Default behavior: `getdotenv aws whoami`
|
|
14205
|
+
attachWhoamiDefaultAction(whoami);
|
|
14206
|
+
// Subcommand behavior: `getdotenv aws whoami really`
|
|
14207
|
+
attachWhoamiReallyAction(whoami);
|
|
13872
14208
|
return undefined;
|
|
13873
14209
|
},
|
|
13874
14210
|
});
|
|
@@ -13976,7 +14312,7 @@ const execShellCommandBatch = async ({ command, getDotenvCliOptions, dotenvEnv,
|
|
|
13976
14312
|
/**
|
|
13977
14313
|
* Attach the default "cmd" subcommand action with contextual typing.
|
|
13978
14314
|
*/
|
|
13979
|
-
const
|
|
14315
|
+
const attachBatchCmdAction = (plugin, cli, batchCmd, pluginOpts, cmd) => {
|
|
13980
14316
|
cmd.action(async (commandParts, _subOpts, thisCommand) => {
|
|
13981
14317
|
const mergedBag = readMergedOptions(batchCmd);
|
|
13982
14318
|
const logger = mergedBag.logger;
|
|
@@ -14085,11 +14421,37 @@ const attachDefaultCmdAction$1 = (plugin, cli, batchCmd, pluginOpts, cmd) => {
|
|
|
14085
14421
|
});
|
|
14086
14422
|
};
|
|
14087
14423
|
|
|
14424
|
+
/**
|
|
14425
|
+
* Attach the default `cmd` subcommand under the `batch` command.
|
|
14426
|
+
*
|
|
14427
|
+
* This encapsulates:
|
|
14428
|
+
* - Subcommand construction (`new Command().name('cmd')…`)
|
|
14429
|
+
* - Action wiring
|
|
14430
|
+
* - Mounting as the default subcommand for `batch`
|
|
14431
|
+
*
|
|
14432
|
+
* @param plugin - The batch plugin instance.
|
|
14433
|
+
* @param cli - The batch command mount.
|
|
14434
|
+
* @param batchCmd - The `batch` command (same as `cli` mount).
|
|
14435
|
+
* @param pluginOpts - Batch plugin factory options.
|
|
14436
|
+
*
|
|
14437
|
+
* @internal
|
|
14438
|
+
*/
|
|
14439
|
+
const attachBatchCmdSubcommand = (plugin, cli, batchCmd, pluginOpts) => {
|
|
14440
|
+
const cmdSub = new Command$1()
|
|
14441
|
+
.name('cmd')
|
|
14442
|
+
.description('execute command, conflicts with --command option (default subcommand)')
|
|
14443
|
+
.enablePositionalOptions()
|
|
14444
|
+
.passThroughOptions()
|
|
14445
|
+
.argument('[command...]');
|
|
14446
|
+
attachBatchCmdAction(plugin, cli, batchCmd, pluginOpts, cmdSub);
|
|
14447
|
+
batchCmd.addCommand(cmdSub, { isDefault: true });
|
|
14448
|
+
};
|
|
14449
|
+
|
|
14088
14450
|
/**
|
|
14089
14451
|
* Attach the parent-level action for the batch plugin.
|
|
14090
14452
|
* Handles parent flags (e.g. `getdotenv batch -l`) and delegates to the batch executor.
|
|
14091
14453
|
*/
|
|
14092
|
-
const
|
|
14454
|
+
const attachBatchDefaultAction = (plugin, cli, pluginOpts, parent) => {
|
|
14093
14455
|
parent.action(async function (...args) {
|
|
14094
14456
|
// Commander Unknown generics: [...unknown[], OptionValues, thisCommand]
|
|
14095
14457
|
const thisCommand = args[args.length - 1];
|
|
@@ -14190,24 +14552,71 @@ const attachParentInvoker$1 = (plugin, cli, pluginOpts, parent) => {
|
|
|
14190
14552
|
});
|
|
14191
14553
|
};
|
|
14192
14554
|
|
|
14555
|
+
/**
|
|
14556
|
+
* Attach options/arguments for the batch plugin mount.
|
|
14557
|
+
*
|
|
14558
|
+
* Note: the plugin description is owned by `src/plugins/batch/index.ts` and
|
|
14559
|
+
* must not be set here.
|
|
14560
|
+
*
|
|
14561
|
+
* @param plugin - Batch plugin instance (for dynamic option descriptions).
|
|
14562
|
+
* @param cli - The `batch` command mount.
|
|
14563
|
+
* @returns The same `cli` instance for chaining.
|
|
14564
|
+
*
|
|
14565
|
+
* @internal
|
|
14566
|
+
*/
|
|
14567
|
+
/** @hidden */
|
|
14568
|
+
function attachBatchOptions(plugin, cli) {
|
|
14569
|
+
const GROUP = `plugin:${cli.name()}`;
|
|
14570
|
+
return (cli
|
|
14571
|
+
.enablePositionalOptions()
|
|
14572
|
+
.passThroughOptions()
|
|
14573
|
+
// Dynamic help: show effective defaults from the merged/interpolated plugin config slice.
|
|
14574
|
+
.addOption((() => {
|
|
14575
|
+
const opt = plugin.createPluginDynamicOption(cli, '-p, --pkg-cwd', (_bag, cfg) => `use nearest package directory as current working directory${cfg.pkgCwd ? ' (default)' : ''}`);
|
|
14576
|
+
cli.setOptionGroup(opt, GROUP);
|
|
14577
|
+
return opt;
|
|
14578
|
+
})())
|
|
14579
|
+
.addOption((() => {
|
|
14580
|
+
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 || './')})`);
|
|
14581
|
+
cli.setOptionGroup(opt, GROUP);
|
|
14582
|
+
return opt;
|
|
14583
|
+
})())
|
|
14584
|
+
.addOption((() => {
|
|
14585
|
+
const opt = plugin.createPluginDynamicOption(cli, '-g, --globs <string>', (_bag, cfg) => `space-delimited globs from root path (default: ${JSON.stringify(cfg.globs || '*')})`);
|
|
14586
|
+
cli.setOptionGroup(opt, GROUP);
|
|
14587
|
+
return opt;
|
|
14588
|
+
})())
|
|
14589
|
+
.option('-c, --command <string>', 'command executed according to the base shell resolution')
|
|
14590
|
+
.option('-l, --list', 'list working directories without executing command')
|
|
14591
|
+
.option('-e, --ignore-errors', 'ignore errors and continue with next path')
|
|
14592
|
+
.argument('[command...]'));
|
|
14593
|
+
}
|
|
14594
|
+
|
|
14193
14595
|
/**
|
|
14194
14596
|
* Zod schema for a single script entry (string or object).
|
|
14195
14597
|
*/
|
|
14196
14598
|
const ScriptSchema = z$2.union([
|
|
14197
14599
|
z$2.string(),
|
|
14198
14600
|
z$2.object({
|
|
14601
|
+
/** Command string to execute. */
|
|
14199
14602
|
cmd: z$2.string(),
|
|
14603
|
+
/** Optional shell override for this script entry. */
|
|
14200
14604
|
shell: z$2.union([z$2.string(), z$2.boolean()]).optional(),
|
|
14201
14605
|
}),
|
|
14202
14606
|
]);
|
|
14203
14607
|
/**
|
|
14204
14608
|
* Zod schema for batch plugin configuration.
|
|
14205
14609
|
*/
|
|
14206
|
-
const
|
|
14610
|
+
const batchPluginConfigSchema = z$2.object({
|
|
14611
|
+
/** Optional scripts table scoped to the batch plugin. */
|
|
14207
14612
|
scripts: z$2.record(z$2.string(), ScriptSchema).optional(),
|
|
14613
|
+
/** Optional default shell for batch execution (overridden by per-script shell when present). */
|
|
14208
14614
|
shell: z$2.union([z$2.string(), z$2.boolean()]).optional(),
|
|
14615
|
+
/** Root path for discovery, relative to CWD (or package root when pkgCwd is true). */
|
|
14209
14616
|
rootPath: z$2.string().optional(),
|
|
14617
|
+
/** Space-delimited glob patterns used to discover directories. */
|
|
14210
14618
|
globs: z$2.string().optional(),
|
|
14619
|
+
/** When true, resolve the batch root from the nearest package directory. */
|
|
14211
14620
|
pkgCwd: z$2.boolean().optional(),
|
|
14212
14621
|
});
|
|
14213
14622
|
|
|
@@ -14229,45 +14638,15 @@ const batchPlugin = (opts = {}) => {
|
|
|
14229
14638
|
ns: 'batch',
|
|
14230
14639
|
// Host validates this when config-loader is enabled; plugins may also
|
|
14231
14640
|
// re-validate at action time as a safety belt.
|
|
14232
|
-
configSchema:
|
|
14641
|
+
configSchema: batchPluginConfigSchema,
|
|
14233
14642
|
setup(cli) {
|
|
14234
14643
|
const batchCmd = cli; // mount provided by host
|
|
14235
|
-
|
|
14236
|
-
batchCmd
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
|
|
14240
|
-
|
|
14241
|
-
.addOption((() => {
|
|
14242
|
-
const opt = plugin.createPluginDynamicOption(cli, '-p, --pkg-cwd', (_bag, cfg) => `use nearest package directory as current working directory${cfg.pkgCwd ? ' (default)' : ''}`);
|
|
14243
|
-
cli.setOptionGroup(opt, GROUP);
|
|
14244
|
-
return opt;
|
|
14245
|
-
})())
|
|
14246
|
-
.addOption((() => {
|
|
14247
|
-
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 || './')})`);
|
|
14248
|
-
cli.setOptionGroup(opt, GROUP);
|
|
14249
|
-
return opt;
|
|
14250
|
-
})())
|
|
14251
|
-
.addOption((() => {
|
|
14252
|
-
const opt = plugin.createPluginDynamicOption(cli, '-g, --globs <string>', (_bag, cfg) => `space-delimited globs from root path (default: ${JSON.stringify(cfg.globs || '*')})`);
|
|
14253
|
-
cli.setOptionGroup(opt, GROUP);
|
|
14254
|
-
return opt;
|
|
14255
|
-
})())
|
|
14256
|
-
.option('-c, --command <string>', 'command executed according to the base shell resolution')
|
|
14257
|
-
.option('-l, --list', 'list working directories without executing command')
|
|
14258
|
-
.option('-e, --ignore-errors', 'ignore errors and continue with next path')
|
|
14259
|
-
.argument('[command...]');
|
|
14260
|
-
// Default subcommand "cmd" with contextual typing for args/opts
|
|
14261
|
-
const cmdSub = new Command$1()
|
|
14262
|
-
.name('cmd')
|
|
14263
|
-
.description('execute command, conflicts with --command option (default subcommand)')
|
|
14264
|
-
.enablePositionalOptions()
|
|
14265
|
-
.passThroughOptions()
|
|
14266
|
-
.argument('[command...]');
|
|
14267
|
-
attachDefaultCmdAction$1(plugin, cli, batchCmd, opts, cmdSub);
|
|
14268
|
-
batchCmd.addCommand(cmdSub, { isDefault: true });
|
|
14269
|
-
// Parent invoker (unified naming)
|
|
14270
|
-
attachParentInvoker$1(plugin, cli, opts, batchCmd);
|
|
14644
|
+
batchCmd.description('Batch command execution across multiple working directories.');
|
|
14645
|
+
attachBatchOptions(plugin, batchCmd);
|
|
14646
|
+
// Default subcommand `cmd` (mounted as batch default subcommand)
|
|
14647
|
+
attachBatchCmdSubcommand(plugin, cli, batchCmd, opts);
|
|
14648
|
+
// Default action for the batch command mount (parent flags and positional form)
|
|
14649
|
+
attachBatchDefaultAction(plugin, cli, opts, batchCmd);
|
|
14271
14650
|
return undefined;
|
|
14272
14651
|
},
|
|
14273
14652
|
});
|
|
@@ -14361,14 +14740,11 @@ async function runCmdWithContext(cli, merged, command, _opts) {
|
|
|
14361
14740
|
}
|
|
14362
14741
|
|
|
14363
14742
|
/**
|
|
14364
|
-
* Attach the default "cmd" subcommand action
|
|
14743
|
+
* Attach the default "cmd" subcommand action.
|
|
14365
14744
|
* Mirrors the prior inline implementation in cmd/index.ts.
|
|
14366
14745
|
*/
|
|
14367
|
-
const
|
|
14368
|
-
cmd
|
|
14369
|
-
.enablePositionalOptions()
|
|
14370
|
-
.passThroughOptions()
|
|
14371
|
-
.action(async function (...allArgs) {
|
|
14746
|
+
const attachCmdDefaultAction = (cli, cmd, aliasKey) => {
|
|
14747
|
+
cmd.action(async function (...allArgs) {
|
|
14372
14748
|
// Commander passes: [...positionals, options, thisCommand]
|
|
14373
14749
|
const thisCommand = allArgs[allArgs.length - 1];
|
|
14374
14750
|
const commandParts = allArgs[0];
|
|
@@ -14401,11 +14777,30 @@ const attachDefaultCmdAction = (cli, cmd, aliasKey) => {
|
|
|
14401
14777
|
});
|
|
14402
14778
|
};
|
|
14403
14779
|
|
|
14780
|
+
/**
|
|
14781
|
+
* Attach options/arguments for the cmd plugin mount.
|
|
14782
|
+
*
|
|
14783
|
+
* Note: the plugin description is owned by `src/plugins/cmd/index.ts` and must
|
|
14784
|
+
* not be set here.
|
|
14785
|
+
*
|
|
14786
|
+
* @param cli - The `cmd` command mount.
|
|
14787
|
+
* @returns The same `cli` instance for chaining.
|
|
14788
|
+
*
|
|
14789
|
+
* @internal
|
|
14790
|
+
*/
|
|
14791
|
+
/** @hidden */
|
|
14792
|
+
function attachCmdOptions(cli) {
|
|
14793
|
+
return cli
|
|
14794
|
+
.enablePositionalOptions()
|
|
14795
|
+
.passThroughOptions()
|
|
14796
|
+
.argument('[command...]');
|
|
14797
|
+
}
|
|
14798
|
+
|
|
14404
14799
|
/**
|
|
14405
14800
|
* Install the parent-level invoker (alias) for the cmd plugin.
|
|
14406
14801
|
* Unifies naming with batch attachParentInvoker; behavior unchanged.
|
|
14407
14802
|
*/
|
|
14408
|
-
const
|
|
14803
|
+
const attachCmdParentInvoker = (cli, options, plugin) => {
|
|
14409
14804
|
const dbg = (...args) => {
|
|
14410
14805
|
if (process.env.GETDOTENV_DEBUG) {
|
|
14411
14806
|
try {
|
|
@@ -14516,8 +14911,9 @@ const attachParentInvoker = (cli, options, _cmd, plugin) => {
|
|
|
14516
14911
|
/**
|
|
14517
14912
|
* Zod schema for cmd plugin configuration.
|
|
14518
14913
|
*/
|
|
14519
|
-
const
|
|
14914
|
+
const cmdPluginConfigSchema = z$2
|
|
14520
14915
|
.object({
|
|
14916
|
+
/** When true, expand the alias value before execution (default behavior when omitted). */
|
|
14521
14917
|
expand: z$2.boolean().optional(),
|
|
14522
14918
|
})
|
|
14523
14919
|
.strict();
|
|
@@ -14536,7 +14932,7 @@ const CmdConfigSchema = z$2
|
|
|
14536
14932
|
const cmdPlugin = (options = {}) => {
|
|
14537
14933
|
const plugin = definePlugin({
|
|
14538
14934
|
ns: 'cmd',
|
|
14539
|
-
configSchema:
|
|
14935
|
+
configSchema: cmdPluginConfigSchema,
|
|
14540
14936
|
setup(cli) {
|
|
14541
14937
|
const aliasSpec = typeof options.optionAlias === 'string'
|
|
14542
14938
|
? { flags: options.optionAlias}
|
|
@@ -14548,14 +14944,13 @@ const cmdPlugin = (options = {}) => {
|
|
|
14548
14944
|
};
|
|
14549
14945
|
const aliasKey = aliasSpec ? deriveKey(aliasSpec.flags) : undefined;
|
|
14550
14946
|
// Mount is the command ('cmd'); attach default action.
|
|
14551
|
-
cli
|
|
14552
|
-
|
|
14553
|
-
|
|
14554
|
-
|
|
14555
|
-
attachDefaultCmdAction(cli, cli, aliasKey);
|
|
14947
|
+
cli.description('Execute command according to the --shell option, conflicts with --command option (default subcommand)');
|
|
14948
|
+
// Options/arguments (positional payload, argv routing) are attached separately.
|
|
14949
|
+
attachCmdOptions(cli);
|
|
14950
|
+
attachCmdDefaultAction(cli, cli, aliasKey);
|
|
14556
14951
|
// Parent-attached option alias (optional, unified naming).
|
|
14557
14952
|
if (aliasSpec !== undefined) {
|
|
14558
|
-
|
|
14953
|
+
attachCmdParentInvoker(cli, options, plugin);
|
|
14559
14954
|
}
|
|
14560
14955
|
return undefined;
|
|
14561
14956
|
},
|
|
@@ -14700,6 +15095,7 @@ const planConfigCopies = ({ format, withLocal, destRoot, }) => {
|
|
|
14700
15095
|
const planCliCopies = ({ cliName, destRoot, }) => {
|
|
14701
15096
|
const subs = { __CLI_NAME__: cliName };
|
|
14702
15097
|
const base = path.join(destRoot, 'src', 'cli', cliName);
|
|
15098
|
+
const helloBase = path.join(base, 'plugins', 'hello');
|
|
14703
15099
|
return [
|
|
14704
15100
|
{
|
|
14705
15101
|
src: path.join(TEMPLATES_ROOT, 'cli', 'index.ts'),
|
|
@@ -14707,8 +15103,28 @@ const planCliCopies = ({ cliName, destRoot, }) => {
|
|
|
14707
15103
|
subs,
|
|
14708
15104
|
},
|
|
14709
15105
|
{
|
|
14710
|
-
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello.ts'),
|
|
14711
|
-
dest: path.join(
|
|
15106
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'index.ts'),
|
|
15107
|
+
dest: path.join(helloBase, 'index.ts'),
|
|
15108
|
+
subs,
|
|
15109
|
+
},
|
|
15110
|
+
{
|
|
15111
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'options.ts'),
|
|
15112
|
+
dest: path.join(helloBase, 'options.ts'),
|
|
15113
|
+
subs,
|
|
15114
|
+
},
|
|
15115
|
+
{
|
|
15116
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'defaultAction.ts'),
|
|
15117
|
+
dest: path.join(helloBase, 'defaultAction.ts'),
|
|
15118
|
+
subs,
|
|
15119
|
+
},
|
|
15120
|
+
{
|
|
15121
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'strangerAction.ts'),
|
|
15122
|
+
dest: path.join(helloBase, 'strangerAction.ts'),
|
|
15123
|
+
subs,
|
|
15124
|
+
},
|
|
15125
|
+
{
|
|
15126
|
+
src: path.join(TEMPLATES_ROOT, 'cli', 'plugins', 'hello', 'types.ts'),
|
|
15127
|
+
dest: path.join(helloBase, 'types.ts'),
|
|
14712
15128
|
subs,
|
|
14713
15129
|
},
|
|
14714
15130
|
];
|
|
@@ -14750,129 +15166,149 @@ const promptDecision = async (filePath, logger, rl) => {
|
|
|
14750
15166
|
};
|
|
14751
15167
|
|
|
14752
15168
|
/**
|
|
14753
|
-
*
|
|
14754
|
-
*
|
|
14755
|
-
*
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
* Init plugin: scaffolds configuration files and a CLI skeleton for get-dotenv.
|
|
14759
|
-
* Supports collision detection, interactive prompts, and CI bypass.
|
|
15169
|
+
* Attach the init plugin default action.
|
|
15170
|
+
*
|
|
15171
|
+
* @param cli - The `init` command mount (with args/options attached).
|
|
15172
|
+
*
|
|
15173
|
+
* @internal
|
|
14760
15174
|
*/
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
|
|
14766
|
-
|
|
14767
|
-
|
|
14768
|
-
|
|
14769
|
-
|
|
14770
|
-
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
14786
|
-
|
|
14787
|
-
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
|
|
14807
|
-
|
|
14808
|
-
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
await copyTextFile(item.src, item.dest, subs);
|
|
14817
|
-
logger.log(`Overwrote ${path.relative(cwd, item.dest)}`);
|
|
14818
|
-
continue;
|
|
14819
|
-
}
|
|
14820
|
-
if (yes) {
|
|
14821
|
-
logger.log(`Skipped ${path.relative(cwd, item.dest)}`);
|
|
14822
|
-
continue;
|
|
14823
|
-
}
|
|
14824
|
-
let decision = globalDecision;
|
|
14825
|
-
if (!decision) {
|
|
14826
|
-
const a = await promptDecision(item.dest, logger, rl);
|
|
14827
|
-
if (a === 'O') {
|
|
14828
|
-
globalDecision = 'overwrite';
|
|
14829
|
-
decision = 'overwrite';
|
|
14830
|
-
}
|
|
14831
|
-
else if (a === 'E') {
|
|
14832
|
-
globalDecision = 'example';
|
|
14833
|
-
decision = 'example';
|
|
14834
|
-
}
|
|
14835
|
-
else if (a === 'S') {
|
|
14836
|
-
globalDecision = 'skip';
|
|
14837
|
-
decision = 'skip';
|
|
14838
|
-
}
|
|
14839
|
-
else {
|
|
14840
|
-
decision =
|
|
14841
|
-
a === 'o' ? 'overwrite' : a === 'e' ? 'example' : 'skip';
|
|
14842
|
-
}
|
|
15175
|
+
function attachInitDefaultAction(cli) {
|
|
15176
|
+
cli.action(async (destArg, opts, thisCommand) => {
|
|
15177
|
+
// Inherit logger from merged root options (base).
|
|
15178
|
+
const bag = readMergedOptions(thisCommand);
|
|
15179
|
+
const logger = bag.logger;
|
|
15180
|
+
const destRel = typeof destArg === 'string' && destArg.length > 0 ? destArg : '.';
|
|
15181
|
+
const cwd = process.cwd();
|
|
15182
|
+
const destRoot = path.resolve(cwd, destRel);
|
|
15183
|
+
const formatInput = opts['configFormat'];
|
|
15184
|
+
const formatRaw = typeof formatInput === 'string' ? formatInput.toLowerCase() : 'json';
|
|
15185
|
+
const format = (['json', 'yaml', 'js', 'ts'].includes(formatRaw) ? formatRaw : 'json');
|
|
15186
|
+
const withLocal = Boolean(opts['withLocal']);
|
|
15187
|
+
// dynamic flag reserved for future template variants; present for UX compatibility
|
|
15188
|
+
void opts['dynamic'];
|
|
15189
|
+
// CLI name default: --cli-name | basename(dest) | 'mycli'
|
|
15190
|
+
const cliNameInput = opts['cliName'];
|
|
15191
|
+
const cliName = (typeof cliNameInput === 'string' && cliNameInput.length > 0
|
|
15192
|
+
? cliNameInput
|
|
15193
|
+
: path.basename(destRoot) || 'mycli') || 'mycli';
|
|
15194
|
+
// Precedence: --force > --yes > auto-detect(non-interactive => yes)
|
|
15195
|
+
const force = Boolean(opts['force']);
|
|
15196
|
+
const yes = Boolean(opts['yes']) || (!force && isNonInteractive());
|
|
15197
|
+
// Build copy plan
|
|
15198
|
+
const cfgCopies = planConfigCopies({ format, withLocal, destRoot });
|
|
15199
|
+
const cliCopies = planCliCopies({ cliName, destRoot });
|
|
15200
|
+
const copies = [...cfgCopies, ...cliCopies];
|
|
15201
|
+
// Interactive state
|
|
15202
|
+
let globalDecision;
|
|
15203
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
15204
|
+
try {
|
|
15205
|
+
for (const item of copies) {
|
|
15206
|
+
const exists = await fs.pathExists(item.dest);
|
|
15207
|
+
if (!exists) {
|
|
15208
|
+
const subs = item.subs ?? {};
|
|
15209
|
+
await copyTextFile(item.src, item.dest, subs);
|
|
15210
|
+
logger.log(`Created ${path.relative(cwd, item.dest)}`);
|
|
15211
|
+
continue;
|
|
15212
|
+
}
|
|
15213
|
+
// Collision
|
|
15214
|
+
if (force) {
|
|
15215
|
+
const subs = item.subs ?? {};
|
|
15216
|
+
await copyTextFile(item.src, item.dest, subs);
|
|
15217
|
+
logger.log(`Overwrote ${path.relative(cwd, item.dest)}`);
|
|
15218
|
+
continue;
|
|
15219
|
+
}
|
|
15220
|
+
if (yes) {
|
|
15221
|
+
logger.log(`Skipped ${path.relative(cwd, item.dest)}`);
|
|
15222
|
+
continue;
|
|
15223
|
+
}
|
|
15224
|
+
let decision = globalDecision;
|
|
15225
|
+
if (!decision) {
|
|
15226
|
+
const a = await promptDecision(item.dest, logger, rl);
|
|
15227
|
+
if (a === 'O') {
|
|
15228
|
+
globalDecision = 'overwrite';
|
|
15229
|
+
decision = 'overwrite';
|
|
14843
15230
|
}
|
|
14844
|
-
if (
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
logger.log(`Overwrote ${path.relative(cwd, item.dest)}`);
|
|
15231
|
+
else if (a === 'E') {
|
|
15232
|
+
globalDecision = 'example';
|
|
15233
|
+
decision = 'example';
|
|
14848
15234
|
}
|
|
14849
|
-
else if (
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
await copyTextFile(item.src, destEx, subs);
|
|
14853
|
-
logger.log(`Wrote example ${path.relative(cwd, destEx)}`);
|
|
15235
|
+
else if (a === 'S') {
|
|
15236
|
+
globalDecision = 'skip';
|
|
15237
|
+
decision = 'skip';
|
|
14854
15238
|
}
|
|
14855
15239
|
else {
|
|
14856
|
-
|
|
15240
|
+
decision = a === 'o' ? 'overwrite' : a === 'e' ? 'example' : 'skip';
|
|
14857
15241
|
}
|
|
14858
15242
|
}
|
|
14859
|
-
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
15243
|
+
if (decision === 'overwrite') {
|
|
15244
|
+
const subs = item.subs ?? {};
|
|
15245
|
+
await copyTextFile(item.src, item.dest, subs);
|
|
15246
|
+
logger.log(`Overwrote ${path.relative(cwd, item.dest)}`);
|
|
15247
|
+
}
|
|
15248
|
+
else if (decision === 'example') {
|
|
15249
|
+
const destEx = `${item.dest}.example`;
|
|
15250
|
+
const subs = item.subs ?? {};
|
|
15251
|
+
await copyTextFile(item.src, destEx, subs);
|
|
15252
|
+
logger.log(`Wrote example ${path.relative(cwd, destEx)}`);
|
|
14867
15253
|
}
|
|
14868
|
-
else
|
|
14869
|
-
logger.log(`
|
|
15254
|
+
else {
|
|
15255
|
+
logger.log(`Skipped ${path.relative(cwd, item.dest)}`);
|
|
14870
15256
|
}
|
|
14871
15257
|
}
|
|
14872
|
-
|
|
14873
|
-
|
|
15258
|
+
// Ensure .gitignore includes local config patterns.
|
|
15259
|
+
const giPath = path.join(destRoot, '.gitignore');
|
|
15260
|
+
const { created, changed } = await ensureLines(giPath, [
|
|
15261
|
+
'getdotenv.config.local.*',
|
|
15262
|
+
'*.local',
|
|
15263
|
+
]);
|
|
15264
|
+
if (created) {
|
|
15265
|
+
logger.log(`Created ${path.relative(cwd, giPath)}`);
|
|
14874
15266
|
}
|
|
14875
|
-
|
|
15267
|
+
else if (changed) {
|
|
15268
|
+
logger.log(`Updated ${path.relative(cwd, giPath)}`);
|
|
15269
|
+
}
|
|
15270
|
+
}
|
|
15271
|
+
finally {
|
|
15272
|
+
rl.close();
|
|
15273
|
+
}
|
|
15274
|
+
});
|
|
15275
|
+
}
|
|
15276
|
+
|
|
15277
|
+
/**
|
|
15278
|
+
* Attach options/arguments for the init plugin mount.
|
|
15279
|
+
*
|
|
15280
|
+
* @param cli - The `init` command mount.
|
|
15281
|
+
*
|
|
15282
|
+
* @internal
|
|
15283
|
+
*/
|
|
15284
|
+
/** @hidden */
|
|
15285
|
+
function attachInitOptions(cli) {
|
|
15286
|
+
return (cli
|
|
15287
|
+
// Description is owned by the plugin index (src/plugins/init/index.ts).
|
|
15288
|
+
.argument('[dest]', 'destination path (default: ./)', '.')
|
|
15289
|
+
.option('--config-format <format>', 'config format: json|yaml|js|ts', 'json')
|
|
15290
|
+
.option('--with-local', 'include .local config variant')
|
|
15291
|
+
.option('--dynamic', 'include dynamic examples (JS/TS configs)')
|
|
15292
|
+
.option('--cli-name <string>', 'CLI name for skeleton and tokens')
|
|
15293
|
+
.option('--force', 'overwrite all existing files')
|
|
15294
|
+
.option('--yes', 'skip all collisions (no overwrite)'));
|
|
15295
|
+
}
|
|
15296
|
+
|
|
15297
|
+
/**
|
|
15298
|
+
* @packageDocumentation
|
|
15299
|
+
* Init plugin subpath. Scaffolds get‑dotenv configuration files and a simple
|
|
15300
|
+
* host‑based CLI skeleton with collision handling and CI‑safe defaults.
|
|
15301
|
+
*/
|
|
15302
|
+
/**
|
|
15303
|
+
* Init plugin: scaffolds configuration files and a CLI skeleton for get-dotenv.
|
|
15304
|
+
* Supports collision detection, interactive prompts, and CI bypass.
|
|
15305
|
+
*/
|
|
15306
|
+
const initPlugin = () => definePlugin({
|
|
15307
|
+
ns: 'init',
|
|
15308
|
+
setup(cli) {
|
|
15309
|
+
cli.description('Scaffold getdotenv config files and a host-based CLI skeleton.');
|
|
15310
|
+
const initCmd = attachInitOptions(cli);
|
|
15311
|
+
attachInitDefaultAction(initCmd);
|
|
14876
15312
|
return undefined;
|
|
14877
15313
|
},
|
|
14878
15314
|
});
|