@continuous-excellence/ze-great-dashboard-aws 0.1.33 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -743,7 +743,7 @@ import { z as z2 } from "zod";
743
743
 
744
744
  // packages/aws/src/internal-board.ts
745
745
  import { z } from "zod";
746
- var durationSchema = z.string().regex(/^\d+(?:ms|s|m|h)$/).refine((value) => Number.parseInt(value, 10) > 0).brand();
746
+ var durationSchema = z.string().regex(/^\d+(?:ms|s|m|h)$/).refine((value2) => Number.parseInt(value2, 10) > 0).brand();
747
747
  var positionSchema = z.object({
748
748
  x: z.number().int().min(0),
749
749
  y: z.number().int().min(0),
@@ -834,8 +834,8 @@ async function assembleRelease(input) {
834
834
  );
835
835
  return { metadata, files };
836
836
  }
837
- function sha256(value) {
838
- return createHash("sha256").update(value).digest("hex");
837
+ function sha256(value2) {
838
+ return createHash("sha256").update(value2).digest("hex");
839
839
  }
840
840
 
841
841
  // packages/aws/src/bootstrap.ts
@@ -892,8 +892,8 @@ function coreBootstrapOutputs(stack) {
892
892
  (output) => Boolean(output.OutputValue)
893
893
  ).map((output) => [output.OutputKey, output.OutputValue])
894
894
  );
895
- const required = ["ArtifactBucketName", "ApplicationStackName", "CloudFormationExecutionRoleArn"];
896
- const missing = required.filter((key) => !values[key]);
895
+ const required2 = ["ArtifactBucketName", "ApplicationStackName", "CloudFormationExecutionRoleArn"];
896
+ const missing = required2.filter((key) => !values[key]);
897
897
  if (missing.length) throw new Error(`Core stack JSON is missing outputs: ${missing.join(", ")}`);
898
898
  return values;
899
899
  }
@@ -910,8 +910,656 @@ function requiredBootstrapParameters(kind, values) {
910
910
  ];
911
911
  const missing = keys.filter((key) => !values[key]);
912
912
  if (missing.length) throw new Error(`Missing required bootstrap values: ${missing.join(", ")}`);
913
- const optional = kind === "core" ? ["RuntimeSecretArn", "ArtifactKmsKeyArn"] : [];
914
- return [...keys, ...optional].filter((key) => values[key] !== void 0).map((ParameterKey) => ({ ParameterKey, ParameterValue: values[ParameterKey] ?? "" }));
913
+ const optional2 = kind === "core" ? ["RuntimeSecretArn", "ArtifactKmsKeyArn"] : [];
914
+ return [...keys, ...optional2].filter((key) => values[key] !== void 0).map((ParameterKey) => ({ ParameterKey, ParameterValue: values[ParameterKey] ?? "" }));
915
+ }
916
+
917
+ // packages/aws/src/handoff.ts
918
+ var githubSubjectKeys = [
919
+ "repository_owner",
920
+ "repository_owner_id",
921
+ "repository",
922
+ "repository_id",
923
+ "context"
924
+ ];
925
+ function outputValues(stack) {
926
+ return Object.fromEntries(
927
+ (stack.Outputs ?? []).filter(
928
+ (entry) => typeof entry.OutputKey === "string" && typeof entry.OutputValue === "string"
929
+ ).map(({ OutputKey, OutputValue }) => [OutputKey, OutputValue])
930
+ );
931
+ }
932
+ function parameterValues(stack) {
933
+ return Object.fromEntries(
934
+ (stack.Parameters ?? []).filter(
935
+ (entry) => typeof entry.ParameterKey === "string" && typeof entry.ParameterValue === "string"
936
+ ).map(({ ParameterKey, ParameterValue }) => [ParameterKey, ParameterValue])
937
+ );
938
+ }
939
+ function capturedStackIdentity(input) {
940
+ const candidate = input && typeof input === "object" && "Stacks" in input ? input.Stacks?.[0] : input;
941
+ if (!candidate || typeof candidate !== "object") return {};
942
+ const { StackName, StackId } = candidate;
943
+ return {
944
+ ...typeof StackName === "string" ? { StackName } : {},
945
+ ...typeof StackId === "string" ? { StackId } : {}
946
+ };
947
+ }
948
+ function required(value2, name) {
949
+ if (!value2) throw new Error(`Bootstrap manifest is missing ${name}`);
950
+ return value2;
951
+ }
952
+ function region(config) {
953
+ return required(config.region, "region");
954
+ }
955
+ function stackName(config, kind) {
956
+ return required(
957
+ kind === "core" ? config.core?.stackName : config.githubOidc?.stackName,
958
+ `${kind}.stackName`
959
+ );
960
+ }
961
+ function changeSetCommands(input) {
962
+ const name = `${input.kind}-initial-review`;
963
+ const stack = stackName(input.config, input.kind);
964
+ const common = ["--stack-name", stack, "--region", region(input.config)];
965
+ const parameters = [
966
+ "ze-great-dashboard-aws",
967
+ "bootstrap",
968
+ "parameters",
969
+ "--kind",
970
+ input.kind,
971
+ "--config",
972
+ input.configPath,
973
+ "--output",
974
+ input.parameterPath
975
+ ];
976
+ if (input.kind === "github-oidc")
977
+ parameters.push("--core-stack-json", input.coreCapturePath ?? "core-deployed-stack.json");
978
+ return [
979
+ { name: "generate-parameters", args: parameters },
980
+ {
981
+ name: "create-change-set",
982
+ args: [
983
+ "aws",
984
+ "cloudformation",
985
+ "create-change-set",
986
+ ...common,
987
+ "--change-set-name",
988
+ name,
989
+ "--change-set-type",
990
+ "CREATE",
991
+ "--template-body",
992
+ `file://${input.templatePath}`,
993
+ "--parameters",
994
+ `file://${input.parameterPath}`,
995
+ "--capabilities",
996
+ "CAPABILITY_NAMED_IAM",
997
+ "--no-cli-pager"
998
+ ]
999
+ },
1000
+ {
1001
+ name: "wait-for-change-set",
1002
+ args: [
1003
+ "aws",
1004
+ "cloudformation",
1005
+ "wait",
1006
+ "change-set-create-complete",
1007
+ ...common,
1008
+ "--change-set-name",
1009
+ name
1010
+ ]
1011
+ },
1012
+ {
1013
+ name: "review-change-set",
1014
+ args: [
1015
+ "aws",
1016
+ "cloudformation",
1017
+ "describe-change-set",
1018
+ ...common,
1019
+ "--change-set-name",
1020
+ name,
1021
+ "--no-cli-pager"
1022
+ ]
1023
+ },
1024
+ {
1025
+ name: "execute-reviewed-change-set",
1026
+ args: ["aws", "cloudformation", "execute-change-set", ...common, "--change-set-name", name]
1027
+ },
1028
+ {
1029
+ name: "wait-for-stack",
1030
+ args: ["aws", "cloudformation", "wait", "stack-create-complete", ...common]
1031
+ },
1032
+ {
1033
+ name: "capture-stack",
1034
+ captureFile: `${input.kind}-deployed-stack.json`,
1035
+ args: ["aws", "cloudformation", "describe-stacks", ...common, "--no-cli-pager"]
1036
+ }
1037
+ ];
1038
+ }
1039
+ var githubOidcProvider = {
1040
+ name: "github-oidc",
1041
+ async prerequisite(config, runner) {
1042
+ const repository = config.githubOidc?.repository;
1043
+ if (!repository)
1044
+ return {
1045
+ status: "unverified",
1046
+ blocking: false,
1047
+ detail: "GitHub repository is absent from the manifest."
1048
+ };
1049
+ if (!runner)
1050
+ return {
1051
+ status: "unverified",
1052
+ blocking: false,
1053
+ detail: "GitHub OIDC subject setting was not queried."
1054
+ };
1055
+ try {
1056
+ const response = JSON.parse(
1057
+ await runner.execute("gh", ["api", `repos/${repository}/actions/oidc/customization/sub`])
1058
+ );
1059
+ const keys = response.include_claim_keys;
1060
+ if (response.use_default === false && Array.isArray(keys) && githubSubjectKeys.every((key) => keys.includes(key)))
1061
+ return {
1062
+ status: "ready",
1063
+ blocking: false,
1064
+ detail: "GitHub emits the immutable owner/repository-ID OIDC subject required by this adapter."
1065
+ };
1066
+ return {
1067
+ status: "immutable-subject-required",
1068
+ blocking: true,
1069
+ detail: "Coordinate migration: inventory and temporarily make existing name-based trust policies compatible, enable immutable GitHub OIDC subjects as a separate GitHub-admin action, verify existing deployments, then retire legacy trust."
1070
+ };
1071
+ } catch {
1072
+ return {
1073
+ status: "unverified",
1074
+ blocking: false,
1075
+ detail: "GitHub OIDC subject setting could not be verified (gh CLI, authentication, permission, or network unavailable)."
1076
+ };
1077
+ }
1078
+ }
1079
+ };
1080
+ async function contracts() {
1081
+ return {
1082
+ core: bootstrapContractVersion(await bootstrapTemplate("core")),
1083
+ githubOidc: bootstrapContractVersion(await bootstrapTemplate("github-oidc"))
1084
+ };
1085
+ }
1086
+ async function bootstrapHandoff(input) {
1087
+ const expectedContracts = await contracts();
1088
+ const coreCapturePath = input.coreStackPath ?? "core-deployed-stack.json";
1089
+ const core = input.coreStack === void 0 ? void 0 : deployedBootstrapStack(input.coreStack, expectedContracts.core);
1090
+ const oidc = input.githubOidcStack === void 0 ? void 0 : deployedBootstrapStack(input.githubOidcStack, expectedContracts.githubOidc);
1091
+ if (!core) {
1092
+ requiredBootstrapParameters("core", {
1093
+ ArtifactBucketName: input.config.core?.artifactBucketName,
1094
+ ApplicationStackName: input.config.core?.applicationStackName,
1095
+ DashboardFunctionName: input.config.core?.dashboardFunctionName
1096
+ });
1097
+ const templatePath = await bootstrapTemplatePath("core");
1098
+ return {
1099
+ phase: "core",
1100
+ expectedContracts,
1101
+ templatePath,
1102
+ parameterPath: "core-bootstrap.json",
1103
+ expectedOutputs: [
1104
+ "BootstrapContractVersion",
1105
+ "ArtifactBucketName",
1106
+ "ApplicationStackName",
1107
+ "CloudFormationExecutionRoleArn"
1108
+ ],
1109
+ requiredCapturedFiles: [],
1110
+ reviewCheckpoints: [
1111
+ "Review every IAM action and CAPABILITY_NAMED_IAM acknowledgement.",
1112
+ "Do not approve retained bucket or role replacements."
1113
+ ],
1114
+ commands: changeSetCommands({
1115
+ kind: "core",
1116
+ config: input.config,
1117
+ configPath: input.configPath,
1118
+ templatePath,
1119
+ parameterPath: "core-bootstrap.json"
1120
+ })
1121
+ };
1122
+ }
1123
+ coreBootstrapOutputs(core);
1124
+ if (!oidc) {
1125
+ requiredBootstrapParameters("github-oidc", {
1126
+ GitHubOidcProviderArn: input.config.githubOidc?.providerArn,
1127
+ GitHubRepository: input.config.githubOidc?.repository,
1128
+ GitHubOwnerId: input.config.githubOidc?.ownerId,
1129
+ GitHubRepositoryId: input.config.githubOidc?.repositoryId,
1130
+ GitHubEnvironment: input.config.githubOidc?.environment,
1131
+ ...coreBootstrapOutputs(core)
1132
+ });
1133
+ const templatePath = await bootstrapTemplatePath("github-oidc");
1134
+ return {
1135
+ phase: "github-oidc",
1136
+ expectedContracts,
1137
+ templatePath,
1138
+ parameterPath: "github-oidc-bootstrap.json",
1139
+ expectedOutputs: ["BootstrapContractVersion", "GitHubDeployRoleArn"],
1140
+ requiredCapturedFiles: [coreCapturePath],
1141
+ reviewCheckpoints: [
1142
+ "Review the exact immutable GitHub OIDC subject, audience, bucket lambda/* prefix, application stack, and execution role.",
1143
+ "Do not execute until the GitHub Environment prerequisite is ready."
1144
+ ],
1145
+ commands: changeSetCommands({
1146
+ kind: "github-oidc",
1147
+ config: input.config,
1148
+ configPath: input.configPath,
1149
+ templatePath,
1150
+ parameterPath: "github-oidc-bootstrap.json",
1151
+ coreCapturePath
1152
+ }),
1153
+ prerequisite: await (input.provider ?? githubOidcProvider).prerequisite(
1154
+ input.config,
1155
+ input.runner
1156
+ )
1157
+ };
1158
+ }
1159
+ const prerequisite = await (input.provider ?? githubOidcProvider).prerequisite(
1160
+ input.config,
1161
+ input.runner
1162
+ );
1163
+ if (prerequisite.status !== "ready")
1164
+ return {
1165
+ phase: "github-environment",
1166
+ expectedContracts,
1167
+ expectedOutputs: [],
1168
+ requiredCapturedFiles: [coreCapturePath, "github-oidc-deployed-stack.json"],
1169
+ reviewCheckpoints: [
1170
+ "A GitHub administrator must complete and verify the immutable-subject migration before deployments."
1171
+ ],
1172
+ commands: [],
1173
+ prerequisite
1174
+ };
1175
+ return {
1176
+ phase: "application-gateway",
1177
+ expectedContracts,
1178
+ expectedOutputs: [],
1179
+ requiredCapturedFiles: [coreCapturePath, "github-oidc-deployed-stack.json"],
1180
+ reviewCheckpoints: [
1181
+ "Consumer owns gateway selection, private Lambda permission, authentication, and smoke tests."
1182
+ ],
1183
+ commands: [],
1184
+ prerequisite
1185
+ };
1186
+ }
1187
+ function assertEqual(actual, expected, label) {
1188
+ if (actual !== expected)
1189
+ throw new Error(
1190
+ `${label} mismatch (captured: ${actual ?? "missing"}; manifest: ${expected ?? "missing"})`
1191
+ );
1192
+ }
1193
+ async function verifyBootstrap(input) {
1194
+ const expected = await contracts();
1195
+ const core = deployedBootstrapStack(input.coreStack, expected.core);
1196
+ const oidc = deployedBootstrapStack(input.githubOidcStack, expected.githubOidc);
1197
+ for (const [kind, capture] of [
1198
+ ["Core", capturedStackIdentity(input.coreStack)],
1199
+ ["GitHub OIDC", capturedStackIdentity(input.githubOidcStack)]
1200
+ ]) {
1201
+ const expectedName = kind === "Core" ? input.config.core?.stackName : input.config.githubOidc?.stackName;
1202
+ assertEqual(capture.StackName, expectedName, `${kind} stack name`);
1203
+ const capturedRegion = capture.StackId?.match(/^arn:[^:]+:cloudformation:([^:]+):/)?.[1];
1204
+ assertEqual(capturedRegion, input.config.region, `${kind} stack Region`);
1205
+ }
1206
+ const coreOutputs = coreBootstrapOutputs(core);
1207
+ const coreParameters = parameterValues(core);
1208
+ assertEqual(
1209
+ coreParameters.ArtifactBucketName,
1210
+ input.config.core?.artifactBucketName,
1211
+ "Artifact bucket"
1212
+ );
1213
+ assertEqual(
1214
+ coreParameters.ApplicationStackName,
1215
+ input.config.core?.applicationStackName,
1216
+ "Application stack"
1217
+ );
1218
+ assertEqual(
1219
+ coreParameters.DashboardFunctionName,
1220
+ input.config.core?.dashboardFunctionName,
1221
+ "Dashboard function"
1222
+ );
1223
+ assertEqual(
1224
+ coreOutputs.ArtifactBucketName,
1225
+ input.config.core?.artifactBucketName,
1226
+ "Core output artifact bucket"
1227
+ );
1228
+ assertEqual(
1229
+ coreOutputs.ApplicationStackName,
1230
+ input.config.core?.applicationStackName,
1231
+ "Core output application stack"
1232
+ );
1233
+ const oidcParameters = parameterValues(oidc);
1234
+ const github = input.config.githubOidc;
1235
+ const executionRole = required(
1236
+ coreOutputs.CloudFormationExecutionRoleArn,
1237
+ "core CloudFormationExecutionRoleArn output"
1238
+ );
1239
+ for (const [key, value2] of Object.entries({
1240
+ GitHubOidcProviderArn: github?.providerArn,
1241
+ GitHubRepository: github?.repository,
1242
+ GitHubOwnerId: github?.ownerId,
1243
+ GitHubRepositoryId: github?.repositoryId,
1244
+ GitHubEnvironment: github?.environment,
1245
+ ApplicationStackName: coreOutputs.ApplicationStackName,
1246
+ ArtifactBucketName: coreOutputs.ArtifactBucketName,
1247
+ CloudFormationExecutionRoleArn: executionRole
1248
+ }))
1249
+ assertEqual(oidcParameters[key], value2, `OIDC ${key}`);
1250
+ if (!/^\d+$/.test(github?.ownerId ?? "") || !/^\d+$/.test(github?.repositoryId ?? ""))
1251
+ throw new Error("GitHub immutable owner and repository IDs must be numeric");
1252
+ const [owner, repository, ...extra] = (github?.repository ?? "").split("/");
1253
+ if (!owner || !repository || extra.length)
1254
+ throw new Error("GitHub repository must be owner/repository");
1255
+ const deployRole = outputValues(oidc).GitHubDeployRoleArn;
1256
+ if (!deployRole) throw new Error("GitHub OIDC stack JSON is missing outputs: GitHubDeployRoleArn");
1257
+ const providerArn = github?.providerArn ?? "";
1258
+ const account = providerArn.match(/^arn:[^:]+:iam::(\d+):oidc-provider\//)?.[1];
1259
+ const deployAccount = deployRole.match(/^arn:[^:]+:iam::(\d+):role\//)?.[1];
1260
+ const executionAccount = executionRole.match(/^arn:[^:]+:iam::(\d+):role\//)?.[1];
1261
+ if (!account || account !== deployAccount || account !== executionAccount)
1262
+ throw new Error("Provider and reviewed role ARNs must belong to the same AWS account");
1263
+ return {
1264
+ verified: true,
1265
+ immutableSubject: `repo:${owner}@${github?.ownerId}/${repository}@${github?.repositoryId}:environment:${github?.environment}`,
1266
+ environmentVariables: {
1267
+ AWS_DEPLOY_ROLE_ARN: deployRole,
1268
+ AWS_CLOUDFORMATION_EXECUTION_ROLE_ARN: executionRole
1269
+ },
1270
+ reviewedArns: {
1271
+ githubDeployRoleArn: deployRole,
1272
+ cloudFormationExecutionRoleArn: executionRole
1273
+ },
1274
+ githubEnvironmentInstructions: [
1275
+ [
1276
+ "gh",
1277
+ "variable",
1278
+ "set",
1279
+ "AWS_DEPLOY_ROLE_ARN",
1280
+ "--repo",
1281
+ `${owner}/${repository}`,
1282
+ "--env",
1283
+ github?.environment ?? "",
1284
+ "--body",
1285
+ deployRole
1286
+ ],
1287
+ [
1288
+ "gh",
1289
+ "variable",
1290
+ "set",
1291
+ "AWS_CLOUDFORMATION_EXECUTION_ROLE_ARN",
1292
+ "--repo",
1293
+ `${owner}/${repository}`,
1294
+ "--env",
1295
+ github?.environment ?? "",
1296
+ "--body",
1297
+ executionRole
1298
+ ]
1299
+ ]
1300
+ };
1301
+ }
1302
+
1303
+ // packages/aws/src/guided.ts
1304
+ function value(input, label) {
1305
+ if (typeof input !== "string" || !input.trim()) throw new Error(`${label} is required`);
1306
+ return input.trim();
1307
+ }
1308
+ function repositoryParts(repository) {
1309
+ const [owner, name, extra] = repository.split("/");
1310
+ if (!owner || !name || extra) throw new Error("repository must be owner/repository");
1311
+ return [owner, name];
1312
+ }
1313
+ function accountFromArn(arn) {
1314
+ return arn.match(/^arn:[^:]+:iam::(\d+):oidc-provider\//)?.[1];
1315
+ }
1316
+ async function optional(runner, command, args2) {
1317
+ if (!runner) return void 0;
1318
+ try {
1319
+ return await runner.execute(command, args2);
1320
+ } catch {
1321
+ return void 0;
1322
+ }
1323
+ }
1324
+ async function discovered(input) {
1325
+ const identity = await optional(input.runner, "aws", [
1326
+ "sts",
1327
+ "get-caller-identity",
1328
+ "--output",
1329
+ "json"
1330
+ ]);
1331
+ const aws = parseOrUnavailable(identity) ?? {};
1332
+ const accountId = typeof aws.Account === "string" ? aws.Account : void 0;
1333
+ const region2 = await optional(input.runner, "aws", ["configure", "get", "region"]);
1334
+ const repo = await optional(input.runner, "gh", ["api", `repos/${input.repository}`]);
1335
+ const github = parseOrUnavailable(repo) ?? {};
1336
+ return {
1337
+ accountId,
1338
+ region: region2?.trim() || void 0,
1339
+ ownerId: typeof github.owner?.id === "number" || typeof github.owner?.id === "string" ? String(github.owner.id) : void 0,
1340
+ repositoryId: typeof github.id === "number" || typeof github.id === "string" ? String(github.id) : void 0
1341
+ };
1342
+ }
1343
+ async function scaffoldBootstrapManifest(input) {
1344
+ const slug = value(input.slug, "slug");
1345
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(slug))
1346
+ throw new Error("slug must use lowercase letters, digits, and hyphens");
1347
+ const repository = value(input.repository, "repository");
1348
+ repositoryParts(repository);
1349
+ const environment = value(input.environment, "environment");
1350
+ const providerArn = value(input.providerArn, "github OIDC provider ARN");
1351
+ if (!accountFromArn(providerArn)) throw new Error("github OIDC provider ARN is invalid");
1352
+ const found = await discovered(input);
1353
+ const accountId = input.accountId ?? found.accountId;
1354
+ const region2 = input.region ?? found.region;
1355
+ const ownerId = input.ownerId ?? found.ownerId;
1356
+ const repositoryId = input.repositoryId ?? found.repositoryId;
1357
+ if (!accountId) throw new Error("--account-id is required when AWS identity is unavailable");
1358
+ if (!region2) throw new Error("--region is required when AWS Region is unavailable");
1359
+ if (!ownerId) throw new Error("--github-owner-id is required when GitHub is unavailable");
1360
+ if (!repositoryId)
1361
+ throw new Error("--github-repository-id is required when GitHub is unavailable");
1362
+ return {
1363
+ region: region2,
1364
+ core: {
1365
+ stackName: `${slug}-bootstrap`,
1366
+ artifactBucketName: `${slug}-lambda-artifacts-${accountId}`,
1367
+ applicationStackName: slug,
1368
+ dashboardFunctionName: slug
1369
+ },
1370
+ githubOidc: {
1371
+ stackName: `${slug}-github-bootstrap`,
1372
+ providerArn,
1373
+ repository,
1374
+ ownerId,
1375
+ repositoryId,
1376
+ environment
1377
+ }
1378
+ };
1379
+ }
1380
+ function incomplete(config) {
1381
+ const fields = [
1382
+ ["region", config.region],
1383
+ ["core.stackName", config.core?.stackName],
1384
+ ["core.artifactBucketName", config.core?.artifactBucketName],
1385
+ ["core.applicationStackName", config.core?.applicationStackName],
1386
+ ["core.dashboardFunctionName", config.core?.dashboardFunctionName],
1387
+ ["githubOidc.stackName", config.githubOidc?.stackName],
1388
+ ["githubOidc.providerArn", config.githubOidc?.providerArn],
1389
+ ["githubOidc.repository", config.githubOidc?.repository],
1390
+ ["githubOidc.ownerId", config.githubOidc?.ownerId],
1391
+ ["githubOidc.repositoryId", config.githubOidc?.repositoryId],
1392
+ ["githubOidc.environment", config.githubOidc?.environment]
1393
+ ];
1394
+ return fields.filter(([, v]) => typeof v !== "string" || !v).map(([name]) => name);
1395
+ }
1396
+ function parseOrUnavailable(raw) {
1397
+ if (!raw) return void 0;
1398
+ try {
1399
+ return JSON.parse(raw);
1400
+ } catch {
1401
+ return void 0;
1402
+ }
1403
+ }
1404
+ function absent(raw) {
1405
+ return /not found|nosuchentity|404/i.test(raw ?? "");
1406
+ }
1407
+ async function bootstrapPreflight(input) {
1408
+ const checks = [];
1409
+ const missing = incomplete(input.config);
1410
+ checks.push(
1411
+ missing.length ? { name: "manifest", status: "missing", detail: `Missing: ${missing.join(", ")}` } : { name: "manifest", status: "ready", detail: "Manifest has all bootstrap fields." }
1412
+ );
1413
+ if (missing.length) return { ready: false, checks };
1414
+ const runner = input.runner;
1415
+ const identityRaw = await optional(runner, "aws", [
1416
+ "sts",
1417
+ "get-caller-identity",
1418
+ "--output",
1419
+ "json"
1420
+ ]);
1421
+ const identity = parseOrUnavailable(identityRaw);
1422
+ const providerAccount = accountFromArn(input.config.githubOidc?.providerArn ?? "");
1423
+ if (!identity || typeof identity.Account !== "string")
1424
+ checks.push({
1425
+ name: "aws-identity",
1426
+ status: "unverified",
1427
+ detail: "AWS identity could not be read."
1428
+ });
1429
+ else
1430
+ checks.push(
1431
+ identity.Account === providerAccount ? {
1432
+ name: "aws-identity",
1433
+ status: "ready",
1434
+ detail: `AWS account ${identity.Account} matches the provider ARN.`
1435
+ } : {
1436
+ name: "aws-identity",
1437
+ status: "mismatch",
1438
+ detail: `AWS account ${identity.Account} does not match provider account ${providerAccount}.`
1439
+ }
1440
+ );
1441
+ const regionRaw = await optional(runner, "aws", ["configure", "get", "region"]);
1442
+ if (!regionRaw?.trim())
1443
+ checks.push({
1444
+ name: "aws-region",
1445
+ status: "unverified",
1446
+ detail: "AWS Region could not be read."
1447
+ });
1448
+ else
1449
+ checks.push(
1450
+ regionRaw.trim() === input.config.region ? { name: "aws-region", status: "ready", detail: `AWS Region is ${input.config.region}.` } : {
1451
+ name: "aws-region",
1452
+ status: "mismatch",
1453
+ detail: `AWS Region ${regionRaw.trim()} does not match manifest ${input.config.region}.`
1454
+ }
1455
+ );
1456
+ const providerRaw = await optional(runner, "aws", [
1457
+ "iam",
1458
+ "get-open-id-connect-provider",
1459
+ "--open-id-connect-provider-arn",
1460
+ input.config.githubOidc?.providerArn ?? ""
1461
+ ]);
1462
+ checks.push(
1463
+ !providerRaw ? {
1464
+ name: "oidc-provider",
1465
+ status: "unverified",
1466
+ detail: "OIDC provider could not be queried."
1467
+ } : absent(providerRaw) ? { name: "oidc-provider", status: "missing", detail: "OIDC provider does not exist." } : { name: "oidc-provider", status: "ready", detail: "OIDC provider exists." }
1468
+ );
1469
+ const repository = input.config.githubOidc?.repository ?? "";
1470
+ const repoRaw = await optional(runner, "gh", ["api", `repos/${repository}`]);
1471
+ const repo = parseOrUnavailable(repoRaw);
1472
+ if (!repoRaw)
1473
+ checks.push({
1474
+ name: "github-repository",
1475
+ status: "unverified",
1476
+ detail: "GitHub repository could not be queried."
1477
+ });
1478
+ else if (absent(repoRaw))
1479
+ checks.push({
1480
+ name: "github-repository",
1481
+ status: "missing",
1482
+ detail: "GitHub repository does not exist or is unavailable."
1483
+ });
1484
+ else if (String(repo?.id) !== input.config.githubOidc?.repositoryId || String(repo?.owner?.id) !== input.config.githubOidc?.ownerId)
1485
+ checks.push({
1486
+ name: "github-repository",
1487
+ status: "mismatch",
1488
+ detail: "GitHub numeric repository identity does not match the manifest."
1489
+ });
1490
+ else
1491
+ checks.push({
1492
+ name: "github-repository",
1493
+ status: "ready",
1494
+ detail: "GitHub repository identity matches the manifest."
1495
+ });
1496
+ const environmentRaw = await optional(runner, "gh", [
1497
+ "api",
1498
+ `repos/${repository}/environments/${input.config.githubOidc?.environment}`
1499
+ ]);
1500
+ checks.push(
1501
+ !environmentRaw ? {
1502
+ name: "github-environment",
1503
+ status: "unverified",
1504
+ detail: "GitHub Environment could not be queried."
1505
+ } : absent(environmentRaw) ? {
1506
+ name: "github-environment",
1507
+ status: "missing",
1508
+ detail: "GitHub Environment does not exist."
1509
+ } : {
1510
+ name: "github-environment",
1511
+ status: "ready",
1512
+ detail: "GitHub Environment exists; its policy is administrator-owned context."
1513
+ }
1514
+ );
1515
+ const subject = await (input.provider ?? githubOidcProvider).prerequisite(input.config, runner);
1516
+ checks.push({
1517
+ name: "immutable-subject",
1518
+ status: subject.status === "immutable-subject-required" ? "mismatch" : subject.status,
1519
+ detail: subject.detail
1520
+ });
1521
+ return {
1522
+ ready: !checks.some(({ status }) => status === "missing" || status === "mismatch"),
1523
+ checks
1524
+ };
1525
+ }
1526
+ function quote(args2) {
1527
+ return args2.map((arg) => `'${arg.replaceAll("'", `'\\"'\\"'`)}'`).join(" ");
1528
+ }
1529
+ async function bootstrapGuide(input) {
1530
+ const handoff = await bootstrapHandoff(input);
1531
+ const lines = [
1532
+ `Phase: ${handoff.phase}`,
1533
+ `Required captures: ${handoff.requiredCapturedFiles.join(", ") || "none"}`
1534
+ ];
1535
+ if (handoff.expectedOutputs.length)
1536
+ lines.push(`Expected outputs: ${handoff.expectedOutputs.join(", ")}`);
1537
+ if (handoff.prerequisite)
1538
+ lines.push(`Prerequisite: ${handoff.prerequisite.status} \u2014 ${handoff.prerequisite.detail}`);
1539
+ lines.push("", "Commands (copy and run each AWS command yourself):");
1540
+ for (const command of handoff.commands) {
1541
+ if (command.name === "review-change-set")
1542
+ lines.push("PAUSE: review the change set before approval.");
1543
+ if (command.name === "execute-reviewed-change-set")
1544
+ lines.push("PAUSE: execute only after explicit approval.");
1545
+ lines.push(`${command.captureFile ? `${command.captureFile}: ` : ""}${quote(command.args)}`);
1546
+ }
1547
+ if (handoff.reviewCheckpoints.length)
1548
+ lines.push("", "Review pauses:", ...handoff.reviewCheckpoints.map((item) => `- ${item}`));
1549
+ if (input.coreStack !== void 0 && input.githubOidcStack !== void 0) {
1550
+ const verified = await verifyBootstrap({
1551
+ config: input.config,
1552
+ coreStack: input.coreStack,
1553
+ githubOidcStack: input.githubOidcStack
1554
+ });
1555
+ lines.push(
1556
+ "",
1557
+ "Optional GitHub administrator action (after the successful verification above):"
1558
+ );
1559
+ for (const command of verified.githubEnvironmentInstructions) lines.push(quote(command));
1560
+ }
1561
+ return `${lines.join("\n")}
1562
+ `;
915
1563
  }
916
1564
 
917
1565
  // packages/aws/src/index.ts
@@ -1065,9 +1713,9 @@ var actualDependencies = {
1065
1713
  nodeVersion: process.versions.node,
1066
1714
  packageVersion: ""
1067
1715
  };
1068
- function readParameterValues(value) {
1069
- if (!Array.isArray(value)) throw new Error("must contain a JSON parameter array");
1070
- const parameters = value.map((entry) => {
1716
+ function readParameterValues(value2) {
1717
+ if (!Array.isArray(value2)) throw new Error("must contain a JSON parameter array");
1718
+ const parameters = value2.map((entry) => {
1071
1719
  if (!entry || typeof entry !== "object" || typeof entry.ParameterKey !== "string" || typeof entry.ParameterValue !== "string")
1072
1720
  throw new Error("entries must contain string ParameterKey and ParameterValue fields");
1073
1721
  return entry;
@@ -1200,23 +1848,23 @@ async function installedPackageVersion() {
1200
1848
  return typeof packageManifest.version === "string" ? packageManifest.version : "";
1201
1849
  }
1202
1850
  var requiredOption = (name, fallback) => {
1203
- const value = option(name, fallback);
1204
- if (!value) throw new Error(`${name} is required`);
1205
- return value;
1851
+ const value2 = option(name, fallback);
1852
+ if (!value2) throw new Error(`${name} is required`);
1853
+ return value2;
1206
1854
  };
1207
- function parameter(key, value) {
1208
- return { ParameterKey: key, ParameterValue: value };
1855
+ function parameter(key, value2) {
1856
+ return { ParameterKey: key, ParameterValue: value2 };
1209
1857
  }
1210
1858
  async function existingParameters(path) {
1211
1859
  try {
1212
1860
  const parsed = JSON.parse(await readFile5(path, "utf8"));
1213
1861
  if (!Array.isArray(parsed)) throw new Error(`${path} must contain a JSON parameter array`);
1214
- const values = parsed.map((value) => {
1215
- if (!value || typeof value !== "object" || typeof value.ParameterKey !== "string" || typeof value.ParameterValue !== "string")
1862
+ const values = parsed.map((value2) => {
1863
+ if (!value2 || typeof value2 !== "object" || typeof value2.ParameterKey !== "string" || typeof value2.ParameterValue !== "string")
1216
1864
  throw new Error(
1217
1865
  `${path} entries must contain string ParameterKey and ParameterValue fields`
1218
1866
  );
1219
- return value;
1867
+ return value2;
1220
1868
  });
1221
1869
  const keys = values.map(({ ParameterKey }) => ParameterKey);
1222
1870
  if (new Set(keys).size !== keys.length) throw new Error(`${path} contains duplicate parameters`);
@@ -1318,9 +1966,9 @@ try {
1318
1966
  console.log(JSON.stringify({ published: true, version, assetPath }));
1319
1967
  } else if (args[0] === "doctor") {
1320
1968
  const parametersPath = option("--parameters", "aws-dashboard-parameters.json") ?? "aws-dashboard-parameters.json";
1321
- const region = option("--region", process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1") ?? "us-east-1";
1969
+ const region2 = option("--region", process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1") ?? "us-east-1";
1322
1970
  const checks = await runDoctor(
1323
- { parametersPath, region },
1971
+ { parametersPath, region: region2 },
1324
1972
  {
1325
1973
  async execute(command, commandArgs) {
1326
1974
  const { execFile: execFile3 } = await import("node:child_process");
@@ -1371,109 +2019,225 @@ try {
1371
2019
  (key) => !packageManaged.has(key) && (includeDefaults || !hasDefault(key) || Object.hasOwn(existingValues, key) || key === "Name" && Boolean(functionName))
1372
2020
  ).map((key) => {
1373
2021
  const definition = definitions[key];
1374
- const value = key === "LambdaArtifactBucket" ? artifactBucket : key === "Name" && functionName ? functionName : existingValues[key] ?? definition?.Default;
1375
- if (value === void 0) throw new Error(`No value for CloudFormation parameter ${key}`);
1376
- return parameter(key, String(value));
2022
+ const value2 = key === "LambdaArtifactBucket" ? artifactBucket : key === "Name" && functionName ? functionName : existingValues[key] ?? definition?.Default;
2023
+ if (value2 === void 0) throw new Error(`No value for CloudFormation parameter ${key}`);
2024
+ return parameter(key, String(value2));
1377
2025
  });
1378
2026
  await writeFile3(output, `${JSON.stringify(parameters, null, 2)}
1379
2027
  `);
1380
2028
  console.log(JSON.stringify({ output }));
1381
2029
  } else if (args[0] === "bootstrap") {
1382
2030
  const action = args[1];
1383
- const kind = bootstrapKind();
1384
- const config = await bootstrapConfig();
1385
- if (action === "template") {
1386
- console.log(
1387
- JSON.stringify({
1388
- kind,
1389
- template: await bootstrapTemplatePath(kind),
1390
- contractVersion: bootstrapContractVersion(await bootstrapTemplate(kind))
1391
- })
1392
- );
1393
- } else if (action === "parameters") {
1394
- const output = option("--output", `aws-dashboard-bootstrap-${kind}.json`) ?? `aws-dashboard-bootstrap-${kind}.json`;
1395
- let coreOutputs = {};
1396
- const coreStackPath = option("--core-stack-json");
1397
- if (kind === "github-oidc" && coreStackPath) {
1398
- coreOutputs = coreBootstrapOutputs(
1399
- deployedBootstrapStack(
1400
- JSON.parse(await readFile5(coreStackPath, "utf8")),
1401
- bootstrapContractVersion(await bootstrapTemplate("core"))
2031
+ if (action === "init") {
2032
+ const output = requiredOption("--output");
2033
+ try {
2034
+ await readFile5(output, "utf8");
2035
+ throw new Error(`Refusing to overwrite existing manifest: ${output}`);
2036
+ } catch (error) {
2037
+ if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT"))
2038
+ throw error;
2039
+ }
2040
+ const runner = {
2041
+ async execute(command, commandArgs) {
2042
+ const { execFile: execFile3 } = await import("node:child_process");
2043
+ const { promisify: promisify3 } = await import("node:util");
2044
+ return (await promisify3(execFile3)(command, commandArgs)).stdout.trim();
2045
+ }
2046
+ };
2047
+ const manifest = await scaffoldBootstrapManifest({
2048
+ slug: requiredOption("--slug"),
2049
+ repository: requiredOption("--repository"),
2050
+ environment: requiredOption("--environment"),
2051
+ providerArn: requiredOption("--github-oidc-provider-arn"),
2052
+ region: option("--region"),
2053
+ accountId: option("--account-id"),
2054
+ ownerId: option("--github-owner-id"),
2055
+ repositoryId: option("--github-repository-id"),
2056
+ runner
2057
+ });
2058
+ await writeFile3(output, `${JSON.stringify(manifest, null, 2)}
2059
+ `, { flag: "wx" });
2060
+ console.log(JSON.stringify({ output, manifest }));
2061
+ } else {
2062
+ const config = await bootstrapConfig();
2063
+ if (action === "preflight") {
2064
+ requiredOption("--config");
2065
+ const runner = {
2066
+ async execute(command, commandArgs) {
2067
+ const { execFile: execFile3 } = await import("node:child_process");
2068
+ const { promisify: promisify3 } = await import("node:util");
2069
+ try {
2070
+ return (await promisify3(execFile3)(command, commandArgs)).stdout.trim();
2071
+ } catch (error) {
2072
+ if (error && typeof error === "object" && "stderr" in error) {
2073
+ const stderr = error.stderr;
2074
+ if (typeof stderr === "string" && /not found|404|nosuchentity/i.test(stderr))
2075
+ return stderr;
2076
+ }
2077
+ throw error;
2078
+ }
2079
+ }
2080
+ };
2081
+ const result = await bootstrapPreflight({ config, runner });
2082
+ if (option("--format") === "text")
2083
+ for (const check of result.checks)
2084
+ console.log(`${check.status.toUpperCase()} ${check.name}: ${check.detail}`);
2085
+ else console.log(JSON.stringify(result));
2086
+ if (!result.ready) process.exitCode = 1;
2087
+ } else if (action === "guide") {
2088
+ const configPath = requiredOption("--config");
2089
+ const coreStackPath = option("--core-stack-json");
2090
+ const githubStackPath = option("--github-oidc-stack-json");
2091
+ const runner = {
2092
+ async execute(command, commandArgs) {
2093
+ const { execFile: execFile3 } = await import("node:child_process");
2094
+ const { promisify: promisify3 } = await import("node:util");
2095
+ return (await promisify3(execFile3)(command, commandArgs)).stdout.trim();
2096
+ }
2097
+ };
2098
+ process.stdout.write(
2099
+ await bootstrapGuide({
2100
+ config,
2101
+ configPath,
2102
+ coreStack: coreStackPath ? JSON.parse(await readFile5(coreStackPath, "utf8")) : void 0,
2103
+ coreStackPath,
2104
+ githubOidcStack: githubStackPath ? JSON.parse(await readFile5(githubStackPath, "utf8")) : void 0,
2105
+ runner
2106
+ })
2107
+ );
2108
+ } else if (action === "handoff") {
2109
+ const configPath = requiredOption("--config");
2110
+ const coreStackPath = option("--core-stack-json");
2111
+ const githubStackPath = option("--github-oidc-stack-json");
2112
+ const runner = {
2113
+ async execute(command, commandArgs) {
2114
+ const { execFile: execFile3 } = await import("node:child_process");
2115
+ const { promisify: promisify3 } = await import("node:util");
2116
+ return (await promisify3(execFile3)(command, commandArgs)).stdout.trim();
2117
+ }
2118
+ };
2119
+ console.log(
2120
+ JSON.stringify(
2121
+ await bootstrapHandoff({
2122
+ config,
2123
+ configPath,
2124
+ coreStack: coreStackPath ? JSON.parse(await readFile5(coreStackPath, "utf8")) : void 0,
2125
+ coreStackPath,
2126
+ githubOidcStack: githubStackPath ? JSON.parse(await readFile5(githubStackPath, "utf8")) : void 0,
2127
+ runner
2128
+ })
1402
2129
  )
1403
2130
  );
1404
- }
1405
- const supplied = requiredBootstrapParameters(kind, bootstrapValues(config, coreOutputs));
1406
- let parameters = supplied;
1407
- const deployedStackPath = option("--deployed-stack-json");
1408
- if (deployedStackPath) {
1409
- const stack = deployedBootstrapStack(
1410
- JSON.parse(await readFile5(deployedStackPath, "utf8")),
1411
- bootstrapContractVersion(await bootstrapTemplate(kind))
2131
+ } else if (action === "verify") {
2132
+ requiredOption("--config");
2133
+ const coreStackPath = requiredOption("--core-stack-json");
2134
+ const githubStackPath = requiredOption("--github-oidc-stack-json");
2135
+ console.log(
2136
+ JSON.stringify(
2137
+ await verifyBootstrap({
2138
+ config,
2139
+ coreStack: JSON.parse(await readFile5(coreStackPath, "utf8")),
2140
+ githubOidcStack: JSON.parse(await readFile5(githubStackPath, "utf8"))
2141
+ })
2142
+ )
1412
2143
  );
1413
- parameters = mergeBootstrapParameters(supplied, stack.Parameters ?? []);
1414
- }
1415
- await writeFile3(output, `${JSON.stringify(parameters, null, 2)}
2144
+ } else if (action === "template") {
2145
+ const kind = bootstrapKind();
2146
+ console.log(
2147
+ JSON.stringify({
2148
+ kind,
2149
+ template: await bootstrapTemplatePath(kind),
2150
+ contractVersion: bootstrapContractVersion(await bootstrapTemplate(kind))
2151
+ })
2152
+ );
2153
+ } else if (action === "parameters") {
2154
+ const kind = bootstrapKind();
2155
+ const output = option("--output", `aws-dashboard-bootstrap-${kind}.json`) ?? `aws-dashboard-bootstrap-${kind}.json`;
2156
+ let coreOutputs = {};
2157
+ const coreStackPath = option("--core-stack-json");
2158
+ if (kind === "github-oidc" && coreStackPath) {
2159
+ coreOutputs = coreBootstrapOutputs(
2160
+ deployedBootstrapStack(
2161
+ JSON.parse(await readFile5(coreStackPath, "utf8")),
2162
+ bootstrapContractVersion(await bootstrapTemplate("core"))
2163
+ )
2164
+ );
2165
+ }
2166
+ const supplied = requiredBootstrapParameters(kind, bootstrapValues(config, coreOutputs));
2167
+ let parameters = supplied;
2168
+ const deployedStackPath = option("--deployed-stack-json");
2169
+ if (deployedStackPath) {
2170
+ const stack = deployedBootstrapStack(
2171
+ JSON.parse(await readFile5(deployedStackPath, "utf8")),
2172
+ bootstrapContractVersion(await bootstrapTemplate(kind))
2173
+ );
2174
+ parameters = mergeBootstrapParameters(supplied, stack.Parameters ?? []);
2175
+ }
2176
+ await writeFile3(output, `${JSON.stringify(parameters, null, 2)}
1416
2177
  `);
1417
- console.log(
1418
- JSON.stringify({ output, kind, preservedDeployedValues: Boolean(deployedStackPath) })
1419
- );
1420
- } else if (action === "status") {
1421
- const stackName = requiredOption("--stack-name", bootstrapStackName(kind, config));
1422
- const region = option("--region", config.region);
1423
- const awsCommand = [
1424
- "aws",
1425
- "cloudformation",
1426
- "describe-stacks",
1427
- "--stack-name",
1428
- stackName,
1429
- ...region ? ["--region", region] : [],
1430
- "--no-cli-pager"
1431
- ];
1432
- console.log(
1433
- JSON.stringify({
1434
- kind,
1435
- contractVersion: bootstrapContractVersion(await bootstrapTemplate(kind)),
1436
- awsCommand,
1437
- shellCommand: args.includes("--format-shell") ? shellCommand(awsCommand) : void 0
1438
- })
1439
- );
1440
- } else if (action === "change-set") {
1441
- const stackName = requiredOption("--stack-name", bootstrapStackName(kind, config));
1442
- const changeSetName = requiredOption("--change-set-name");
1443
- const parametersPath = requiredOption("--parameters");
1444
- const region = option("--region", config.region);
1445
- await existingParameters(parametersPath);
1446
- const awsCommand = [
1447
- "aws",
1448
- "cloudformation",
1449
- "create-change-set",
1450
- "--stack-name",
1451
- stackName,
1452
- "--change-set-name",
1453
- changeSetName,
1454
- "--change-set-type",
1455
- option("--change-set-type", "UPDATE") ?? "UPDATE",
1456
- "--template-body",
1457
- `file://${await bootstrapTemplatePath(kind)}`,
1458
- "--parameters",
1459
- `file://${parametersPath}`,
1460
- "--capabilities",
1461
- "CAPABILITY_NAMED_IAM",
1462
- ...region ? ["--region", region] : [],
1463
- "--no-cli-pager"
1464
- ];
1465
- console.log(
1466
- JSON.stringify({
1467
- kind,
1468
- reviewRequired: true,
1469
- awsCommand,
1470
- shellCommand: args.includes("--format-shell") ? shellCommand(awsCommand) : void 0
1471
- })
1472
- );
1473
- } else {
1474
- throw new Error(
1475
- "Usage: ze-great-dashboard-aws bootstrap template|parameters|status|change-set --kind core|github-oidc [options]"
1476
- );
2178
+ console.log(
2179
+ JSON.stringify({ output, kind, preservedDeployedValues: Boolean(deployedStackPath) })
2180
+ );
2181
+ } else if (action === "status") {
2182
+ const kind = bootstrapKind();
2183
+ const stackName2 = requiredOption("--stack-name", bootstrapStackName(kind, config));
2184
+ const region2 = option("--region", config.region);
2185
+ const awsCommand = [
2186
+ "aws",
2187
+ "cloudformation",
2188
+ "describe-stacks",
2189
+ "--stack-name",
2190
+ stackName2,
2191
+ ...region2 ? ["--region", region2] : [],
2192
+ "--no-cli-pager"
2193
+ ];
2194
+ console.log(
2195
+ JSON.stringify({
2196
+ kind,
2197
+ contractVersion: bootstrapContractVersion(await bootstrapTemplate(kind)),
2198
+ awsCommand,
2199
+ shellCommand: args.includes("--format-shell") ? shellCommand(awsCommand) : void 0
2200
+ })
2201
+ );
2202
+ } else if (action === "change-set") {
2203
+ const kind = bootstrapKind();
2204
+ const stackName2 = requiredOption("--stack-name", bootstrapStackName(kind, config));
2205
+ const changeSetName = requiredOption("--change-set-name");
2206
+ const parametersPath = requiredOption("--parameters");
2207
+ const region2 = option("--region", config.region);
2208
+ await existingParameters(parametersPath);
2209
+ const awsCommand = [
2210
+ "aws",
2211
+ "cloudformation",
2212
+ "create-change-set",
2213
+ "--stack-name",
2214
+ stackName2,
2215
+ "--change-set-name",
2216
+ changeSetName,
2217
+ "--change-set-type",
2218
+ option("--change-set-type", "UPDATE") ?? "UPDATE",
2219
+ "--template-body",
2220
+ `file://${await bootstrapTemplatePath(kind)}`,
2221
+ "--parameters",
2222
+ `file://${parametersPath}`,
2223
+ "--capabilities",
2224
+ "CAPABILITY_NAMED_IAM",
2225
+ ...region2 ? ["--region", region2] : [],
2226
+ "--no-cli-pager"
2227
+ ];
2228
+ console.log(
2229
+ JSON.stringify({
2230
+ kind,
2231
+ reviewRequired: true,
2232
+ awsCommand,
2233
+ shellCommand: args.includes("--format-shell") ? shellCommand(awsCommand) : void 0
2234
+ })
2235
+ );
2236
+ } else {
2237
+ throw new Error(
2238
+ "Usage: ze-great-dashboard-aws bootstrap init|preflight|guide|handoff|verify --config manifest.json [options], or bootstrap template|parameters|status|change-set --kind core|github-oidc [options]"
2239
+ );
2240
+ }
1477
2241
  }
1478
2242
  } else if (args[0] !== "package")
1479
2243
  throw new Error(