@mtndev/cli 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/cjs/__chunks/VH7oDXx4.cjs +28 -0
- package/cjs/commands/aws/ec2/allow-ip/handler.cjs +261 -12
- package/cjs/commands/aws/ec2/allow-ip/index.cjs +25 -55
- package/cjs/commands/aws/ec2/allow-ip/schema.cjs +33 -13
- package/cjs/commands/aws/ec2/allow-ip/utils.cjs +379 -39
- package/cjs/commands/aws/iam/admin-group.cjs +111 -15
- package/cjs/commands/aws/iam/break-glass/handler.cjs +127 -12
- package/cjs/commands/aws/iam/break-glass/index.cjs +7 -19
- package/cjs/commands/aws/iam/break-glass/schema.cjs +21 -12
- package/cjs/commands/aws/iam/check-admin/handler.cjs +89 -10
- package/cjs/commands/aws/iam/check-admin/index.cjs +7 -17
- package/cjs/commands/aws/iam/check-admin/schema.cjs +19 -12
- package/cjs/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.json +20 -0
- package/cjs/commands/aws/iam/policies/sts-federation.json +11 -0
- package/cjs/commands/aws/iam/policies/user-admins-deny-list.json +175 -0
- package/cjs/commands/aws/iam/policies/user-admins-deny-regions-list.json +41 -0
- package/cjs/commands/aws/iam/policies/user-admins-self-service-iam.json +56 -0
- package/cjs/commands/aws/iam/policies/user-admins-write-list.json +117 -0
- package/cjs/commands/aws/iam/remove-admin/handler.cjs +120 -11
- package/cjs/commands/aws/iam/remove-admin/index.cjs +7 -18
- package/cjs/commands/aws/iam/remove-admin/schema.cjs +22 -12
- package/cjs/commands/aws/iam/setup-acl/handler.cjs +227 -14
- package/cjs/commands/aws/iam/setup-acl/index.cjs +13 -33
- package/cjs/commands/aws/iam/setup-acl/schema.cjs +19 -7
- package/cjs/commands/aws/iam/setup-acl/utils.cjs +305 -20
- package/cjs/commands/aws/iam/users/handler.cjs +59 -12
- package/cjs/commands/aws/iam/users/index.cjs +18 -41
- package/cjs/commands/aws/iam/users/schema.cjs +17 -7
- package/cjs/commands/aws/iam/users/utils.cjs +454 -30
- package/cjs/commands/aws/index.cjs +223 -229
- package/cjs/commands/aws/route53/add-record/handler.cjs +264 -18
- package/cjs/commands/aws/route53/add-record/index.cjs +26 -57
- package/cjs/commands/aws/route53/add-record/schema.cjs +43 -11
- package/cjs/commands/aws/route53/add-record/utils.cjs +404 -37
- package/cjs/commands/aws/route53/remove-record/handler.cjs +114 -14
- package/cjs/commands/aws/route53/remove-record/index.cjs +5 -18
- package/cjs/commands/aws/route53/remove-record/schema.cjs +22 -8
- package/cjs/commands/aws/schema.cjs +19 -10
- package/cjs/commands/aws/ses/send-email/handler.cjs +73 -12
- package/cjs/commands/aws/ses/send-email/index.cjs +9 -23
- package/cjs/commands/aws/ses/send-email/schema.cjs +24 -7
- package/cjs/commands/aws/ses/send-email/utils.cjs +87 -11
- package/cjs/commands/aws/ses/smtp-password.cjs +72 -10
- package/cjs/commands/aws/utils/exec-aws.cjs +145 -16
- package/cjs/commands/aws/utils/resolve-policy-path.cjs +15 -6
- package/cjs/commands/aws/utils/resolve-profile.cjs +93 -11
- package/cjs/commands/aws/utils/tty.cjs +54 -10
- package/cjs/commands/db/create-readonly-user/handler.cjs +254 -11
- package/cjs/commands/db/create-readonly-user/index.cjs +18 -40
- package/cjs/commands/db/create-readonly-user/mysql.cjs +102 -9
- package/cjs/commands/db/create-readonly-user/postgres.cjs +209 -9
- package/cjs/commands/db/create-readonly-user/schema.cjs +39 -12
- package/cjs/commands/db/create-readonly-user/utils.cjs +160 -22
- package/cjs/commands/db/index.cjs +46 -44
- package/cjs/commands/doppler/handler.cjs +95 -11
- package/cjs/commands/doppler/index.cjs +33 -68
- package/cjs/commands/doppler/schema.cjs +130 -14
- package/cjs/commands/doppler/set-secret.cjs +378 -19
- package/cjs/commands/doppler/utils/exec.cjs +106 -12
- package/cjs/commands/doppler/utils.cjs +412 -34
- package/cjs/commands/icons/handler.cjs +82 -8
- package/cjs/commands/icons/index.cjs +15 -31
- package/cjs/commands/icons/schema.cjs +101 -13
- package/cjs/commands/icons/utils.cjs +131 -13
- package/cjs/commands/index.cjs +54 -161
- package/cjs/commands/schema.cjs +25 -8
- package/cjs/index.cjs +3 -4
- package/esm/commands/aws/ec2/allow-ip/handler.js +258 -12
- package/esm/commands/aws/ec2/allow-ip/index.js +4 -55
- package/esm/commands/aws/ec2/allow-ip/schema.js +29 -13
- package/esm/commands/aws/ec2/allow-ip/utils.js +363 -39
- package/esm/commands/aws/iam/admin-group.js +104 -15
- package/esm/commands/aws/iam/break-glass/handler.js +124 -12
- package/esm/commands/aws/iam/break-glass/index.js +4 -19
- package/esm/commands/aws/iam/break-glass/schema.js +19 -12
- package/esm/commands/aws/iam/check-admin/handler.js +86 -10
- package/esm/commands/aws/iam/check-admin/index.js +4 -17
- package/esm/commands/aws/iam/check-admin/schema.js +17 -12
- package/esm/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.json +20 -0
- package/esm/commands/aws/iam/policies/sts-federation.json +11 -0
- package/esm/commands/aws/iam/policies/user-admins-deny-list.json +175 -0
- package/esm/commands/aws/iam/policies/user-admins-deny-regions-list.json +41 -0
- package/esm/commands/aws/iam/policies/user-admins-self-service-iam.json +56 -0
- package/esm/commands/aws/iam/policies/user-admins-write-list.json +117 -0
- package/esm/commands/aws/iam/remove-admin/handler.js +117 -11
- package/esm/commands/aws/iam/remove-admin/index.js +4 -18
- package/esm/commands/aws/iam/remove-admin/schema.js +20 -12
- package/esm/commands/aws/iam/setup-acl/handler.js +224 -14
- package/esm/commands/aws/iam/setup-acl/index.js +4 -33
- package/esm/commands/aws/iam/setup-acl/schema.js +18 -7
- package/esm/commands/aws/iam/setup-acl/utils.js +298 -20
- package/esm/commands/aws/iam/users/handler.js +56 -12
- package/esm/commands/aws/iam/users/index.js +4 -41
- package/esm/commands/aws/iam/users/schema.js +16 -7
- package/esm/commands/aws/iam/users/utils.js +440 -30
- package/esm/commands/aws/index.js +142 -229
- package/esm/commands/aws/route53/add-record/handler.js +258 -18
- package/esm/commands/aws/route53/add-record/index.js +4 -57
- package/esm/commands/aws/route53/add-record/schema.js +40 -11
- package/esm/commands/aws/route53/add-record/utils.js +389 -37
- package/esm/commands/aws/route53/remove-record/handler.js +111 -14
- package/esm/commands/aws/route53/remove-record/index.js +3 -18
- package/esm/commands/aws/route53/remove-record/schema.js +21 -8
- package/esm/commands/aws/schema.js +16 -10
- package/esm/commands/aws/ses/send-email/handler.js +70 -12
- package/esm/commands/aws/ses/send-email/index.js +4 -23
- package/esm/commands/aws/ses/send-email/schema.js +23 -7
- package/esm/commands/aws/ses/send-email/utils.js +84 -11
- package/esm/commands/aws/ses/smtp-password.js +69 -10
- package/esm/commands/aws/utils/exec-aws.js +136 -15
- package/esm/commands/aws/utils/resolve-policy-path.js +14 -6
- package/esm/commands/aws/utils/resolve-profile.js +88 -11
- package/esm/commands/aws/utils/tty.js +51 -10
- package/esm/commands/db/create-readonly-user/handler.js +251 -11
- package/esm/commands/db/create-readonly-user/index.js +4 -40
- package/esm/commands/db/create-readonly-user/mysql.js +98 -9
- package/esm/commands/db/create-readonly-user/postgres.js +205 -9
- package/esm/commands/db/create-readonly-user/schema.js +35 -12
- package/esm/commands/db/create-readonly-user/utils.js +151 -22
- package/esm/commands/db/index.js +31 -44
- package/esm/commands/doppler/handler.js +92 -11
- package/esm/commands/doppler/index.js +6 -68
- package/esm/commands/doppler/schema.js +125 -15
- package/esm/commands/doppler/set-secret.js +370 -19
- package/esm/commands/doppler/utils/exec.js +100 -12
- package/esm/commands/doppler/utils.js +396 -34
- package/esm/commands/icons/handler.js +79 -8
- package/esm/commands/icons/index.js +4 -31
- package/esm/commands/icons/schema.js +97 -15
- package/esm/commands/icons/utils.js +125 -13
- package/esm/commands/index.js +47 -155
- package/esm/commands/schema.js +22 -7
- package/esm/index.js +3 -4
- package/package.json +5 -2
- package/vitest.config.ts +11 -0
- package/cjs/__chunks/2FSYDFUV.cjs +0 -146
- package/cjs/__chunks/3A5ZYU5M.cjs +0 -105
- package/cjs/__chunks/3BY5Y4W7.cjs +0 -1
- package/cjs/__chunks/3NOHGUCF.cjs +0 -9
- package/cjs/__chunks/3T2LXW7J.cjs +0 -387
- package/cjs/__chunks/4SFWHCB3.cjs +0 -133
- package/cjs/__chunks/4XT5JEAS.cjs +0 -1
- package/cjs/__chunks/AZEB4H4I.cjs +0 -208
- package/cjs/__chunks/B2KFW7T7.cjs +0 -1
- package/cjs/__chunks/B5ENMOCD.cjs +0 -288
- package/cjs/__chunks/B6XM5KSF.cjs +0 -338
- package/cjs/__chunks/BDU2UCE5.cjs +0 -1
- package/cjs/__chunks/BLMSMR5E.cjs +0 -46
- package/cjs/__chunks/CMUD7LLN.cjs +0 -47
- package/cjs/__chunks/DTQ2LB3I.cjs +0 -49
- package/cjs/__chunks/FM5MVRQF.cjs +0 -23
- package/cjs/__chunks/FRHST54J.cjs +0 -135
- package/cjs/__chunks/FU4VP2LN.cjs +0 -1
- package/cjs/__chunks/G5K4PVIL.cjs +0 -26
- package/cjs/__chunks/GU3R54MK.cjs +0 -127
- package/cjs/__chunks/GUL36IFZ.cjs +0 -61
- package/cjs/__chunks/GZMTNK5N.cjs +0 -1
- package/cjs/__chunks/H74JSDBU.cjs +0 -30
- package/cjs/__chunks/INFOPAHN.cjs +0 -117
- package/cjs/__chunks/J36P6UNE.cjs +0 -75
- package/cjs/__chunks/JB7T7PKQ.cjs +0 -538
- package/cjs/__chunks/JSL2JDMW.cjs +0 -1
- package/cjs/__chunks/K4QNHRTP.cjs +0 -163
- package/cjs/__chunks/KU76LC42.cjs +0 -192
- package/cjs/__chunks/LFDSHANP.cjs +0 -34
- package/cjs/__chunks/LHE24ECK.cjs +0 -192
- package/cjs/__chunks/LIKQ3SI4.cjs +0 -1
- package/cjs/__chunks/LXDF5QVZ.cjs +0 -313
- package/cjs/__chunks/LXOOJR2R.cjs +0 -74
- package/cjs/__chunks/N5MXFOQZ.cjs +0 -167
- package/cjs/__chunks/N7D56LPP.cjs +0 -25
- package/cjs/__chunks/NFXQNOQH.cjs +0 -393
- package/cjs/__chunks/NIMXXHJQ.cjs +0 -92
- package/cjs/__chunks/NLKJIUEX.cjs +0 -126
- package/cjs/__chunks/NT2XXAI6.cjs +0 -89
- package/cjs/__chunks/NWZGFHKH.cjs +0 -121
- package/cjs/__chunks/R3GUC7O7.cjs +0 -110
- package/cjs/__chunks/RPVAXPJ4.cjs +0 -1
- package/cjs/__chunks/S3LVCWPN.cjs +0 -23
- package/cjs/__chunks/SBBNW2BF.cjs +0 -249
- package/cjs/__chunks/T2A7TN4F.cjs +0 -81
- package/cjs/__chunks/TKNNDDIB.cjs +0 -97
- package/cjs/__chunks/TNSRDJPR.cjs +0 -1
- package/cjs/__chunks/UWHPE2GI.cjs +0 -20
- package/cjs/__chunks/W6K7BV74.cjs +0 -1
- package/cjs/__chunks/WBOQ6AUN.cjs +0 -320
- package/cjs/__chunks/WHGN5PZI.cjs +0 -299
- package/cjs/__chunks/WOYF2L6T.cjs +0 -11
- package/cjs/__chunks/X26XY2N6.cjs +0 -18
- package/cjs/__chunks/XMKOKRXZ.cjs +0 -16
- package/cjs/__chunks/XTJPQ3EX.cjs +0 -1
- package/cjs/__chunks/YRHFKTW2.cjs +0 -63
- package/cjs/__chunks/ZMDIOZPT.cjs +0 -22
- package/cjs/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.cjs +0 -28
- package/cjs/commands/aws/iam/policies/sts-federation.cjs +0 -19
- package/cjs/commands/aws/iam/policies/user-admins-deny-list.cjs +0 -183
- package/cjs/commands/aws/iam/policies/user-admins-deny-regions-list.cjs +0 -49
- package/cjs/commands/aws/iam/policies/user-admins-self-service-iam.cjs +0 -64
- package/cjs/commands/aws/iam/policies/user-admins-write-list.cjs +0 -125
- package/esm/__chunks/2FTTEFEK.js +0 -97
- package/esm/__chunks/33UYMUQD.js +0 -338
- package/esm/__chunks/3BKWTHLJ.js +0 -25
- package/esm/__chunks/3HE4BEJX.js +0 -105
- package/esm/__chunks/3QLI5BDL.js +0 -387
- package/esm/__chunks/5GIUAFXL.js +0 -89
- package/esm/__chunks/5ZBVGSOK.js +0 -0
- package/esm/__chunks/6G4PXXKF.js +0 -75
- package/esm/__chunks/73BJG53F.js +0 -11
- package/esm/__chunks/7VF4Q43Q.js +0 -0
- package/esm/__chunks/AEDEAYF5.js +0 -0
- package/esm/__chunks/AZCP3SFJ.js +0 -0
- package/esm/__chunks/B2DVQ2UL.js +0 -61
- package/esm/__chunks/CVQVHLW5.js +0 -0
- package/esm/__chunks/D62SPZXC.js +0 -0
- package/esm/__chunks/DLEJ25GZ.js +0 -121
- package/esm/__chunks/DMYFNF54.js +0 -192
- package/esm/__chunks/DNFTUMGD.js +0 -23
- package/esm/__chunks/DVKD6YSR.js +0 -49
- package/esm/__chunks/EMSH4P76.js +0 -20
- package/esm/__chunks/FFKFVMIE.js +0 -167
- package/esm/__chunks/FL7AILGH.js +0 -133
- package/esm/__chunks/FNXUONAV.js +0 -110
- package/esm/__chunks/G4NJHFFU.js +0 -320
- package/esm/__chunks/HJSXC6HP.js +0 -117
- package/esm/__chunks/IEOGAQYI.js +0 -46
- package/esm/__chunks/IZFGBYGE.js +0 -34
- package/esm/__chunks/J34UVKLA.js +0 -26
- package/esm/__chunks/JZPBBBIG.js +0 -74
- package/esm/__chunks/KWUJP5MG.js +0 -135
- package/esm/__chunks/M35URMT4.js +0 -313
- package/esm/__chunks/NF3M2L6C.js +0 -81
- package/esm/__chunks/O3CCA76Q.js +0 -22
- package/esm/__chunks/PNPPDYKC.js +0 -299
- package/esm/__chunks/QFXN4EHM.js +0 -393
- package/esm/__chunks/QKDDLIWD.js +0 -146
- package/esm/__chunks/QSE66AKY.js +0 -208
- package/esm/__chunks/QUF7PS4R.js +0 -18
- package/esm/__chunks/S7IJPJ4R.js +0 -126
- package/esm/__chunks/SD3E3EPH.js +0 -163
- package/esm/__chunks/SJXQJ7RS.js +0 -92
- package/esm/__chunks/SNCKIAOR.js +0 -0
- package/esm/__chunks/T3TTUHTW.js +0 -30
- package/esm/__chunks/TIOVE6HR.js +0 -0
- package/esm/__chunks/TXKMEIDQ.js +0 -0
- package/esm/__chunks/U7HAE2RF.js +0 -288
- package/esm/__chunks/UXWY7BYE.js +0 -63
- package/esm/__chunks/VKAKISRG.js +0 -192
- package/esm/__chunks/WD2YR6I7.js +0 -0
- package/esm/__chunks/WOZFZKFY.js +0 -0
- package/esm/__chunks/XI7GZDE2.js +0 -0
- package/esm/__chunks/XOUWDB3T.js +0 -249
- package/esm/__chunks/XQN2TA4S.js +0 -23
- package/esm/__chunks/XXK32TIM.js +0 -16
- package/esm/__chunks/YGWTVPZE.js +0 -538
- package/esm/__chunks/YRG7PT33.js +0 -127
- package/esm/__chunks/ZHKVJO2U.js +0 -47
- package/esm/__chunks/ZO55YUSY.js +0 -9
- package/esm/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.js +0 -28
- package/esm/commands/aws/iam/policies/sts-federation.js +0 -19
- package/esm/commands/aws/iam/policies/user-admins-deny-list.js +0 -183
- package/esm/commands/aws/iam/policies/user-admins-deny-regions-list.js +0 -49
- package/esm/commands/aws/iam/policies/user-admins-self-service-iam.js +0 -64
- package/esm/commands/aws/iam/policies/user-admins-write-list.js +0 -125
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
} from "
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { profileField, verboseField } from "../../schema.js";
|
|
2
|
+
import { RECORD_TYPES } from "../add-record/schema.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/commands/aws/route53/remove-record/schema.js
|
|
5
|
+
/**
|
|
6
|
+
* Zod schema for `aws route53 remove-record` options.
|
|
7
|
+
*/
|
|
8
|
+
const removeRecordOptionsSchema = z.object({
|
|
9
|
+
profile: profileField,
|
|
10
|
+
zone: z.string().optional().describe("Hosted zone name or ID (e.g. mountaindev.com or Z123…). Skips the zone picker when set."),
|
|
11
|
+
type: z.string().optional().transform((value) => value === void 0 ? void 0 : value.toUpperCase()).refine((value) => value === void 0 || typeof value === "string" && RECORD_TYPES.includes(value), { message: `Record type must be one of: ${RECORD_TYPES.join(", ")}` }).describe(`DNS record type (${RECORD_TYPES.join(", ")})`),
|
|
12
|
+
name: z.string().optional().describe("Record name relative to the zone (e.g. \"redline\") or a fully-qualified name"),
|
|
13
|
+
yes: z.boolean().optional().default(false).describe("Skip the confirmation prompt"),
|
|
14
|
+
dryRun: z.boolean().optional().default(false).describe("Preview the deletion without calling Route53"),
|
|
15
|
+
verbose: verboseField
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {z.infer<typeof removeRecordOptionsSchema>} RemoveRecordOptions
|
|
19
|
+
*/
|
|
20
|
+
//#endregion
|
|
21
|
+
export { removeRecordOptionsSchema };
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
//#region src/commands/aws/schema.js
|
|
3
|
+
/**
|
|
4
|
+
* Shared Zod field for the AWS profile option.
|
|
5
|
+
*/
|
|
6
|
+
const profileField = z.string().optional().describe("AWS profile (defaults to the AWS_PROFILE environment variable)");
|
|
7
|
+
/**
|
|
8
|
+
* Shared Zod field for the AWS region option.
|
|
9
|
+
*/
|
|
10
|
+
const regionField = z.string().optional().describe("AWS region");
|
|
11
|
+
/**
|
|
12
|
+
* Shared Zod field for verbose logging.
|
|
13
|
+
*/
|
|
14
|
+
const verboseField = z.boolean().optional().default(false).describe("Enable verbose logging for detailed output and error traces");
|
|
15
|
+
//#endregion
|
|
16
|
+
export { profileField, regionField, verboseField };
|
|
@@ -1,12 +1,70 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
} from "
|
|
4
|
-
import "
|
|
5
|
-
import "
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import { resolveProfile } from "../../utils/resolve-profile.js";
|
|
2
|
+
import { sendEmailOptionsSchema } from "./schema.js";
|
|
3
|
+
import { defaultBody, sendViaApi, sendViaSmtp } from "./utils.js";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { ZodError } from "zod";
|
|
6
|
+
//#region src/commands/aws/ses/send-email/handler.js
|
|
7
|
+
/**
|
|
8
|
+
* `aws ses send-email` handler: send a single test email via the SES API or SMTP.
|
|
9
|
+
* @param {unknown} options
|
|
10
|
+
*/
|
|
11
|
+
async function sendEmailCommand(options) {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = sendEmailOptionsSchema.parse(options);
|
|
14
|
+
const { to, from, subject, transport, region, profile: profileOption, accessKeyId, dryRun, verbose } = parsed;
|
|
15
|
+
const body = parsed.body ?? defaultBody();
|
|
16
|
+
const profile = transport === "api" ? await resolveProfile({ profile: profileOption }) : profileOption;
|
|
17
|
+
console.log(chalk.blue("\n📧 SES send-email"));
|
|
18
|
+
console.log(chalk.gray(` transport: ${transport}`));
|
|
19
|
+
console.log(chalk.gray(` region: ${region}`));
|
|
20
|
+
console.log(chalk.gray(` from: ${from}`));
|
|
21
|
+
console.log(chalk.gray(` to: ${to}`));
|
|
22
|
+
console.log(chalk.gray(` subject: ${subject}`));
|
|
23
|
+
if (verbose) console.log(chalk.gray(` body: ${body}`));
|
|
24
|
+
if (dryRun) {
|
|
25
|
+
console.log(chalk.yellow("\n[dry-run] No email sent."));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const messageId = transport === "api" ? await sendViaApi({
|
|
29
|
+
to,
|
|
30
|
+
from,
|
|
31
|
+
subject,
|
|
32
|
+
body,
|
|
33
|
+
region,
|
|
34
|
+
profile
|
|
35
|
+
}) : await sendViaSmtp({
|
|
36
|
+
to,
|
|
37
|
+
from,
|
|
38
|
+
subject,
|
|
39
|
+
body,
|
|
40
|
+
region,
|
|
41
|
+
accessKeyId
|
|
42
|
+
});
|
|
43
|
+
console.log(chalk.green(`\n✅ Email sent${messageId ? ` (messageId: ${messageId})` : ""}.`));
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error instanceof ZodError) {
|
|
46
|
+
console.error(chalk.red("\n❌ Invalid command options:"));
|
|
47
|
+
error.errors.forEach((err) => {
|
|
48
|
+
console.error(chalk.red(` ${err.path.join(".")}: ${err.message}`));
|
|
49
|
+
});
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const verbose = options?.verbose || false;
|
|
53
|
+
console.error(chalk.red("\n❌ send-email failed:"));
|
|
54
|
+
if (error instanceof Error) {
|
|
55
|
+
console.error(chalk.red(` ${error.message}`));
|
|
56
|
+
if (verbose && error.stack) {
|
|
57
|
+
console.error(chalk.gray("\nStack trace:"));
|
|
58
|
+
console.error(chalk.gray(error.stack));
|
|
59
|
+
}
|
|
60
|
+
} else console.error(chalk.red(` ${String(error)}`));
|
|
61
|
+
console.error(chalk.yellow("\n💡 Troubleshooting tips:"));
|
|
62
|
+
console.error(chalk.yellow(" • The sender address must be verified in SES"));
|
|
63
|
+
console.error(chalk.yellow(" • New SES accounts are sandboxed — recipients must also be verified"));
|
|
64
|
+
console.error(chalk.yellow(" • For smtp transport, set SES_SMTP_USER and SES_SMTP_PASSWORD"));
|
|
65
|
+
console.error(chalk.yellow(" • Use --verbose for detailed error information\n"));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { sendEmailCommand };
|
|
@@ -1,23 +1,4 @@
|
|
|
1
|
-
import "
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
defaultBody,
|
|
7
|
-
sendViaApi,
|
|
8
|
-
sendViaSmtp
|
|
9
|
-
} from "../../../../__chunks/UXWY7BYE.js";
|
|
10
|
-
import "../../../../__chunks/B2DVQ2UL.js";
|
|
11
|
-
import "../../../../__chunks/5GIUAFXL.js";
|
|
12
|
-
import {
|
|
13
|
-
sendEmailOptionsSchema
|
|
14
|
-
} from "../../../../__chunks/3BKWTHLJ.js";
|
|
15
|
-
import "../../../../__chunks/73BJG53F.js";
|
|
16
|
-
import "../../../../__chunks/ZHKVJO2U.js";
|
|
17
|
-
export {
|
|
18
|
-
defaultBody,
|
|
19
|
-
sendEmailCommand,
|
|
20
|
-
sendEmailOptionsSchema,
|
|
21
|
-
sendViaApi,
|
|
22
|
-
sendViaSmtp
|
|
23
|
-
};
|
|
1
|
+
import { sendEmailOptionsSchema } from "./schema.js";
|
|
2
|
+
import { defaultBody, sendViaApi, sendViaSmtp } from "./utils.js";
|
|
3
|
+
import { sendEmailCommand } from "./handler.js";
|
|
4
|
+
export { defaultBody, sendEmailCommand, sendEmailOptionsSchema, sendViaApi, sendViaSmtp };
|
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { profileField, verboseField } from "../../schema.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/commands/aws/ses/send-email/schema.js
|
|
4
|
+
/**
|
|
5
|
+
* Zod schema for `aws ses send-email` options.
|
|
6
|
+
*/
|
|
7
|
+
const sendEmailOptionsSchema = z.object({
|
|
8
|
+
to: z.string({ required_error: "--to <address> is required" }).describe("Recipient email address"),
|
|
9
|
+
from: z.string({ required_error: "--from <address> is required" }).describe("Verified SES sender email address"),
|
|
10
|
+
subject: z.string().optional().default("MTN CLI test email").describe("Email subject line"),
|
|
11
|
+
body: z.string().optional().describe("Email body (defaults to a timestamp and random token)"),
|
|
12
|
+
transport: z.enum(["api", "smtp"]).optional().default("api").describe("Send via the SES API (default) or SES SMTP"),
|
|
13
|
+
region: z.string().optional().default("us-east-1").describe("AWS region for the SES endpoint"),
|
|
14
|
+
profile: profileField,
|
|
15
|
+
accessKeyId: z.string().optional().describe("IAM access key ID used as the SMTP username (smtp transport only)"),
|
|
16
|
+
dryRun: z.boolean().optional().default(false).describe("Print the email payload without sending"),
|
|
17
|
+
verbose: verboseField
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {z.infer<typeof sendEmailOptionsSchema>} SendEmailOptions
|
|
21
|
+
*/
|
|
22
|
+
//#endregion
|
|
23
|
+
export { sendEmailOptionsSchema };
|
|
@@ -1,11 +1,84 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { calculateSmtpPassword, smtpEndpoint } from "../smtp-password.js";
|
|
2
|
+
import { randomBytes } from "crypto";
|
|
3
|
+
//#region src/commands/aws/ses/send-email/utils.js
|
|
4
|
+
/**
|
|
5
|
+
* Build a default test-email body with a timestamp and random token.
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
function defaultBody() {
|
|
9
|
+
return `MTN CLI test email — random token: ${randomBytes(16).toString("hex")} — sent at ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Send an email through the SES API using the AWS SDK.
|
|
13
|
+
* @param {{
|
|
14
|
+
* to: string;
|
|
15
|
+
* from: string;
|
|
16
|
+
* subject: string;
|
|
17
|
+
* body: string;
|
|
18
|
+
* region: string;
|
|
19
|
+
* profile?: string;
|
|
20
|
+
* }} params
|
|
21
|
+
* @returns {Promise<string | undefined>} The SES message id
|
|
22
|
+
*/
|
|
23
|
+
async function sendViaApi(params) {
|
|
24
|
+
const { to, from, subject, body, region, profile } = params;
|
|
25
|
+
const { SESClient, SendEmailCommand } = await import("@aws-sdk/client-ses");
|
|
26
|
+
const { fromNodeProviderChain } = await import("@aws-sdk/credential-providers");
|
|
27
|
+
const client = new SESClient({
|
|
28
|
+
region,
|
|
29
|
+
credentials: fromNodeProviderChain(profile ? { profile } : {})
|
|
30
|
+
});
|
|
31
|
+
const command = new SendEmailCommand({
|
|
32
|
+
Source: from,
|
|
33
|
+
Destination: { ToAddresses: [to] },
|
|
34
|
+
Message: {
|
|
35
|
+
Subject: { Data: subject },
|
|
36
|
+
Body: { Text: { Data: body } }
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return (await client.send(command)).MessageId;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Send an email through the SES SMTP endpoint using nodemailer.
|
|
43
|
+
* Credentials come from SES_SMTP_USER/SES_SMTP_PASSWORD, or the password is
|
|
44
|
+
* derived from AWS_SECRET_ACCESS_KEY when only that is available.
|
|
45
|
+
* @param {{
|
|
46
|
+
* to: string;
|
|
47
|
+
* from: string;
|
|
48
|
+
* subject: string;
|
|
49
|
+
* body: string;
|
|
50
|
+
* region: string;
|
|
51
|
+
* accessKeyId?: string;
|
|
52
|
+
* }} params
|
|
53
|
+
* @returns {Promise<string | undefined>} The SMTP message id
|
|
54
|
+
*/
|
|
55
|
+
async function sendViaSmtp(params) {
|
|
56
|
+
const { to, from, subject, body, region, accessKeyId } = params;
|
|
57
|
+
const user = process.env.SES_SMTP_USER ?? accessKeyId;
|
|
58
|
+
let pass = process.env.SES_SMTP_PASSWORD;
|
|
59
|
+
if (!pass) {
|
|
60
|
+
const secret = process.env.AWS_SECRET_ACCESS_KEY;
|
|
61
|
+
if (secret) pass = calculateSmtpPassword({
|
|
62
|
+
secretAccessKey: secret,
|
|
63
|
+
region
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (!user || !pass) throw new Error("SMTP transport requires SES_SMTP_USER and SES_SMTP_PASSWORD, or AWS_SECRET_ACCESS_KEY (plus --access-key-id) to derive the SMTP password.");
|
|
67
|
+
const nodemailerModule = await import("nodemailer");
|
|
68
|
+
return (await (nodemailerModule.default ?? nodemailerModule).createTransport({
|
|
69
|
+
host: smtpEndpoint(region),
|
|
70
|
+
port: 587,
|
|
71
|
+
secure: false,
|
|
72
|
+
auth: {
|
|
73
|
+
user,
|
|
74
|
+
pass
|
|
75
|
+
}
|
|
76
|
+
}).sendMail({
|
|
77
|
+
from,
|
|
78
|
+
to,
|
|
79
|
+
subject,
|
|
80
|
+
text: body
|
|
81
|
+
})).messageId;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
export { defaultBody, sendViaApi, sendViaSmtp };
|
|
@@ -1,10 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { createHmac } from "crypto";
|
|
2
|
+
//#region src/commands/aws/ses/smtp-password.js
|
|
3
|
+
/**
|
|
4
|
+
* AWS regions that expose an SES SMTP endpoint.
|
|
5
|
+
*/
|
|
6
|
+
const SMTP_REGIONS = /* @__PURE__ */ new Set([
|
|
7
|
+
"us-east-2",
|
|
8
|
+
"us-east-1",
|
|
9
|
+
"us-west-1",
|
|
10
|
+
"us-west-2",
|
|
11
|
+
"ap-south-1",
|
|
12
|
+
"ap-northeast-2",
|
|
13
|
+
"ap-southeast-1",
|
|
14
|
+
"ap-southeast-2",
|
|
15
|
+
"ap-northeast-1",
|
|
16
|
+
"ca-central-1",
|
|
17
|
+
"eu-central-1",
|
|
18
|
+
"eu-west-1",
|
|
19
|
+
"eu-west-2",
|
|
20
|
+
"eu-west-3",
|
|
21
|
+
"eu-south-1",
|
|
22
|
+
"eu-north-1",
|
|
23
|
+
"sa-east-1",
|
|
24
|
+
"us-gov-west-1",
|
|
25
|
+
"us-gov-east-1",
|
|
26
|
+
"me-south-1",
|
|
27
|
+
"af-south-1",
|
|
28
|
+
"il-central-1"
|
|
29
|
+
]);
|
|
30
|
+
const DATE = "11111111";
|
|
31
|
+
const SERVICE = "ses";
|
|
32
|
+
const MESSAGE = "SendRawEmail";
|
|
33
|
+
const TERMINAL = "aws4_request";
|
|
34
|
+
const VERSION = 4;
|
|
35
|
+
/**
|
|
36
|
+
* @param {Buffer} key
|
|
37
|
+
* @param {string} message
|
|
38
|
+
* @returns {Buffer}
|
|
39
|
+
*/
|
|
40
|
+
function sign(key, message) {
|
|
41
|
+
return createHmac("sha256", key).update(message, "utf8").digest();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Derive an SES SMTP password from an IAM secret access key, following the
|
|
45
|
+
* AWS Signature V4 algorithm with SES-specific constants.
|
|
46
|
+
* @see https://docs.aws.amazon.com/ses/latest/dg/smtp-credentials.html#smtp-credentials-convert
|
|
47
|
+
* @param {{ secretAccessKey: string; region: string }} params
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
function calculateSmtpPassword(params) {
|
|
51
|
+
const { secretAccessKey, region } = params;
|
|
52
|
+
if (!SMTP_REGIONS.has(region)) throw new Error(`calculateSmtpPassword: region "${region}" has no SES SMTP endpoint`);
|
|
53
|
+
let signature = sign(Buffer.from(`AWS4${secretAccessKey}`, "utf8"), DATE);
|
|
54
|
+
signature = sign(signature, region);
|
|
55
|
+
signature = sign(signature, SERVICE);
|
|
56
|
+
signature = sign(signature, TERMINAL);
|
|
57
|
+
signature = sign(signature, MESSAGE);
|
|
58
|
+
return Buffer.concat([Buffer.from([VERSION]), signature]).toString("base64");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* SES SMTP endpoint host for a region.
|
|
62
|
+
* @param {string} region
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
function smtpEndpoint(region) {
|
|
66
|
+
return `email-smtp.${region}.amazonaws.com`;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { SMTP_REGIONS, calculateSmtpPassword, smtpEndpoint };
|
|
@@ -1,16 +1,137 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
3
|
+
//#region src/commands/aws/utils/exec-aws.js
|
|
4
|
+
/**
|
|
5
|
+
* Error raised when the AWS CLI (or aws-vault) exits non-zero.
|
|
6
|
+
* The `code` is the AWS service error code parsed from stderr (e.g. "UnauthorizedOperation").
|
|
7
|
+
*/
|
|
8
|
+
var AwsCliError = class extends Error {
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} message
|
|
11
|
+
* @param {string | null} [code]
|
|
12
|
+
*/
|
|
13
|
+
constructor(message, code = null) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "AwsCliError";
|
|
16
|
+
this.code = code;
|
|
17
|
+
}
|
|
16
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* Extract the AWS service error code from a CLI stderr string.
|
|
21
|
+
* AWS prints codes in parentheses, e.g. "...(UnauthorizedOperation) when calling...".
|
|
22
|
+
* @param {string} stderr
|
|
23
|
+
* @returns {string | null}
|
|
24
|
+
*/
|
|
25
|
+
function parseAwsErrorCode(stderr) {
|
|
26
|
+
return /\(([A-Za-z.]+)\)/.exec(stderr)?.[1] ?? null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Verify that the given executables are available on PATH.
|
|
30
|
+
* @param {string[]} commands
|
|
31
|
+
* @throws {Error} If any command is missing
|
|
32
|
+
*/
|
|
33
|
+
function requireCommands(commands) {
|
|
34
|
+
const missing = commands.filter((command) => {
|
|
35
|
+
return spawnSync("which", [command], { stdio: "pipe" }).status !== 0;
|
|
36
|
+
});
|
|
37
|
+
if (missing.length > 0) throw new Error(`Required command(s) not found on PATH: ${missing.join(", ")}`);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Run an AWS CLI command using a credential profile.
|
|
41
|
+
* Uses an argument array (no shell) so JSON arguments need no escaping.
|
|
42
|
+
* @param {{
|
|
43
|
+
* profile?: string;
|
|
44
|
+
* region?: string;
|
|
45
|
+
* args: string[];
|
|
46
|
+
* expectJson?: boolean;
|
|
47
|
+
* verbose?: boolean;
|
|
48
|
+
* env?: Record<string, string>;
|
|
49
|
+
* }} params
|
|
50
|
+
* @returns {any} Parsed JSON when expectJson is true, otherwise trimmed stdout string
|
|
51
|
+
*/
|
|
52
|
+
function runAws(params) {
|
|
53
|
+
const { profile, region, args, expectJson = true, verbose = false, env } = params;
|
|
54
|
+
/** @type {string[]} */
|
|
55
|
+
const finalArgs = [];
|
|
56
|
+
if (profile) finalArgs.push("--profile", profile);
|
|
57
|
+
if (region) finalArgs.push("--region", region);
|
|
58
|
+
finalArgs.push("--no-cli-pager", ...args);
|
|
59
|
+
if (expectJson) finalArgs.push("--output", "json");
|
|
60
|
+
return spawnAws("aws", finalArgs, {
|
|
61
|
+
expectJson,
|
|
62
|
+
verbose,
|
|
63
|
+
env
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run an AWS CLI command through `aws-vault exec` for profiles whose credentials
|
|
68
|
+
* are stored in the OS keychain rather than ~/.aws/credentials.
|
|
69
|
+
* @param {{
|
|
70
|
+
* profile: string;
|
|
71
|
+
* args: string[];
|
|
72
|
+
* duration?: string;
|
|
73
|
+
* expectJson?: boolean;
|
|
74
|
+
* verbose?: boolean;
|
|
75
|
+
* }} params
|
|
76
|
+
* @returns {any}
|
|
77
|
+
*/
|
|
78
|
+
function runAwsVault(params) {
|
|
79
|
+
const { profile, args, duration = "15m", expectJson = true, verbose = false } = params;
|
|
80
|
+
/** @type {string[]} */
|
|
81
|
+
const awsArgs = ["--no-cli-pager", ...args];
|
|
82
|
+
if (expectJson) awsArgs.push("--output", "json");
|
|
83
|
+
return spawnAws("aws-vault", [
|
|
84
|
+
"exec",
|
|
85
|
+
profile,
|
|
86
|
+
"--duration",
|
|
87
|
+
duration,
|
|
88
|
+
"--",
|
|
89
|
+
"aws",
|
|
90
|
+
...awsArgs
|
|
91
|
+
], {
|
|
92
|
+
expectJson,
|
|
93
|
+
verbose
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} command
|
|
98
|
+
* @param {string[]} args
|
|
99
|
+
* @param {{ expectJson: boolean; verbose: boolean; env?: Record<string, string> }} options
|
|
100
|
+
* @returns {any}
|
|
101
|
+
*/
|
|
102
|
+
function spawnAws(command, args, options) {
|
|
103
|
+
const { expectJson, verbose, env } = options;
|
|
104
|
+
if (verbose) console.log(chalk.gray(` Executing: ${command} ${args.join(" ")}`));
|
|
105
|
+
const result = spawnSync(command, args, {
|
|
106
|
+
encoding: "utf-8",
|
|
107
|
+
env: env ? {
|
|
108
|
+
...process.env,
|
|
109
|
+
...env
|
|
110
|
+
} : process.env
|
|
111
|
+
});
|
|
112
|
+
if (result.error) throw new AwsCliError(result.error.message);
|
|
113
|
+
if (result.status !== 0) {
|
|
114
|
+
const stderr = (result.stderr || result.stdout || "").trim();
|
|
115
|
+
throw new AwsCliError(stderr || `Command failed: ${command} ${args.join(" ")}`, parseAwsErrorCode(stderr));
|
|
116
|
+
}
|
|
117
|
+
const stdout = (result.stdout ?? "").trim();
|
|
118
|
+
if (!expectJson) return stdout;
|
|
119
|
+
if (!stdout) return {};
|
|
120
|
+
return JSON.parse(stdout);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Fetch the caller identity for a profile (account id and ARN).
|
|
124
|
+
* @param {{ profile?: string; region?: string; verbose?: boolean }} params
|
|
125
|
+
* @returns {{ Account: string; Arn: string; UserId: string }}
|
|
126
|
+
*/
|
|
127
|
+
function getCallerIdentity(params) {
|
|
128
|
+
const { profile, region, verbose } = params;
|
|
129
|
+
return runAws({
|
|
130
|
+
profile,
|
|
131
|
+
region,
|
|
132
|
+
args: ["sts", "get-caller-identity"],
|
|
133
|
+
verbose
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { AwsCliError, getCallerIdentity, parseAwsErrorCode, requireCommands, runAws, runAwsVault };
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { fileURLToPath } from "url";
|
|
2
|
+
//#region src/commands/aws/utils/resolve-policy-path.js
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the absolute path to a bundled IAM policy document.
|
|
5
|
+
* Policies live alongside the source in `iam/policies/`, and the CLI runs from
|
|
6
|
+
* source, so this path is valid at runtime.
|
|
7
|
+
* @param {string} fileName - Policy JSON file name (e.g. "user-admins-write-list.json")
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
function resolvePolicyPath(fileName) {
|
|
11
|
+
return fileURLToPath(new URL(`../iam/policies/${fileName}`, import.meta.url));
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { resolvePolicyPath };
|
|
@@ -1,11 +1,88 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import { prompt, promptChoice } from "./tty.js";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
//#region src/commands/aws/utils/resolve-profile.js
|
|
7
|
+
/**
|
|
8
|
+
* Above this many matches we ask the user to narrow the filter rather than
|
|
9
|
+
* printing an unusable wall of numbered options.
|
|
10
|
+
*/
|
|
11
|
+
const MAX_LISTED = 30;
|
|
12
|
+
/**
|
|
13
|
+
* Read profile names from ~/.aws/config (honoring AWS_CONFIG_FILE).
|
|
14
|
+
* Handles both `[profile NAME]` entries and a bare `[default]` section.
|
|
15
|
+
* @returns {string[]}
|
|
16
|
+
*/
|
|
17
|
+
function listConfigProfiles() {
|
|
18
|
+
const configPath = process.env.AWS_CONFIG_FILE ?? join(homedir(), ".aws", "config");
|
|
19
|
+
let contents;
|
|
20
|
+
try {
|
|
21
|
+
contents = readFileSync(configPath, "utf-8");
|
|
22
|
+
} catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
/** @type {string[]} */
|
|
26
|
+
const profiles = [];
|
|
27
|
+
const profileRe = /^\[profile\s+(.+?)\]$/;
|
|
28
|
+
for (const rawLine of contents.split("\n")) {
|
|
29
|
+
const line = rawLine.trim();
|
|
30
|
+
const match = profileRe.exec(line);
|
|
31
|
+
if (match?.[1]) profiles.push(match[1].trim());
|
|
32
|
+
else if (line === "[default]") profiles.push("default");
|
|
33
|
+
}
|
|
34
|
+
return profiles;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Interactively narrow a profile list by substring, then pick one.
|
|
38
|
+
* A single match auto-selects; too many matches asks the user to narrow.
|
|
39
|
+
* @param {string[]} profiles
|
|
40
|
+
* @returns {Promise<string>}
|
|
41
|
+
*/
|
|
42
|
+
async function pickProfileInteractive(profiles) {
|
|
43
|
+
for (;;) {
|
|
44
|
+
const filter = (await prompt(chalk.cyan("Filter profiles (substring, Enter for all): "))).trim().toLowerCase();
|
|
45
|
+
const matches = filter ? profiles.filter((name) => name.toLowerCase().includes(filter)) : profiles;
|
|
46
|
+
if (matches.length === 0) {
|
|
47
|
+
console.log(chalk.yellow(` No profiles match "${filter}". Try again.`));
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (matches.length === 1) {
|
|
51
|
+
const only = matches[0] ?? "";
|
|
52
|
+
console.log(chalk.green(` → ${only}`));
|
|
53
|
+
return only;
|
|
54
|
+
}
|
|
55
|
+
if (matches.length > MAX_LISTED) {
|
|
56
|
+
console.log(chalk.yellow(` ${matches.length} profiles match — narrow the filter (over ${MAX_LISTED} to list).`));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
return matches[await promptChoice({
|
|
60
|
+
message: chalk.cyan("Select a profile:"),
|
|
61
|
+
options: matches
|
|
62
|
+
})] ?? "";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the AWS profile for a single-profile command.
|
|
67
|
+
*
|
|
68
|
+
* Precedence: explicit `--profile`, then `AWS_PROFILE`, then — only when stdin
|
|
69
|
+
* is a TTY — an interactive filter-and-pick over `~/.aws/config` profiles.
|
|
70
|
+
* Returns `undefined` when nothing is set and we cannot prompt (non-interactive
|
|
71
|
+
* shell, or no config profiles), leaving each command's existing "no profile"
|
|
72
|
+
* handling in place.
|
|
73
|
+
* @param {{ profile?: string; interactive?: boolean }} params
|
|
74
|
+
* @returns {Promise<string | undefined>}
|
|
75
|
+
*/
|
|
76
|
+
async function resolveProfile(params) {
|
|
77
|
+
const explicit = params.profile ?? process.env.AWS_PROFILE;
|
|
78
|
+
if (explicit) return explicit;
|
|
79
|
+
if (!(params.interactive ?? Boolean(process.stdin.isTTY))) return;
|
|
80
|
+
const profiles = listConfigProfiles();
|
|
81
|
+
if (profiles.length === 0) return;
|
|
82
|
+
console.log(chalk.yellow("\nNo AWS profile selected (--profile and AWS_PROFILE are unset)."));
|
|
83
|
+
const chosen = await pickProfileInteractive(profiles);
|
|
84
|
+
console.log(chalk.gray(`Using profile "${chosen}" for this command.\n`));
|
|
85
|
+
return chosen;
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { listConfigProfiles, pickProfileInteractive, resolveProfile };
|