@karmaniverous/get-dotenv 6.0.0-0 → 6.0.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.
Files changed (93) hide show
  1. package/README.md +86 -334
  2. package/dist/cli.d.ts +569 -0
  3. package/dist/cli.mjs +18788 -0
  4. package/dist/cliHost.d.ts +548 -253
  5. package/dist/cliHost.mjs +1990 -1458
  6. package/dist/config.d.ts +192 -14
  7. package/dist/config.mjs +256 -81
  8. package/dist/env-overlay.d.ts +226 -18
  9. package/dist/env-overlay.mjs +181 -22
  10. package/dist/getdotenv.cli.mjs +18166 -3437
  11. package/dist/index.d.ts +729 -136
  12. package/dist/index.mjs +18207 -3457
  13. package/dist/plugins-aws.d.ts +289 -104
  14. package/dist/plugins-aws.mjs +2462 -350
  15. package/dist/plugins-batch.d.ts +355 -105
  16. package/dist/plugins-batch.mjs +2595 -420
  17. package/dist/plugins-cmd.d.ts +287 -118
  18. package/dist/plugins-cmd.mjs +2661 -839
  19. package/dist/plugins-init.d.ts +272 -100
  20. package/dist/plugins-init.mjs +2152 -37
  21. package/dist/plugins.d.ts +323 -140
  22. package/dist/plugins.mjs +18006 -2025
  23. package/dist/templates/cli/index.ts +26 -0
  24. package/dist/templates/cli/plugins/hello.ts +43 -0
  25. package/dist/templates/config/js/getdotenv.config.js +20 -0
  26. package/dist/templates/config/json/local/getdotenv.config.local.json +7 -0
  27. package/dist/templates/config/json/public/getdotenv.config.json +9 -0
  28. package/dist/templates/config/public/getdotenv.config.json +8 -0
  29. package/dist/templates/config/ts/getdotenv.config.ts +28 -0
  30. package/dist/templates/config/yaml/local/getdotenv.config.local.yaml +7 -0
  31. package/dist/templates/config/yaml/public/getdotenv.config.yaml +7 -0
  32. package/dist/templates/getdotenv.config.js +20 -0
  33. package/dist/templates/getdotenv.config.json +9 -0
  34. package/dist/templates/getdotenv.config.local.json +7 -0
  35. package/dist/templates/getdotenv.config.local.yaml +7 -0
  36. package/dist/templates/getdotenv.config.ts +28 -0
  37. package/dist/templates/getdotenv.config.yaml +7 -0
  38. package/dist/templates/hello.ts +43 -0
  39. package/dist/templates/index.ts +26 -0
  40. package/dist/templates/js/getdotenv.config.js +20 -0
  41. package/dist/templates/json/local/getdotenv.config.local.json +7 -0
  42. package/dist/templates/json/public/getdotenv.config.json +9 -0
  43. package/dist/templates/local/getdotenv.config.local.json +7 -0
  44. package/dist/templates/local/getdotenv.config.local.yaml +7 -0
  45. package/dist/templates/plugins/hello.ts +43 -0
  46. package/dist/templates/public/getdotenv.config.json +9 -0
  47. package/dist/templates/public/getdotenv.config.yaml +7 -0
  48. package/dist/templates/ts/getdotenv.config.ts +28 -0
  49. package/dist/templates/yaml/local/getdotenv.config.local.yaml +7 -0
  50. package/dist/templates/yaml/public/getdotenv.config.yaml +7 -0
  51. package/getdotenv.config.json +1 -19
  52. package/package.json +52 -89
  53. package/templates/cli/index.ts +26 -0
  54. package/templates/cli/plugins/hello.ts +43 -0
  55. package/templates/config/js/getdotenv.config.js +9 -4
  56. package/templates/config/json/public/getdotenv.config.json +0 -3
  57. package/templates/config/public/getdotenv.config.json +0 -5
  58. package/templates/config/ts/getdotenv.config.ts +17 -5
  59. package/templates/config/yaml/public/getdotenv.config.yaml +0 -3
  60. package/dist/cliHost.cjs +0 -2078
  61. package/dist/cliHost.d.cts +0 -451
  62. package/dist/cliHost.d.mts +0 -451
  63. package/dist/config.cjs +0 -252
  64. package/dist/config.d.cts +0 -55
  65. package/dist/config.d.mts +0 -55
  66. package/dist/env-overlay.cjs +0 -163
  67. package/dist/env-overlay.d.cts +0 -50
  68. package/dist/env-overlay.d.mts +0 -50
  69. package/dist/index.cjs +0 -4077
  70. package/dist/index.d.cts +0 -318
  71. package/dist/index.d.mts +0 -318
  72. package/dist/plugins-aws.cjs +0 -666
  73. package/dist/plugins-aws.d.cts +0 -158
  74. package/dist/plugins-aws.d.mts +0 -158
  75. package/dist/plugins-batch.cjs +0 -658
  76. package/dist/plugins-batch.d.cts +0 -181
  77. package/dist/plugins-batch.d.mts +0 -181
  78. package/dist/plugins-cmd.cjs +0 -1112
  79. package/dist/plugins-cmd.d.cts +0 -178
  80. package/dist/plugins-cmd.d.mts +0 -178
  81. package/dist/plugins-demo.cjs +0 -352
  82. package/dist/plugins-demo.d.cts +0 -158
  83. package/dist/plugins-demo.d.mts +0 -158
  84. package/dist/plugins-demo.d.ts +0 -158
  85. package/dist/plugins-demo.mjs +0 -350
  86. package/dist/plugins-init.cjs +0 -289
  87. package/dist/plugins-init.d.cts +0 -162
  88. package/dist/plugins-init.d.mts +0 -162
  89. package/dist/plugins.cjs +0 -2327
  90. package/dist/plugins.d.cts +0 -211
  91. package/dist/plugins.d.mts +0 -211
  92. package/templates/cli/ts/index.ts +0 -9
  93. package/templates/cli/ts/plugins/hello.ts +0 -17
@@ -1,666 +0,0 @@
1
- 'use strict';
2
-
3
- var execa = require('execa');
4
- var zod = require('zod');
5
-
6
- // Minimal tokenizer for shell-off execution:
7
- // Splits by whitespace while preserving quoted segments (single or double quotes).
8
- const tokenize = (command) => {
9
- const out = [];
10
- let cur = '';
11
- let quote = null;
12
- for (let i = 0; i < command.length; i++) {
13
- const c = command.charAt(i);
14
- if (quote) {
15
- if (c === quote) {
16
- // Support doubled quotes inside a quoted segment (Windows/PowerShell style):
17
- // "" -> " and '' -> '
18
- const next = command.charAt(i + 1);
19
- if (next === quote) {
20
- cur += quote;
21
- i += 1; // skip the second quote
22
- }
23
- else {
24
- // end of quoted segment
25
- quote = null;
26
- }
27
- }
28
- else {
29
- cur += c;
30
- }
31
- }
32
- else {
33
- if (c === '"' || c === "'") {
34
- quote = c;
35
- }
36
- else if (/\s/.test(c)) {
37
- if (cur) {
38
- out.push(cur);
39
- cur = '';
40
- }
41
- }
42
- else {
43
- cur += c;
44
- }
45
- }
46
- }
47
- if (cur)
48
- out.push(cur);
49
- return out;
50
- };
51
-
52
- const dbg = (...args) => {
53
- if (process.env.GETDOTENV_DEBUG) {
54
- // Use stderr to avoid interfering with stdout assertions
55
- console.error('[getdotenv:run]', ...args);
56
- }
57
- };
58
- // Strip repeated symmetric outer quotes (single or double) until stable.
59
- // This is safe for argv arrays passed to execa (no quoting needed) and avoids
60
- // passing quote characters through to Node (e.g., for `node -e "<code>"`).
61
- // Handles stacked quotes from shells like PowerShell: """code""" -> code.
62
- const stripOuterQuotes = (s) => {
63
- let out = s;
64
- // Repeatedly trim only when the entire string is wrapped in matching quotes.
65
- // Stop as soon as the ends are asymmetric or no quotes remain.
66
- while (out.length >= 2) {
67
- const a = out.charAt(0);
68
- const b = out.charAt(out.length - 1);
69
- const symmetric = (a === '"' && b === '"') || (a === "'" && b === "'");
70
- if (!symmetric)
71
- break;
72
- out = out.slice(1, -1);
73
- }
74
- return out;
75
- };
76
- // Extract exitCode/stdout/stderr from execa result or error in a tolerant way.
77
- const pickResult = (r) => {
78
- const exit = r.exitCode;
79
- const stdoutVal = r.stdout;
80
- const stderrVal = r.stderr;
81
- return {
82
- exitCode: typeof exit === 'number' ? exit : Number.NaN,
83
- stdout: typeof stdoutVal === 'string' ? stdoutVal : '',
84
- stderr: typeof stderrVal === 'string' ? stderrVal : '',
85
- };
86
- };
87
- // Convert NodeJS.ProcessEnv (string | undefined values) to the shape execa
88
- // expects (Readonly<Partial<Record<string, string>>>), dropping undefineds.
89
- const sanitizeEnv = (env) => {
90
- if (!env)
91
- return undefined;
92
- const entries = Object.entries(env).filter((e) => typeof e[1] === 'string');
93
- return entries.length > 0 ? Object.fromEntries(entries) : undefined;
94
- };
95
- /**
96
- * Execute a command and capture stdout/stderr (buffered).
97
- * - Preserves plain vs shell behavior and argv/string normalization.
98
- * - Never re-emits stdout/stderr to parent; returns captured buffers.
99
- * - Supports optional timeout (ms).
100
- */
101
- const runCommandResult = async (command, shell, opts = {}) => {
102
- const envSan = sanitizeEnv(opts.env);
103
- {
104
- let file;
105
- let args = [];
106
- if (Array.isArray(command)) {
107
- file = command[0];
108
- args = command.slice(1).map(stripOuterQuotes);
109
- }
110
- else {
111
- const tokens = tokenize(command);
112
- file = tokens[0];
113
- args = tokens.slice(1);
114
- }
115
- if (!file)
116
- return { exitCode: 0, stdout: '', stderr: '' };
117
- dbg('exec:capture (plain)', { file, args });
118
- try {
119
- const result = await execa.execa(file, args, {
120
- ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
121
- ...(envSan !== undefined ? { env: envSan } : {}),
122
- stdio: 'pipe',
123
- ...(opts.timeoutMs !== undefined
124
- ? { timeout: opts.timeoutMs, killSignal: 'SIGKILL' }
125
- : {}),
126
- });
127
- const ok = pickResult(result);
128
- dbg('exit:capture (plain)', { exitCode: ok.exitCode });
129
- return ok;
130
- }
131
- catch (err) {
132
- const out = pickResult(err);
133
- dbg('exit:capture:error (plain)', { exitCode: out.exitCode });
134
- return out;
135
- }
136
- }
137
- };
138
- const runCommand = async (command, shell, opts) => {
139
- if (shell === false) {
140
- let file;
141
- let args = [];
142
- if (Array.isArray(command)) {
143
- file = command[0];
144
- args = command.slice(1).map(stripOuterQuotes);
145
- }
146
- else {
147
- const tokens = tokenize(command);
148
- file = tokens[0];
149
- args = tokens.slice(1);
150
- }
151
- if (!file)
152
- return 0;
153
- dbg('exec (plain)', { file, args, stdio: opts.stdio });
154
- // Build options without injecting undefined properties (exactOptionalPropertyTypes).
155
- const envSan = sanitizeEnv(opts.env);
156
- const plainOpts = {};
157
- if (opts.cwd !== undefined)
158
- plainOpts.cwd = opts.cwd;
159
- if (envSan !== undefined)
160
- plainOpts.env = envSan;
161
- if (opts.stdio !== undefined)
162
- plainOpts.stdio = opts.stdio;
163
- const result = await execa.execa(file, args, plainOpts);
164
- if (opts.stdio === 'pipe' && result.stdout) {
165
- process.stdout.write(result.stdout + (result.stdout.endsWith('\n') ? '' : '\n'));
166
- }
167
- const exit = result?.exitCode;
168
- dbg('exit (plain)', { exitCode: exit });
169
- return typeof exit === 'number' ? exit : Number.NaN;
170
- }
171
- else {
172
- const commandStr = Array.isArray(command) ? command.join(' ') : command;
173
- dbg('exec (shell)', {
174
- shell: typeof shell === 'string' ? shell : 'custom',
175
- stdio: opts.stdio,
176
- command: commandStr,
177
- });
178
- const envSan = sanitizeEnv(opts.env);
179
- const shellOpts = { shell };
180
- if (opts.cwd !== undefined)
181
- shellOpts.cwd = opts.cwd;
182
- if (envSan !== undefined)
183
- shellOpts.env = envSan;
184
- if (opts.stdio !== undefined)
185
- shellOpts.stdio = opts.stdio;
186
- const result = await execa.execaCommand(commandStr, shellOpts);
187
- const out = result?.stdout;
188
- if (opts.stdio === 'pipe' && out) {
189
- process.stdout.write(out + (out.endsWith('\n') ? '' : '\n'));
190
- }
191
- const exit = result?.exitCode;
192
- dbg('exit (shell)', { exitCode: exit });
193
- return typeof exit === 'number' ? exit : Number.NaN;
194
- }
195
- };
196
-
197
- const dropUndefined = (bag) => Object.fromEntries(Object.entries(bag).filter((e) => typeof e[1] === 'string'));
198
- /** Build a sanitized env for child processes from base + overlay. */
199
- const buildSpawnEnv = (base, overlay) => {
200
- const raw = {
201
- ...(base ?? {}),
202
- ...(overlay ?? {}),
203
- };
204
- // Drop undefined first
205
- const entries = Object.entries(dropUndefined(raw));
206
- if (process.platform === 'win32') {
207
- // Windows: keys are case-insensitive; collapse duplicates
208
- const byLower = new Map();
209
- for (const [k, v] of entries) {
210
- byLower.set(k.toLowerCase(), [k, v]); // last wins; preserve latest casing
211
- }
212
- const out = {};
213
- for (const [, [k, v]] of byLower)
214
- out[k] = v;
215
- // HOME fallback from USERPROFILE (common expectation)
216
- if (!Object.prototype.hasOwnProperty.call(out, 'HOME')) {
217
- const up = out['USERPROFILE'];
218
- if (typeof up === 'string' && up.length > 0)
219
- out['HOME'] = up;
220
- }
221
- // Normalize TMP/TEMP coherence (pick any present; reflect to both)
222
- const tmp = out['TMP'] ?? out['TEMP'];
223
- if (typeof tmp === 'string' && tmp.length > 0) {
224
- out['TMP'] = tmp;
225
- out['TEMP'] = tmp;
226
- }
227
- return out;
228
- }
229
- // POSIX: keep keys as-is
230
- const out = Object.fromEntries(entries);
231
- // Ensure TMPDIR exists when any temp key is present (best-effort)
232
- const tmpdir = out['TMPDIR'] ?? out['TMP'] ?? out['TEMP'];
233
- if (typeof tmpdir === 'string' && tmpdir.length > 0) {
234
- out['TMPDIR'] = tmpdir;
235
- }
236
- return out;
237
- };
238
-
239
- /** src/cliHost/definePlugin.ts
240
- * Plugin contracts for the GetDotenv CLI host.
241
- *
242
- * This module exposes a structural public interface for the host that plugins
243
- * should use (GetDotenvCliPublic). Using a structural type at the seam avoids
244
- * nominal class identity issues (private fields) in downstream consumers.
245
- */
246
- /**
247
- * Define a GetDotenv CLI plugin with compositional helpers.
248
- *
249
- * @example
250
- * const parent = definePlugin(\{ id: 'p', setup(cli) \{ /* ... *\/ \} \})
251
- * .use(childA)
252
- * .use(childB);
253
- */
254
- const definePlugin = (spec) => {
255
- const { children = [], ...rest } = spec;
256
- const plugin = {
257
- ...rest,
258
- children: [...children],
259
- use(child) {
260
- this.children.push(child);
261
- return this;
262
- },
263
- };
264
- return plugin;
265
- };
266
-
267
- /**
268
- * Batch services (neutral): resolve command and shell settings.
269
- * Shared by the generator path and the batch plugin to avoid circular deps.
270
- */
271
- /**
272
- * Resolve a command string from the {@link Scripts} table.
273
- * A script may be expressed as a string or an object with a `cmd` property.
274
- *
275
- * @param scripts - Optional scripts table.
276
- * @param command - User-provided command name or string.
277
- * @returns Resolved command string (falls back to the provided command).
278
- */
279
- /**
280
- * Resolve the shell setting for a given command:
281
- * - If the script entry is an object, prefer its `shell` override.
282
- * - Otherwise use the provided `shell` (string | boolean).
283
- *
284
- * @param scripts - Optional scripts table.
285
- * @param command - User-provided command name or string.
286
- * @param shell - Global shell preference (string | boolean).
287
- */
288
- const resolveShell = (scripts, command, shell) => scripts && typeof scripts[command] === 'object'
289
- ? (scripts[command].shell ?? false)
290
- : (shell ?? false);
291
-
292
- const DEFAULT_TIMEOUT_MS = 15_000;
293
- const trim = (s) => (typeof s === 'string' ? s.trim() : '');
294
- const unquote = (s) => s.length >= 2 &&
295
- ((s.startsWith('"') && s.endsWith('"')) ||
296
- (s.startsWith("'") && s.endsWith("'")))
297
- ? s.slice(1, -1)
298
- : s;
299
- const parseExportCredentialsJson = (txt) => {
300
- try {
301
- const obj = JSON.parse(txt);
302
- const src = obj.Credentials ?? obj;
303
- const ak = src.AccessKeyId;
304
- const sk = src.SecretAccessKey;
305
- const tk = src.SessionToken;
306
- if (ak && sk)
307
- return {
308
- accessKeyId: ak,
309
- secretAccessKey: sk,
310
- ...(tk ? { sessionToken: tk } : {}),
311
- };
312
- }
313
- catch {
314
- /* ignore */
315
- }
316
- return undefined;
317
- };
318
- const parseExportCredentialsEnv = (txt) => {
319
- const lines = txt.split(/\r?\n/);
320
- let id;
321
- let secret;
322
- let token;
323
- for (const raw of lines) {
324
- const line = raw.trim();
325
- if (!line)
326
- continue;
327
- // POSIX: export AWS_ACCESS_KEY_ID=..., export AWS_SECRET_ACCESS_KEY=..., export AWS_SESSION_TOKEN=...
328
- let m = /^export\s+([A-Z0-9_]+)\s*=\s*(.+)$/.exec(line);
329
- if (!m) {
330
- // PowerShell: $Env:AWS_ACCESS_KEY_ID="...", etc.
331
- m = /^\$Env:([A-Z0-9_]+)\s*=\s*(.+)$/.exec(line);
332
- }
333
- if (!m)
334
- continue;
335
- const k = m[1];
336
- const valRaw = m[2];
337
- if (typeof valRaw !== 'string')
338
- continue;
339
- let v = unquote(valRaw.trim());
340
- // Drop trailing semicolons if present (some shells)
341
- v = v.replace(/;$/, '');
342
- if (k === 'AWS_ACCESS_KEY_ID')
343
- id = v;
344
- else if (k === 'AWS_SECRET_ACCESS_KEY')
345
- secret = v;
346
- else if (k === 'AWS_SESSION_TOKEN')
347
- token = v;
348
- }
349
- if (id && secret)
350
- return {
351
- accessKeyId: id,
352
- secretAccessKey: secret,
353
- ...(token ? { sessionToken: token } : {}),
354
- };
355
- return undefined;
356
- };
357
- const getAwsConfigure = async (key, profile, timeoutMs = DEFAULT_TIMEOUT_MS) => {
358
- const r = await runCommandResult(['aws', 'configure', 'get', key, '--profile', profile], false, {
359
- env: process.env,
360
- timeoutMs,
361
- });
362
- // Guard for mocked undefined in tests; keep narrow lint scope.
363
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
364
- if (!r || typeof r.exitCode !== 'number')
365
- return undefined;
366
- if (r.exitCode === 0) {
367
- const v = trim(r.stdout);
368
- return v.length > 0 ? v : undefined;
369
- }
370
- return undefined;
371
- };
372
- const exportCredentials = async (profile, timeoutMs = DEFAULT_TIMEOUT_MS) => {
373
- // Try JSON format first (AWS CLI v2)
374
- const rJson = await runCommandResult([
375
- 'aws',
376
- 'configure',
377
- 'export-credentials',
378
- '--profile',
379
- profile,
380
- '--format',
381
- 'json',
382
- ], false, { env: process.env, timeoutMs });
383
- if (rJson.exitCode === 0) {
384
- const creds = parseExportCredentialsJson(rJson.stdout);
385
- if (creds)
386
- return creds;
387
- }
388
- // Fallback: env lines
389
- const rEnv = await runCommandResult(['aws', 'configure', 'export-credentials', '--profile', profile], false, { env: process.env, timeoutMs });
390
- if (rEnv.exitCode === 0) {
391
- const creds = parseExportCredentialsEnv(rEnv.stdout);
392
- if (creds)
393
- return creds;
394
- }
395
- return undefined;
396
- };
397
- const resolveAwsContext = async ({ dotenv, cfg, }) => {
398
- const profileKey = cfg.profileKey ?? 'AWS_LOCAL_PROFILE';
399
- const profileFallbackKey = cfg.profileFallbackKey ?? 'AWS_PROFILE';
400
- const regionKey = cfg.regionKey ?? 'AWS_REGION';
401
- const profile = cfg.profile ??
402
- dotenv[profileKey] ??
403
- dotenv[profileFallbackKey] ??
404
- undefined;
405
- let region = cfg.region ?? dotenv[regionKey] ?? undefined;
406
- // Short-circuit when strategy is disabled.
407
- if (cfg.strategy === 'none') {
408
- // If region is still missing and we have a profile, try best-effort region resolve.
409
- if (!region && profile)
410
- region = await getAwsConfigure('region', profile);
411
- if (!region && cfg.defaultRegion)
412
- region = cfg.defaultRegion;
413
- const out = {};
414
- if (profile !== undefined)
415
- out.profile = profile;
416
- if (region !== undefined)
417
- out.region = region;
418
- return out;
419
- }
420
- // Env-first credentials.
421
- let credentials;
422
- const envId = trim(process.env.AWS_ACCESS_KEY_ID);
423
- const envSecret = trim(process.env.AWS_SECRET_ACCESS_KEY);
424
- const envToken = trim(process.env.AWS_SESSION_TOKEN);
425
- if (envId && envSecret) {
426
- credentials = {
427
- accessKeyId: envId,
428
- secretAccessKey: envSecret,
429
- ...(envToken ? { sessionToken: envToken } : {}),
430
- };
431
- }
432
- else if (profile) {
433
- // Try export-credentials
434
- credentials = await exportCredentials(profile);
435
- // On failure, detect SSO and optionally login then retry
436
- if (!credentials) {
437
- const ssoSession = await getAwsConfigure('sso_session', profile);
438
- const looksSSO = typeof ssoSession === 'string' && ssoSession.length > 0;
439
- if (looksSSO && cfg.loginOnDemand) {
440
- // Best-effort login, then retry export once.
441
- await runCommandResult(['aws', 'sso', 'login', '--profile', profile], false, {
442
- env: process.env,
443
- timeoutMs: DEFAULT_TIMEOUT_MS,
444
- });
445
- credentials = await exportCredentials(profile);
446
- }
447
- }
448
- // Static fallback if still missing.
449
- if (!credentials) {
450
- const id = await getAwsConfigure('aws_access_key_id', profile);
451
- const secret = await getAwsConfigure('aws_secret_access_key', profile);
452
- const token = await getAwsConfigure('aws_session_token', profile);
453
- if (id && secret) {
454
- credentials = {
455
- accessKeyId: id,
456
- secretAccessKey: secret,
457
- ...(token ? { sessionToken: token } : {}),
458
- };
459
- }
460
- }
461
- }
462
- // Final region resolution
463
- if (!region && profile)
464
- region = await getAwsConfigure('region', profile);
465
- if (!region && cfg.defaultRegion)
466
- region = cfg.defaultRegion;
467
- const out = {};
468
- if (profile !== undefined)
469
- out.profile = profile;
470
- if (region !== undefined)
471
- out.region = region;
472
- if (credentials)
473
- out.credentials = credentials;
474
- return out;
475
- };
476
-
477
- const AwsPluginConfigSchema = zod.z.object({
478
- profile: zod.z.string().optional(),
479
- region: zod.z.string().optional(),
480
- defaultRegion: zod.z.string().optional(),
481
- profileKey: zod.z.string().default('AWS_LOCAL_PROFILE').optional(),
482
- profileFallbackKey: zod.z.string().default('AWS_PROFILE').optional(),
483
- regionKey: zod.z.string().default('AWS_REGION').optional(),
484
- strategy: zod.z.enum(['cli-export', 'none']).default('cli-export').optional(),
485
- loginOnDemand: zod.z.boolean().default(false).optional(),
486
- setEnv: zod.z.boolean().default(true).optional(),
487
- addCtx: zod.z.boolean().default(true).optional(),
488
- });
489
-
490
- const awsPlugin = () => definePlugin({
491
- id: 'aws',
492
- // Host validates this slice when the loader path is active.
493
- configSchema: AwsPluginConfigSchema,
494
- setup(cli) {
495
- // Subcommand: aws
496
- cli
497
- .ns('aws')
498
- .description('Establish an AWS session and optionally forward to the AWS CLI')
499
- .enablePositionalOptions()
500
- .passThroughOptions()
501
- .allowUnknownOption(true)
502
- // Boolean toggles
503
- .option('--login-on-demand', 'attempt aws sso login on-demand')
504
- .option('--no-login-on-demand', 'disable sso login on-demand')
505
- .option('--set-env', 'write resolved values into process.env')
506
- .option('--no-set-env', 'do not write resolved values into process.env')
507
- .option('--add-ctx', 'mirror results under ctx.plugins.aws')
508
- .option('--no-add-ctx', 'do not mirror results under ctx.plugins.aws')
509
- // Strings / enums
510
- .option('--profile <string>', 'AWS profile name')
511
- .option('--region <string>', 'AWS region')
512
- .option('--default-region <string>', 'fallback region')
513
- .option('--strategy <string>', 'credential acquisition strategy: cli-export|none')
514
- // Advanced key overrides
515
- .option('--profile-key <string>', 'dotenv/config key for local profile')
516
- .option('--profile-fallback-key <string>', 'fallback dotenv/config key for profile')
517
- .option('--region-key <string>', 'dotenv/config key for region')
518
- // Accept any extra operands so Commander does not error when tokens appear after "--".
519
- .argument('[args...]')
520
- .action(async (args, opts, thisCommand) => {
521
- const self = thisCommand;
522
- const parent = (self.parent ?? null);
523
- // Access merged root CLI options (installed by passOptions())
524
- const rootOpts = (parent?.getDotenvCliOptions ?? {});
525
- const capture = process.env.GETDOTENV_STDIO === 'pipe' ||
526
- Boolean(rootOpts?.capture);
527
- const underTests = process.env.GETDOTENV_TEST === '1' ||
528
- typeof process.env.VITEST_WORKER_ID === 'string';
529
- // Build overlay cfg from subcommand flags layered over discovered config.
530
- const ctx = cli.getCtx();
531
- const cfgBase = (ctx?.pluginConfigs?.['aws'] ??
532
- {});
533
- const overlay = {};
534
- // Map boolean toggles (respect explicit --no-*)
535
- if (Object.prototype.hasOwnProperty.call(opts, 'loginOnDemand'))
536
- overlay.loginOnDemand = Boolean(opts.loginOnDemand);
537
- if (Object.prototype.hasOwnProperty.call(opts, 'setEnv'))
538
- overlay.setEnv = Boolean(opts.setEnv);
539
- if (Object.prototype.hasOwnProperty.call(opts, 'addCtx'))
540
- overlay.addCtx = Boolean(opts.addCtx);
541
- // Strings/enums
542
- if (typeof opts.profile === 'string')
543
- overlay.profile = opts.profile;
544
- if (typeof opts.region === 'string')
545
- overlay.region = opts.region;
546
- if (typeof opts.defaultRegion === 'string')
547
- overlay.defaultRegion = opts.defaultRegion;
548
- if (typeof opts.strategy === 'string')
549
- overlay.strategy =
550
- opts.strategy;
551
- // Advanced key overrides
552
- if (typeof opts.profileKey === 'string')
553
- overlay.profileKey = opts.profileKey;
554
- if (typeof opts.profileFallbackKey === 'string')
555
- overlay.profileFallbackKey = opts.profileFallbackKey;
556
- if (typeof opts.regionKey === 'string')
557
- overlay.regionKey = opts.regionKey;
558
- const cfg = {
559
- ...cfgBase,
560
- ...overlay,
561
- };
562
- // Resolve current context with overrides
563
- const out = await resolveAwsContext({
564
- dotenv: ctx?.dotenv ?? {},
565
- cfg,
566
- });
567
- // Apply env/ctx mirrors per toggles
568
- if (cfg.setEnv !== false) {
569
- if (out.region) {
570
- process.env.AWS_REGION = out.region;
571
- if (!process.env.AWS_DEFAULT_REGION)
572
- process.env.AWS_DEFAULT_REGION = out.region;
573
- }
574
- if (out.credentials) {
575
- process.env.AWS_ACCESS_KEY_ID = out.credentials.accessKeyId;
576
- process.env.AWS_SECRET_ACCESS_KEY =
577
- out.credentials.secretAccessKey;
578
- if (out.credentials.sessionToken !== undefined) {
579
- process.env.AWS_SESSION_TOKEN = out.credentials.sessionToken;
580
- }
581
- }
582
- }
583
- if (cfg.addCtx !== false) {
584
- if (ctx) {
585
- ctx.plugins ??= {};
586
- ctx.plugins['aws'] = {
587
- ...(out.profile ? { profile: out.profile } : {}),
588
- ...(out.region ? { region: out.region } : {}),
589
- ...(out.credentials ? { credentials: out.credentials } : {}),
590
- };
591
- }
592
- }
593
- // Forward when positional args are present; otherwise session-only.
594
- if (Array.isArray(args) && args.length > 0) {
595
- const argv = ['aws', ...args];
596
- const shellSetting = resolveShell(rootOpts?.scripts, 'aws', rootOpts?.shell);
597
- const ctxDotenv = (ctx?.dotenv ?? {});
598
- const exit = await runCommand(argv, shellSetting, {
599
- env: buildSpawnEnv(process.env, ctxDotenv),
600
- stdio: capture ? 'pipe' : 'inherit',
601
- });
602
- // Deterministic termination (suppressed under tests)
603
- if (!underTests) {
604
- process.exit(typeof exit === 'number' ? exit : 0);
605
- }
606
- return;
607
- }
608
- else {
609
- // Session only: low-noise breadcrumb under debug
610
- if (process.env.GETDOTENV_DEBUG) {
611
- const log = console;
612
- log.log('[aws] session established', {
613
- profile: out.profile,
614
- region: out.region,
615
- hasCreds: Boolean(out.credentials),
616
- });
617
- }
618
- if (!underTests)
619
- process.exit(0);
620
- return;
621
- }
622
- });
623
- },
624
- async afterResolve(_cli, ctx) {
625
- const log = console;
626
- const cfgRaw = (ctx.pluginConfigs?.['aws'] ?? {});
627
- const cfg = (cfgRaw || {});
628
- const out = await resolveAwsContext({
629
- dotenv: ctx.dotenv,
630
- cfg,
631
- });
632
- const { profile, region, credentials } = out;
633
- if (cfg.setEnv !== false) {
634
- if (region) {
635
- process.env.AWS_REGION = region;
636
- if (!process.env.AWS_DEFAULT_REGION)
637
- process.env.AWS_DEFAULT_REGION = region;
638
- }
639
- if (credentials) {
640
- process.env.AWS_ACCESS_KEY_ID = credentials.accessKeyId;
641
- process.env.AWS_SECRET_ACCESS_KEY = credentials.secretAccessKey;
642
- if (credentials.sessionToken !== undefined) {
643
- process.env.AWS_SESSION_TOKEN = credentials.sessionToken;
644
- }
645
- }
646
- }
647
- if (cfg.addCtx !== false) {
648
- ctx.plugins ??= {};
649
- ctx.plugins['aws'] = {
650
- ...(profile ? { profile } : {}),
651
- ...(region ? { region } : {}),
652
- ...(credentials ? { credentials } : {}),
653
- };
654
- }
655
- // Optional: low-noise breadcrumb for diagnostics
656
- if (process.env.GETDOTENV_DEBUG) {
657
- log.log('[aws] afterResolve', {
658
- profile,
659
- region,
660
- hasCreds: Boolean(credentials),
661
- });
662
- }
663
- },
664
- });
665
-
666
- exports.awsPlugin = awsPlugin;