@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.
Files changed (264) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/cjs/__chunks/VH7oDXx4.cjs +28 -0
  3. package/cjs/commands/aws/ec2/allow-ip/handler.cjs +261 -12
  4. package/cjs/commands/aws/ec2/allow-ip/index.cjs +25 -55
  5. package/cjs/commands/aws/ec2/allow-ip/schema.cjs +33 -13
  6. package/cjs/commands/aws/ec2/allow-ip/utils.cjs +379 -39
  7. package/cjs/commands/aws/iam/admin-group.cjs +111 -15
  8. package/cjs/commands/aws/iam/break-glass/handler.cjs +127 -12
  9. package/cjs/commands/aws/iam/break-glass/index.cjs +7 -19
  10. package/cjs/commands/aws/iam/break-glass/schema.cjs +21 -12
  11. package/cjs/commands/aws/iam/check-admin/handler.cjs +89 -10
  12. package/cjs/commands/aws/iam/check-admin/index.cjs +7 -17
  13. package/cjs/commands/aws/iam/check-admin/schema.cjs +19 -12
  14. package/cjs/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.json +20 -0
  15. package/cjs/commands/aws/iam/policies/sts-federation.json +11 -0
  16. package/cjs/commands/aws/iam/policies/user-admins-deny-list.json +175 -0
  17. package/cjs/commands/aws/iam/policies/user-admins-deny-regions-list.json +41 -0
  18. package/cjs/commands/aws/iam/policies/user-admins-self-service-iam.json +56 -0
  19. package/cjs/commands/aws/iam/policies/user-admins-write-list.json +117 -0
  20. package/cjs/commands/aws/iam/remove-admin/handler.cjs +120 -11
  21. package/cjs/commands/aws/iam/remove-admin/index.cjs +7 -18
  22. package/cjs/commands/aws/iam/remove-admin/schema.cjs +22 -12
  23. package/cjs/commands/aws/iam/setup-acl/handler.cjs +227 -14
  24. package/cjs/commands/aws/iam/setup-acl/index.cjs +13 -33
  25. package/cjs/commands/aws/iam/setup-acl/schema.cjs +19 -7
  26. package/cjs/commands/aws/iam/setup-acl/utils.cjs +305 -20
  27. package/cjs/commands/aws/iam/users/handler.cjs +59 -12
  28. package/cjs/commands/aws/iam/users/index.cjs +18 -41
  29. package/cjs/commands/aws/iam/users/schema.cjs +17 -7
  30. package/cjs/commands/aws/iam/users/utils.cjs +454 -30
  31. package/cjs/commands/aws/index.cjs +223 -229
  32. package/cjs/commands/aws/route53/add-record/handler.cjs +264 -18
  33. package/cjs/commands/aws/route53/add-record/index.cjs +26 -57
  34. package/cjs/commands/aws/route53/add-record/schema.cjs +43 -11
  35. package/cjs/commands/aws/route53/add-record/utils.cjs +404 -37
  36. package/cjs/commands/aws/route53/remove-record/handler.cjs +114 -14
  37. package/cjs/commands/aws/route53/remove-record/index.cjs +5 -18
  38. package/cjs/commands/aws/route53/remove-record/schema.cjs +22 -8
  39. package/cjs/commands/aws/schema.cjs +19 -10
  40. package/cjs/commands/aws/ses/send-email/handler.cjs +73 -12
  41. package/cjs/commands/aws/ses/send-email/index.cjs +9 -23
  42. package/cjs/commands/aws/ses/send-email/schema.cjs +24 -7
  43. package/cjs/commands/aws/ses/send-email/utils.cjs +87 -11
  44. package/cjs/commands/aws/ses/smtp-password.cjs +72 -10
  45. package/cjs/commands/aws/utils/exec-aws.cjs +145 -16
  46. package/cjs/commands/aws/utils/resolve-policy-path.cjs +15 -6
  47. package/cjs/commands/aws/utils/resolve-profile.cjs +93 -11
  48. package/cjs/commands/aws/utils/tty.cjs +54 -10
  49. package/cjs/commands/db/create-readonly-user/handler.cjs +254 -11
  50. package/cjs/commands/db/create-readonly-user/index.cjs +18 -40
  51. package/cjs/commands/db/create-readonly-user/mysql.cjs +102 -9
  52. package/cjs/commands/db/create-readonly-user/postgres.cjs +209 -9
  53. package/cjs/commands/db/create-readonly-user/schema.cjs +39 -12
  54. package/cjs/commands/db/create-readonly-user/utils.cjs +160 -22
  55. package/cjs/commands/db/index.cjs +46 -44
  56. package/cjs/commands/doppler/handler.cjs +95 -11
  57. package/cjs/commands/doppler/index.cjs +33 -68
  58. package/cjs/commands/doppler/schema.cjs +130 -14
  59. package/cjs/commands/doppler/set-secret.cjs +378 -19
  60. package/cjs/commands/doppler/utils/exec.cjs +106 -12
  61. package/cjs/commands/doppler/utils.cjs +412 -34
  62. package/cjs/commands/icons/handler.cjs +82 -8
  63. package/cjs/commands/icons/index.cjs +15 -31
  64. package/cjs/commands/icons/schema.cjs +101 -13
  65. package/cjs/commands/icons/utils.cjs +131 -13
  66. package/cjs/commands/index.cjs +54 -161
  67. package/cjs/commands/schema.cjs +25 -8
  68. package/cjs/index.cjs +3 -4
  69. package/esm/commands/aws/ec2/allow-ip/handler.js +258 -12
  70. package/esm/commands/aws/ec2/allow-ip/index.js +4 -55
  71. package/esm/commands/aws/ec2/allow-ip/schema.js +29 -13
  72. package/esm/commands/aws/ec2/allow-ip/utils.js +363 -39
  73. package/esm/commands/aws/iam/admin-group.js +104 -15
  74. package/esm/commands/aws/iam/break-glass/handler.js +124 -12
  75. package/esm/commands/aws/iam/break-glass/index.js +4 -19
  76. package/esm/commands/aws/iam/break-glass/schema.js +19 -12
  77. package/esm/commands/aws/iam/check-admin/handler.js +86 -10
  78. package/esm/commands/aws/iam/check-admin/index.js +4 -17
  79. package/esm/commands/aws/iam/check-admin/schema.js +17 -12
  80. package/esm/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.json +20 -0
  81. package/esm/commands/aws/iam/policies/sts-federation.json +11 -0
  82. package/esm/commands/aws/iam/policies/user-admins-deny-list.json +175 -0
  83. package/esm/commands/aws/iam/policies/user-admins-deny-regions-list.json +41 -0
  84. package/esm/commands/aws/iam/policies/user-admins-self-service-iam.json +56 -0
  85. package/esm/commands/aws/iam/policies/user-admins-write-list.json +117 -0
  86. package/esm/commands/aws/iam/remove-admin/handler.js +117 -11
  87. package/esm/commands/aws/iam/remove-admin/index.js +4 -18
  88. package/esm/commands/aws/iam/remove-admin/schema.js +20 -12
  89. package/esm/commands/aws/iam/setup-acl/handler.js +224 -14
  90. package/esm/commands/aws/iam/setup-acl/index.js +4 -33
  91. package/esm/commands/aws/iam/setup-acl/schema.js +18 -7
  92. package/esm/commands/aws/iam/setup-acl/utils.js +298 -20
  93. package/esm/commands/aws/iam/users/handler.js +56 -12
  94. package/esm/commands/aws/iam/users/index.js +4 -41
  95. package/esm/commands/aws/iam/users/schema.js +16 -7
  96. package/esm/commands/aws/iam/users/utils.js +440 -30
  97. package/esm/commands/aws/index.js +142 -229
  98. package/esm/commands/aws/route53/add-record/handler.js +258 -18
  99. package/esm/commands/aws/route53/add-record/index.js +4 -57
  100. package/esm/commands/aws/route53/add-record/schema.js +40 -11
  101. package/esm/commands/aws/route53/add-record/utils.js +389 -37
  102. package/esm/commands/aws/route53/remove-record/handler.js +111 -14
  103. package/esm/commands/aws/route53/remove-record/index.js +3 -18
  104. package/esm/commands/aws/route53/remove-record/schema.js +21 -8
  105. package/esm/commands/aws/schema.js +16 -10
  106. package/esm/commands/aws/ses/send-email/handler.js +70 -12
  107. package/esm/commands/aws/ses/send-email/index.js +4 -23
  108. package/esm/commands/aws/ses/send-email/schema.js +23 -7
  109. package/esm/commands/aws/ses/send-email/utils.js +84 -11
  110. package/esm/commands/aws/ses/smtp-password.js +69 -10
  111. package/esm/commands/aws/utils/exec-aws.js +136 -15
  112. package/esm/commands/aws/utils/resolve-policy-path.js +14 -6
  113. package/esm/commands/aws/utils/resolve-profile.js +88 -11
  114. package/esm/commands/aws/utils/tty.js +51 -10
  115. package/esm/commands/db/create-readonly-user/handler.js +251 -11
  116. package/esm/commands/db/create-readonly-user/index.js +4 -40
  117. package/esm/commands/db/create-readonly-user/mysql.js +98 -9
  118. package/esm/commands/db/create-readonly-user/postgres.js +205 -9
  119. package/esm/commands/db/create-readonly-user/schema.js +35 -12
  120. package/esm/commands/db/create-readonly-user/utils.js +151 -22
  121. package/esm/commands/db/index.js +31 -44
  122. package/esm/commands/doppler/handler.js +92 -11
  123. package/esm/commands/doppler/index.js +6 -68
  124. package/esm/commands/doppler/schema.js +125 -15
  125. package/esm/commands/doppler/set-secret.js +370 -19
  126. package/esm/commands/doppler/utils/exec.js +100 -12
  127. package/esm/commands/doppler/utils.js +396 -34
  128. package/esm/commands/icons/handler.js +79 -8
  129. package/esm/commands/icons/index.js +4 -31
  130. package/esm/commands/icons/schema.js +97 -15
  131. package/esm/commands/icons/utils.js +125 -13
  132. package/esm/commands/index.js +47 -155
  133. package/esm/commands/schema.js +22 -7
  134. package/esm/index.js +3 -4
  135. package/package.json +5 -2
  136. package/vitest.config.ts +11 -0
  137. package/cjs/__chunks/2FSYDFUV.cjs +0 -146
  138. package/cjs/__chunks/3A5ZYU5M.cjs +0 -105
  139. package/cjs/__chunks/3BY5Y4W7.cjs +0 -1
  140. package/cjs/__chunks/3NOHGUCF.cjs +0 -9
  141. package/cjs/__chunks/3T2LXW7J.cjs +0 -387
  142. package/cjs/__chunks/4SFWHCB3.cjs +0 -133
  143. package/cjs/__chunks/4XT5JEAS.cjs +0 -1
  144. package/cjs/__chunks/AZEB4H4I.cjs +0 -208
  145. package/cjs/__chunks/B2KFW7T7.cjs +0 -1
  146. package/cjs/__chunks/B5ENMOCD.cjs +0 -288
  147. package/cjs/__chunks/B6XM5KSF.cjs +0 -338
  148. package/cjs/__chunks/BDU2UCE5.cjs +0 -1
  149. package/cjs/__chunks/BLMSMR5E.cjs +0 -46
  150. package/cjs/__chunks/CMUD7LLN.cjs +0 -47
  151. package/cjs/__chunks/DTQ2LB3I.cjs +0 -49
  152. package/cjs/__chunks/FM5MVRQF.cjs +0 -23
  153. package/cjs/__chunks/FRHST54J.cjs +0 -135
  154. package/cjs/__chunks/FU4VP2LN.cjs +0 -1
  155. package/cjs/__chunks/G5K4PVIL.cjs +0 -26
  156. package/cjs/__chunks/GU3R54MK.cjs +0 -127
  157. package/cjs/__chunks/GUL36IFZ.cjs +0 -61
  158. package/cjs/__chunks/GZMTNK5N.cjs +0 -1
  159. package/cjs/__chunks/H74JSDBU.cjs +0 -30
  160. package/cjs/__chunks/INFOPAHN.cjs +0 -117
  161. package/cjs/__chunks/J36P6UNE.cjs +0 -75
  162. package/cjs/__chunks/JB7T7PKQ.cjs +0 -538
  163. package/cjs/__chunks/JSL2JDMW.cjs +0 -1
  164. package/cjs/__chunks/K4QNHRTP.cjs +0 -163
  165. package/cjs/__chunks/KU76LC42.cjs +0 -192
  166. package/cjs/__chunks/LFDSHANP.cjs +0 -34
  167. package/cjs/__chunks/LHE24ECK.cjs +0 -192
  168. package/cjs/__chunks/LIKQ3SI4.cjs +0 -1
  169. package/cjs/__chunks/LXDF5QVZ.cjs +0 -313
  170. package/cjs/__chunks/LXOOJR2R.cjs +0 -74
  171. package/cjs/__chunks/N5MXFOQZ.cjs +0 -167
  172. package/cjs/__chunks/N7D56LPP.cjs +0 -25
  173. package/cjs/__chunks/NFXQNOQH.cjs +0 -393
  174. package/cjs/__chunks/NIMXXHJQ.cjs +0 -92
  175. package/cjs/__chunks/NLKJIUEX.cjs +0 -126
  176. package/cjs/__chunks/NT2XXAI6.cjs +0 -89
  177. package/cjs/__chunks/NWZGFHKH.cjs +0 -121
  178. package/cjs/__chunks/R3GUC7O7.cjs +0 -110
  179. package/cjs/__chunks/RPVAXPJ4.cjs +0 -1
  180. package/cjs/__chunks/S3LVCWPN.cjs +0 -23
  181. package/cjs/__chunks/SBBNW2BF.cjs +0 -249
  182. package/cjs/__chunks/T2A7TN4F.cjs +0 -81
  183. package/cjs/__chunks/TKNNDDIB.cjs +0 -97
  184. package/cjs/__chunks/TNSRDJPR.cjs +0 -1
  185. package/cjs/__chunks/UWHPE2GI.cjs +0 -20
  186. package/cjs/__chunks/W6K7BV74.cjs +0 -1
  187. package/cjs/__chunks/WBOQ6AUN.cjs +0 -320
  188. package/cjs/__chunks/WHGN5PZI.cjs +0 -299
  189. package/cjs/__chunks/WOYF2L6T.cjs +0 -11
  190. package/cjs/__chunks/X26XY2N6.cjs +0 -18
  191. package/cjs/__chunks/XMKOKRXZ.cjs +0 -16
  192. package/cjs/__chunks/XTJPQ3EX.cjs +0 -1
  193. package/cjs/__chunks/YRHFKTW2.cjs +0 -63
  194. package/cjs/__chunks/ZMDIOZPT.cjs +0 -22
  195. package/cjs/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.cjs +0 -28
  196. package/cjs/commands/aws/iam/policies/sts-federation.cjs +0 -19
  197. package/cjs/commands/aws/iam/policies/user-admins-deny-list.cjs +0 -183
  198. package/cjs/commands/aws/iam/policies/user-admins-deny-regions-list.cjs +0 -49
  199. package/cjs/commands/aws/iam/policies/user-admins-self-service-iam.cjs +0 -64
  200. package/cjs/commands/aws/iam/policies/user-admins-write-list.cjs +0 -125
  201. package/esm/__chunks/2FTTEFEK.js +0 -97
  202. package/esm/__chunks/33UYMUQD.js +0 -338
  203. package/esm/__chunks/3BKWTHLJ.js +0 -25
  204. package/esm/__chunks/3HE4BEJX.js +0 -105
  205. package/esm/__chunks/3QLI5BDL.js +0 -387
  206. package/esm/__chunks/5GIUAFXL.js +0 -89
  207. package/esm/__chunks/5ZBVGSOK.js +0 -0
  208. package/esm/__chunks/6G4PXXKF.js +0 -75
  209. package/esm/__chunks/73BJG53F.js +0 -11
  210. package/esm/__chunks/7VF4Q43Q.js +0 -0
  211. package/esm/__chunks/AEDEAYF5.js +0 -0
  212. package/esm/__chunks/AZCP3SFJ.js +0 -0
  213. package/esm/__chunks/B2DVQ2UL.js +0 -61
  214. package/esm/__chunks/CVQVHLW5.js +0 -0
  215. package/esm/__chunks/D62SPZXC.js +0 -0
  216. package/esm/__chunks/DLEJ25GZ.js +0 -121
  217. package/esm/__chunks/DMYFNF54.js +0 -192
  218. package/esm/__chunks/DNFTUMGD.js +0 -23
  219. package/esm/__chunks/DVKD6YSR.js +0 -49
  220. package/esm/__chunks/EMSH4P76.js +0 -20
  221. package/esm/__chunks/FFKFVMIE.js +0 -167
  222. package/esm/__chunks/FL7AILGH.js +0 -133
  223. package/esm/__chunks/FNXUONAV.js +0 -110
  224. package/esm/__chunks/G4NJHFFU.js +0 -320
  225. package/esm/__chunks/HJSXC6HP.js +0 -117
  226. package/esm/__chunks/IEOGAQYI.js +0 -46
  227. package/esm/__chunks/IZFGBYGE.js +0 -34
  228. package/esm/__chunks/J34UVKLA.js +0 -26
  229. package/esm/__chunks/JZPBBBIG.js +0 -74
  230. package/esm/__chunks/KWUJP5MG.js +0 -135
  231. package/esm/__chunks/M35URMT4.js +0 -313
  232. package/esm/__chunks/NF3M2L6C.js +0 -81
  233. package/esm/__chunks/O3CCA76Q.js +0 -22
  234. package/esm/__chunks/PNPPDYKC.js +0 -299
  235. package/esm/__chunks/QFXN4EHM.js +0 -393
  236. package/esm/__chunks/QKDDLIWD.js +0 -146
  237. package/esm/__chunks/QSE66AKY.js +0 -208
  238. package/esm/__chunks/QUF7PS4R.js +0 -18
  239. package/esm/__chunks/S7IJPJ4R.js +0 -126
  240. package/esm/__chunks/SD3E3EPH.js +0 -163
  241. package/esm/__chunks/SJXQJ7RS.js +0 -92
  242. package/esm/__chunks/SNCKIAOR.js +0 -0
  243. package/esm/__chunks/T3TTUHTW.js +0 -30
  244. package/esm/__chunks/TIOVE6HR.js +0 -0
  245. package/esm/__chunks/TXKMEIDQ.js +0 -0
  246. package/esm/__chunks/U7HAE2RF.js +0 -288
  247. package/esm/__chunks/UXWY7BYE.js +0 -63
  248. package/esm/__chunks/VKAKISRG.js +0 -192
  249. package/esm/__chunks/WD2YR6I7.js +0 -0
  250. package/esm/__chunks/WOZFZKFY.js +0 -0
  251. package/esm/__chunks/XI7GZDE2.js +0 -0
  252. package/esm/__chunks/XOUWDB3T.js +0 -249
  253. package/esm/__chunks/XQN2TA4S.js +0 -23
  254. package/esm/__chunks/XXK32TIM.js +0 -16
  255. package/esm/__chunks/YGWTVPZE.js +0 -538
  256. package/esm/__chunks/YRG7PT33.js +0 -127
  257. package/esm/__chunks/ZHKVJO2U.js +0 -47
  258. package/esm/__chunks/ZO55YUSY.js +0 -9
  259. package/esm/commands/aws/iam/policies/s3-restrictions-deny-non-s3-services.js +0 -28
  260. package/esm/commands/aws/iam/policies/sts-federation.js +0 -19
  261. package/esm/commands/aws/iam/policies/user-admins-deny-list.js +0 -183
  262. package/esm/commands/aws/iam/policies/user-admins-deny-regions-list.js +0 -49
  263. package/esm/commands/aws/iam/policies/user-admins-self-service-iam.js +0 -64
  264. package/esm/commands/aws/iam/policies/user-admins-write-list.js +0 -125
@@ -1,30 +1,440 @@
1
- import {
2
- buildUsername,
3
- createCliAccessKey,
4
- createUsersInteractive,
5
- ensureAclGroupsExist,
6
- ensureUser,
7
- ensureUserInGroup,
8
- inferAccountName,
9
- isValidSlug,
10
- normalizeIdentifier,
11
- normalizeSlug,
12
- parseMdUsername,
13
- roleToGroup
14
- } from "../../../../__chunks/3QLI5BDL.js";
15
- import "../../../../__chunks/FNXUONAV.js";
16
- import "../../../../__chunks/ZHKVJO2U.js";
17
- export {
18
- buildUsername,
19
- createCliAccessKey,
20
- createUsersInteractive,
21
- ensureAclGroupsExist,
22
- ensureUser,
23
- ensureUserInGroup,
24
- inferAccountName,
25
- isValidSlug,
26
- normalizeIdentifier,
27
- normalizeSlug,
28
- parseMdUsername,
29
- roleToGroup
30
- };
1
+ import { prompt, promptYesNo } from "../../utils/tty.js";
2
+ import { runAws } from "../../utils/exec-aws.js";
3
+ import chalk from "chalk";
4
+ //#region src/commands/aws/iam/users/utils.js
5
+ const KNOWN_ROLES = [
6
+ "read-only",
7
+ "iam-admin",
8
+ "power-user"
9
+ ];
10
+ const REQUIRED_ACL_GROUPS = [
11
+ "acl-read-only",
12
+ "acl-iam-full-access",
13
+ "acl-power-users"
14
+ ];
15
+ /**
16
+ * Map a user role to its ACL group.
17
+ * @param {string} role
18
+ * @returns {string}
19
+ */
20
+ function roleToGroup(role) {
21
+ switch (role) {
22
+ case "read-only": return "acl-read-only";
23
+ case "iam-admin": return "acl-iam-full-access";
24
+ case "power-user": return "acl-power-users";
25
+ default: throw new Error(`Unknown role: ${role}`);
26
+ }
27
+ }
28
+ /**
29
+ * Lowercase and hyphenate a raw value into a slug.
30
+ * @param {string} value
31
+ * @returns {string}
32
+ */
33
+ function normalizeSlug(value) {
34
+ return value.toLowerCase().replace(/ /g, "-");
35
+ }
36
+ /**
37
+ * @param {string} value
38
+ * @returns {boolean}
39
+ */
40
+ function isValidSlug(value) {
41
+ return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(value);
42
+ }
43
+ /**
44
+ * Build the canonical IAM username.
45
+ * @param {{ identifier: string; accountName: string; role: string }} params
46
+ * @returns {string}
47
+ */
48
+ function buildUsername(params) {
49
+ const { identifier, accountName, role } = params;
50
+ return `md-${identifier}-${accountName}-${role}`;
51
+ }
52
+ /**
53
+ * Strip md- prefix, role suffix, and account suffix from a raw identifier.
54
+ * @param {{ raw: string; accountName: string; role: string }} params
55
+ * @returns {string}
56
+ */
57
+ function normalizeIdentifier(params) {
58
+ const { raw, accountName, role } = params;
59
+ let identifier = normalizeSlug(raw);
60
+ if (identifier.startsWith("md-")) identifier = identifier.slice(3);
61
+ if (identifier.endsWith(`-${role}`)) identifier = identifier.slice(0, -(role.length + 1));
62
+ else for (const knownRole of KNOWN_ROLES) if (identifier.endsWith(`-${knownRole}`)) {
63
+ identifier = identifier.slice(0, -(knownRole.length + 1));
64
+ break;
65
+ }
66
+ if (identifier.endsWith(`-${accountName}`)) identifier = identifier.slice(0, -(accountName.length + 1));
67
+ return identifier;
68
+ }
69
+ /**
70
+ * Parse an md-{identifier}-{account}-{role} username into parts.
71
+ * Identifier is the first segment; account is everything between it and the role
72
+ * (so multi-segment account slugs still work).
73
+ * @param {string} name - Profile name or IAM user name
74
+ * @returns {{ identifier: string; accountName: string; role: string } | null}
75
+ */
76
+ function parseMdUsername(name) {
77
+ let value = normalizeSlug(name);
78
+ if (!value.startsWith("md-")) return null;
79
+ value = value.slice(3);
80
+ const rolesByLength = [...KNOWN_ROLES].sort((a, b) => b.length - a.length);
81
+ /** @type {string | null} */
82
+ let role = null;
83
+ for (const knownRole of rolesByLength) if (value.endsWith(`-${knownRole}`)) {
84
+ role = knownRole;
85
+ value = value.slice(0, -(knownRole.length + 1));
86
+ break;
87
+ }
88
+ if (!role) return null;
89
+ const dash = value.indexOf("-");
90
+ if (dash <= 0 || dash === value.length - 1) return null;
91
+ const identifier = value.slice(0, dash);
92
+ const accountName = value.slice(dash + 1);
93
+ if (!isValidSlug(identifier) || !isValidSlug(accountName)) return null;
94
+ return {
95
+ identifier,
96
+ accountName,
97
+ role
98
+ };
99
+ }
100
+ /**
101
+ * Infer the AWS account slug from the caller IAM user / profile name.
102
+ * Prefers the ARN username, then the profile string.
103
+ * @param {{
104
+ * profile?: string;
105
+ * identity?: { Arn?: string };
106
+ * }} params
107
+ * @returns {string | null}
108
+ */
109
+ function inferAccountName(params) {
110
+ const { profile, identity } = params;
111
+ const arn = identity?.Arn ?? "";
112
+ const username = arn.includes("/") ? arn.split("/").pop() ?? "" : "";
113
+ for (const candidate of [username, profile ?? ""]) {
114
+ if (!candidate) continue;
115
+ const parsed = parseMdUsername(candidate);
116
+ if (parsed) return parsed.accountName;
117
+ }
118
+ return null;
119
+ }
120
+ /**
121
+ * @param {{ username: string; profile?: string; verbose?: boolean }} params
122
+ * @returns {boolean}
123
+ */
124
+ function userExists(params) {
125
+ const { username, profile, verbose } = params;
126
+ try {
127
+ runAws({
128
+ profile,
129
+ args: [
130
+ "iam",
131
+ "get-user",
132
+ "--user-name",
133
+ username
134
+ ],
135
+ verbose
136
+ });
137
+ return true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+ /**
143
+ * Create the IAM user if it does not exist.
144
+ * @param {{ username: string; profile?: string; verbose?: boolean }} params
145
+ */
146
+ function ensureUser(params) {
147
+ const { username, profile, verbose } = params;
148
+ if (userExists({
149
+ username,
150
+ profile,
151
+ verbose
152
+ })) {
153
+ console.log(` User exists: ${username}`);
154
+ return;
155
+ }
156
+ console.log(` Create user: ${username}`);
157
+ runAws({
158
+ profile,
159
+ args: [
160
+ "iam",
161
+ "create-user",
162
+ "--user-name",
163
+ username,
164
+ "--path",
165
+ "/"
166
+ ],
167
+ verbose
168
+ });
169
+ }
170
+ /**
171
+ * Add the user to the group if not already a member.
172
+ * @param {{ username: string; group: string; profile?: string; verbose?: boolean }} params
173
+ */
174
+ function ensureUserInGroup(params) {
175
+ const { username, group, profile, verbose } = params;
176
+ if (runAws({
177
+ profile,
178
+ args: [
179
+ "iam",
180
+ "get-group",
181
+ "--group-name",
182
+ group,
183
+ "--query",
184
+ `Users[?UserName=='${username}'].UserName`,
185
+ "--output",
186
+ "text"
187
+ ],
188
+ expectJson: false,
189
+ verbose
190
+ })) {
191
+ console.log(` Already in group: ${group}`);
192
+ return;
193
+ }
194
+ console.log(` Add to group: ${group}`);
195
+ runAws({
196
+ profile,
197
+ args: [
198
+ "iam",
199
+ "add-user-to-group",
200
+ "--user-name",
201
+ username,
202
+ "--group-name",
203
+ group
204
+ ],
205
+ expectJson: false,
206
+ verbose
207
+ });
208
+ }
209
+ /**
210
+ * Create a CLI access key for the user and print a one-line setup command.
211
+ * @param {{ username: string; profile?: string; verbose?: boolean }} params
212
+ * @returns {boolean} False when the user already has the maximum number of keys
213
+ */
214
+ function createCliAccessKey(params) {
215
+ const { username, profile, verbose } = params;
216
+ const keyCountRaw = runAws({
217
+ profile,
218
+ args: [
219
+ "iam",
220
+ "list-access-keys",
221
+ "--user-name",
222
+ username,
223
+ "--query",
224
+ "length(AccessKeyMetadata)",
225
+ "--output",
226
+ "text"
227
+ ],
228
+ expectJson: false,
229
+ verbose
230
+ });
231
+ if ((Number.parseInt(keyCountRaw, 10) || 0) >= 2) {
232
+ console.log(chalk.yellow(` WARNING: ${username} already has 2 access keys (AWS max).`));
233
+ console.log(chalk.yellow(" Delete an existing key before creating a new one."));
234
+ return false;
235
+ }
236
+ const [accessKeyId, secretAccessKey] = runAws({
237
+ profile,
238
+ args: [
239
+ "iam",
240
+ "create-access-key",
241
+ "--user-name",
242
+ username,
243
+ "--query",
244
+ "AccessKey.[AccessKeyId,SecretAccessKey]",
245
+ "--output",
246
+ "text"
247
+ ],
248
+ expectJson: false,
249
+ verbose
250
+ }).split(/\s+/);
251
+ if (!accessKeyId || !secretAccessKey) throw new Error("Failed to parse the newly created access key");
252
+ const setupCmd = ` export AWS_ACCESS_KEY_ID='${accessKeyId}' AWS_SECRET_ACCESS_KEY='${secretAccessKey}' && aws-vault add ${username} --env --add-config && aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID" --profile ${username} && aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY" --profile ${username} && aws configure set region us-east-1 --profile ${username}`;
253
+ console.log("");
254
+ console.log(` CLI credentials for ${username} (secret shown once — copy now):`);
255
+ console.log(" ────────────────────────────────────────────────────────────────");
256
+ console.log(" YOU MUST run the command below with a LEADING SPACE so it is not saved");
257
+ console.log(" to shell history — same idea as: TEST=\"a\" && echo $TEST");
258
+ console.log("");
259
+ console.log(setupCmd);
260
+ console.log(" ────────────────────────────────────────────────────────────────");
261
+ console.log("");
262
+ return true;
263
+ }
264
+ /**
265
+ * Verify the required ACL groups exist before creating users.
266
+ * @param {{ profile?: string; verbose?: boolean }} params
267
+ * @returns {boolean}
268
+ */
269
+ function ensureAclGroupsExist(params) {
270
+ const { profile, verbose } = params;
271
+ const missing = REQUIRED_ACL_GROUPS.filter((group) => {
272
+ try {
273
+ runAws({
274
+ profile,
275
+ args: [
276
+ "iam",
277
+ "get-group",
278
+ "--group-name",
279
+ group
280
+ ],
281
+ verbose
282
+ });
283
+ return false;
284
+ } catch {
285
+ return true;
286
+ }
287
+ });
288
+ if (missing.length > 0) {
289
+ missing.forEach((group) => console.log(` Missing group: ${group}`));
290
+ console.log("");
291
+ console.log(" Run `mtn aws iam setup-acl` first to create ACL groups and policies.");
292
+ return false;
293
+ }
294
+ return true;
295
+ }
296
+ /**
297
+ * @param {{ accountName?: string; inferredAccountName?: string | null }} params
298
+ * @returns {Promise<string>}
299
+ */
300
+ async function promptAccountName(params) {
301
+ const { accountName, inferredAccountName } = params;
302
+ if (accountName) {
303
+ const slug = normalizeSlug(accountName);
304
+ if (isValidSlug(slug)) {
305
+ console.log(` Account name: ${slug}`);
306
+ return slug;
307
+ }
308
+ console.log(" Invalid account name option — falling back to prompt.");
309
+ }
310
+ const defaultSlug = inferredAccountName && isValidSlug(normalizeSlug(inferredAccountName)) ? normalizeSlug(inferredAccountName) : null;
311
+ for (;;) {
312
+ const hint = defaultSlug ? ` [default: ${defaultSlug}]` : " (e.g. vincent)";
313
+ const raw = await prompt(` AWS account name${hint} — used in md-{identifier}-{account}-{role}: `);
314
+ if (raw === "" && defaultSlug) {
315
+ console.log(` → account name: ${defaultSlug}`);
316
+ return defaultSlug;
317
+ }
318
+ const slug = normalizeSlug(raw);
319
+ if (isValidSlug(slug)) {
320
+ if (raw !== slug) console.log(` → account name: ${slug}`);
321
+ return slug;
322
+ }
323
+ console.log(" Invalid account name — use lowercase letters, numbers, and hyphens" + (defaultSlug ? ", or press Enter to accept the default." : "."));
324
+ }
325
+ }
326
+ /**
327
+ * @returns {Promise<string>}
328
+ */
329
+ async function promptRole() {
330
+ for (;;) {
331
+ console.log(" Role (username will be md-{identifier}-{account}-{role}):");
332
+ console.log(" 1) read-only → acl-read-only");
333
+ console.log(" 2) iam-admin → acl-iam-full-access");
334
+ console.log(" 3) power-user → acl-power-users");
335
+ switch (await prompt(" Choice [1-3]: ")) {
336
+ case "1":
337
+ case "read-only": return "read-only";
338
+ case "2":
339
+ case "iam-admin": return "iam-admin";
340
+ case "3":
341
+ case "power-user": return "power-user";
342
+ default: console.log(" Invalid choice — enter 1, 2, 3, or the role name.");
343
+ }
344
+ }
345
+ }
346
+ /**
347
+ * @param {{ accountName: string; role: string }} params
348
+ * @returns {Promise<string>}
349
+ */
350
+ async function promptIdentifier(params) {
351
+ const { accountName, role } = params;
352
+ const exampleUser = buildUsername({
353
+ identifier: "declan",
354
+ accountName,
355
+ role
356
+ });
357
+ for (;;) {
358
+ const raw = await prompt(` Identifier (e.g. declan, or full ${exampleUser}): `);
359
+ const lowerRaw = normalizeSlug(raw);
360
+ const identifier = normalizeIdentifier({
361
+ raw,
362
+ accountName,
363
+ role
364
+ });
365
+ if (isValidSlug(identifier)) {
366
+ if (lowerRaw !== identifier) console.log(` → parsed identifier: ${identifier}`);
367
+ return identifier;
368
+ }
369
+ console.log(" Invalid identifier — use lowercase letters, numbers, and hyphens.");
370
+ console.log(" Tip: enter just the short name (e.g. declan).");
371
+ }
372
+ }
373
+ /**
374
+ * Interactively create IAM users, assign ACL groups, and print CLI credentials.
375
+ * @param {{
376
+ * accountName?: string;
377
+ * profile?: string;
378
+ * identity?: { Arn?: string };
379
+ * verbose?: boolean;
380
+ * }} params
381
+ */
382
+ async function createUsersInteractive(params) {
383
+ const { accountName: accountNameOption, profile, identity, verbose } = params;
384
+ if (!ensureAclGroupsExist({
385
+ profile,
386
+ verbose
387
+ })) return;
388
+ console.log("");
389
+ const accountName = await promptAccountName({
390
+ accountName: accountNameOption,
391
+ inferredAccountName: inferAccountName({
392
+ profile,
393
+ identity
394
+ })
395
+ });
396
+ console.log("");
397
+ while (await promptYesNo({
398
+ message: " Create another user?",
399
+ defaultYes: false
400
+ })) {
401
+ console.log("");
402
+ const role = await promptRole();
403
+ const username = buildUsername({
404
+ identifier: await promptIdentifier({
405
+ accountName,
406
+ role
407
+ }),
408
+ accountName,
409
+ role
410
+ });
411
+ const group = roleToGroup(role);
412
+ console.log("");
413
+ console.log(` → user: ${username}`);
414
+ console.log(` → group: ${group}`);
415
+ console.log("");
416
+ ensureUser({
417
+ username,
418
+ profile,
419
+ verbose
420
+ });
421
+ ensureUserInGroup({
422
+ username,
423
+ group,
424
+ profile,
425
+ verbose
426
+ });
427
+ try {
428
+ createCliAccessKey({
429
+ username,
430
+ profile,
431
+ verbose
432
+ });
433
+ } catch (error) {
434
+ console.log(chalk.yellow(` Could not create access key: ${error instanceof Error ? error.message : String(error)}`));
435
+ }
436
+ console.log("");
437
+ }
438
+ }
439
+ //#endregion
440
+ export { buildUsername, createCliAccessKey, createUsersInteractive, ensureAclGroupsExist, ensureUser, ensureUserInGroup, inferAccountName, isValidSlug, normalizeIdentifier, normalizeSlug, parseMdUsername, roleToGroup };