@biffo/cli 0.80.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +318 -42
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command24 } from "commander";
4
+ import { Command as Command25 } from "commander";
5
5
 
6
6
  // src/commands/core.ts
7
7
  import { Command as Command4 } from "commander";
@@ -819,7 +819,7 @@ var GitHubAdapter = class {
819
819
  } catch (err) {
820
820
  if (err.status !== 404) throw err;
821
821
  }
822
- await new Promise((resolve17) => setTimeout(resolve17, intervalMs));
822
+ await new Promise((resolve18) => setTimeout(resolve18, intervalMs));
823
823
  }
824
824
  throw new Error(
825
825
  `Branch "${branch}" not found in ${org}/${repo} after ${timeoutMs / 1e3}s \u2014 GitHub template generation may have stalled. Check the repository and re-run biffo init.`
@@ -834,7 +834,7 @@ var GitHubAdapter = class {
834
834
  } catch (err) {
835
835
  if (err.status !== 404) throw err;
836
836
  }
837
- await new Promise((resolve17) => setTimeout(resolve17, intervalMs));
837
+ await new Promise((resolve18) => setTimeout(resolve18, intervalMs));
838
838
  }
839
839
  throw new Error(
840
840
  `Ref "${ref}" not found in ${org}/${repo} after ${timeoutMs / 1e3}s \u2014 GitHub template generation may have stalled. Check the repository and re-run biffo init.`
@@ -1039,7 +1039,7 @@ var GitHubAdapter = class {
1039
1039
  }
1040
1040
  if (status !== 404 || Date.now() >= deadline) throw err;
1041
1041
  log.info("Branch protection endpoint not yet ready, retrying...");
1042
- await new Promise((resolve17) => setTimeout(resolve17, protectionIntervalMs));
1042
+ await new Promise((resolve18) => setTimeout(resolve18, protectionIntervalMs));
1043
1043
  }
1044
1044
  }
1045
1045
  }
@@ -1082,6 +1082,28 @@ var GitHubAdapter = class {
1082
1082
  }
1083
1083
  }
1084
1084
  }
1085
+ /**
1086
+ * Read an environment-scoped Actions variable's value, or `null` if it isn't
1087
+ * set (a 404). The counterpart to `setEnvVariable`, used by
1088
+ * `biffo sibling check-identity` to read each sibling's baked-in
1089
+ * `CORE_COGNITO_USER_POOL_ID` and compare it against the core's live pool
1090
+ * (#400/#496). A 404 means the variable was never set on that environment,
1091
+ * which is a real, reportable state — not an error — so it maps to `null`.
1092
+ */
1093
+ async getEnvVariable(org, repo, env, name) {
1094
+ try {
1095
+ const { data } = await this.octokit.request(
1096
+ "GET /repos/{owner}/{repo}/environments/{environment_name}/variables/{variable_name}",
1097
+ { owner: org, repo, environment_name: env, variable_name: name }
1098
+ );
1099
+ return data.value;
1100
+ } catch (err) {
1101
+ if (err.status === 404) {
1102
+ return null;
1103
+ }
1104
+ throw err;
1105
+ }
1106
+ }
1085
1107
  async setRepoVariable(org, repo, name, value) {
1086
1108
  log.info(`Setting variable: ${name}`);
1087
1109
  try {
@@ -1243,7 +1265,7 @@ var GitHubAdapter = class {
1243
1265
  } catch (err) {
1244
1266
  if (err.status !== 404 || Date.now() >= deadline) throw err;
1245
1267
  log.info(`Workflow ${workflowId} not yet indexed by GitHub Actions, retrying...`);
1246
- await new Promise((resolve17) => setTimeout(resolve17, intervalMs));
1268
+ await new Promise((resolve18) => setTimeout(resolve18, intervalMs));
1247
1269
  }
1248
1270
  }
1249
1271
  }
@@ -1262,7 +1284,7 @@ var GitHubAdapter = class {
1262
1284
  } catch (err) {
1263
1285
  if (err.status !== 404 || Date.now() >= deadline) throw err;
1264
1286
  log.info(`Workflow ${workflowId} not yet indexed by GitHub Actions, retrying...`);
1265
- await new Promise((resolve17) => setTimeout(resolve17, intervalMs));
1287
+ await new Promise((resolve18) => setTimeout(resolve18, intervalMs));
1266
1288
  }
1267
1289
  }
1268
1290
  }
@@ -1286,7 +1308,7 @@ var GitHubAdapter = class {
1286
1308
  } else {
1287
1309
  log.info(" Waiting for run to be queued...");
1288
1310
  }
1289
- await new Promise((resolve17) => setTimeout(resolve17, intervalMs));
1311
+ await new Promise((resolve18) => setTimeout(resolve18, intervalMs));
1290
1312
  }
1291
1313
  throw new Error(
1292
1314
  `Workflow ${workflowId} did not complete within ${timeoutMs / 1e3 / 60} minutes`
@@ -2831,7 +2853,7 @@ var AwsAdapter = class {
2831
2853
  const code = err.Code;
2832
2854
  if (code === "OperationAborted" && attempt < maxAttempts) {
2833
2855
  log.info(` Waiting for S3 to release "${bucketName}"... (${attempt}/${maxAttempts})`);
2834
- await new Promise((resolve17) => setTimeout(resolve17, retryDelayMs));
2856
+ await new Promise((resolve18) => setTimeout(resolve18, retryDelayMs));
2835
2857
  } else if (code === "OperationAborted") {
2836
2858
  return false;
2837
2859
  } else {
@@ -7258,14 +7280,268 @@ pluginCommand.addCommand(pluginSyncMigrationsCommand);
7258
7280
  pluginCommand.addCommand(pluginInfoCommand);
7259
7281
 
7260
7282
  // src/commands/sibling.ts
7283
+ import { Command as Command22 } from "commander";
7284
+
7285
+ // src/commands/sibling-check-identity.ts
7286
+ import { existsSync as existsSync29, readFileSync as readFileSync22 } from "fs";
7287
+ import { resolve as resolve17 } from "path";
7288
+ import chalk20 from "chalk";
7261
7289
  import { Command as Command21 } from "commander";
7262
- var siblingCommand = new Command21("sibling").description(
7290
+
7291
+ // src/lib/sibling-identity-check.ts
7292
+ function checkSiblingIdentity(envs) {
7293
+ const findings = [];
7294
+ for (const env of envs) {
7295
+ if (env.publishedDoc === null) {
7296
+ findings.push({
7297
+ environment: env.environment,
7298
+ subject: "published-document",
7299
+ kind: "published-doc-unreachable",
7300
+ expected: env.livePoolId,
7301
+ actual: null
7302
+ });
7303
+ } else {
7304
+ const docPool = env.publishedDoc.userPoolId ?? null;
7305
+ if (docPool !== env.livePoolId) {
7306
+ findings.push({
7307
+ environment: env.environment,
7308
+ subject: "published-document",
7309
+ kind: "published-doc-stale",
7310
+ expected: env.livePoolId,
7311
+ actual: docPool
7312
+ });
7313
+ }
7314
+ }
7315
+ for (const sibling of env.siblings) {
7316
+ if (sibling.coreCognitoUserPoolId === null) {
7317
+ findings.push({
7318
+ environment: env.environment,
7319
+ subject: sibling.projectName,
7320
+ kind: "sibling-var-missing",
7321
+ expected: env.livePoolId,
7322
+ actual: null
7323
+ });
7324
+ } else if (sibling.coreCognitoUserPoolId !== env.livePoolId) {
7325
+ findings.push({
7326
+ environment: env.environment,
7327
+ subject: sibling.projectName,
7328
+ kind: "sibling-backend-stale",
7329
+ expected: env.livePoolId,
7330
+ actual: sibling.coreCognitoUserPoolId
7331
+ });
7332
+ }
7333
+ }
7334
+ }
7335
+ return { ok: findings.length === 0, findings };
7336
+ }
7337
+
7338
+ // src/commands/sibling-check-identity.ts
7339
+ var VALID_ENVIRONMENTS2 = ["dev", "staging", "prod"];
7340
+ var SIBLING_CORE_POOL_VAR = "CORE_COGNITO_USER_POOL_ID";
7341
+ var siblingCheckIdentityCommand = new Command21("check-identity").description(
7342
+ "Detect when a core's Cognito pool has drifted from its published identity document or any sibling's baked-in CORE_COGNITO_USER_POOL_ID (#400). Run from the core repo; exits non-zero on drift so a scheduled/CI run goes red."
7343
+ ).option("--env <environment>", "Only check this environment (default: dev, staging, prod)").option("-p, --project <name>", "Project name (overrides biffo.config.json in current directory)").option("-c, --config <path>", "Path to biffo.config.json").action(async (options) => {
7344
+ if (options.env && !VALID_ENVIRONMENTS2.includes(options.env)) {
7345
+ log.error(
7346
+ `Unknown environment: ${options.env}. Must be one of: ${VALID_ENVIRONMENTS2.join(", ")}`
7347
+ );
7348
+ process.exit(1);
7349
+ }
7350
+ const environments = options.env ? [options.env] : [...VALID_ENVIRONMENTS2];
7351
+ const config = await resolveConfig4(options);
7352
+ const { org, repo } = config.source_control.config;
7353
+ const awsConfig2 = config.cloud.config;
7354
+ const stateBucket = awsConfig2.tf_state_bucket ?? `${config.project.name}-terraform-state-${awsConfig2.account_id}`;
7355
+ const token = await resolveGithubToken3(true);
7356
+ const deps = {
7357
+ aws: new AwsAdapter(config),
7358
+ github: new GitHubAdapter(token),
7359
+ fetchIdentityDoc: fetchPublishedIdentity
7360
+ };
7361
+ console.log(chalk20.bold(`
7362
+ Biffo \u2014 Sibling identity check (${config.project.name})
7363
+ `));
7364
+ let result;
7365
+ try {
7366
+ result = await runCheckIdentity(deps, {
7367
+ coreOrg: org,
7368
+ coreRepo: repo,
7369
+ coreProjectName: config.project.name,
7370
+ stateBucket,
7371
+ environments
7372
+ });
7373
+ } catch (err) {
7374
+ if (err instanceof SiblingResolutionError) {
7375
+ log.error(`Could not enumerate siblings: ${err.message}`);
7376
+ } else {
7377
+ log.error(`Identity check failed: ${err.message}`);
7378
+ }
7379
+ process.exit(1);
7380
+ }
7381
+ printIdentityReport(result);
7382
+ if (!result.ok) process.exit(1);
7383
+ });
7384
+ async function runCheckIdentity(deps, params) {
7385
+ const siblings = await discoverSiblings(
7386
+ deps.github,
7387
+ params.coreOrg,
7388
+ params.coreRepo,
7389
+ params.coreProjectName
7390
+ );
7391
+ const envInputs = [];
7392
+ const skipped = [];
7393
+ for (const environment of params.environments) {
7394
+ let outputs;
7395
+ try {
7396
+ outputs = await deps.aws.readTerraformOutputs(
7397
+ params.stateBucket,
7398
+ `${environment}/terraform.tfstate`
7399
+ );
7400
+ } catch {
7401
+ skipped.push({
7402
+ environment,
7403
+ reason: "no Terraform state (core not deployed to this environment)"
7404
+ });
7405
+ continue;
7406
+ }
7407
+ const livePoolId = outputs["cognito_user_pool_id"];
7408
+ const portalUrl = outputs["portal_url"];
7409
+ if (!livePoolId || !portalUrl) {
7410
+ skipped.push({
7411
+ environment,
7412
+ reason: "Terraform outputs missing cognito_user_pool_id or portal_url"
7413
+ });
7414
+ continue;
7415
+ }
7416
+ const publishedDoc = await deps.fetchIdentityDoc(portalUrl);
7417
+ const envSiblings = siblings.filter(
7418
+ (s) => s.registered && s.repoState !== "gone" && s.environments.includes(environment)
7419
+ );
7420
+ const siblingInputs = await Promise.all(
7421
+ envSiblings.map(async (s) => ({
7422
+ projectName: s.projectName,
7423
+ coreCognitoUserPoolId: await deps.github.getEnvVariable(
7424
+ s.org,
7425
+ s.repo,
7426
+ environment,
7427
+ SIBLING_CORE_POOL_VAR
7428
+ )
7429
+ }))
7430
+ );
7431
+ envInputs.push({ environment, livePoolId, publishedDoc, siblings: siblingInputs });
7432
+ }
7433
+ const { ok, findings } = checkSiblingIdentity(envInputs);
7434
+ return { ok, findings, skipped };
7435
+ }
7436
+ var FINDING_LABEL = {
7437
+ "published-doc-unreachable": "published identity document unreachable",
7438
+ "published-doc-stale": "published identity document is stale",
7439
+ "sibling-var-missing": `sibling backend has no ${SIBLING_CORE_POOL_VAR}`,
7440
+ "sibling-backend-stale": `sibling backend ${SIBLING_CORE_POOL_VAR} is stale`
7441
+ };
7442
+ function printIdentityReport(result) {
7443
+ for (const s of result.skipped) {
7444
+ log.warn(`${s.environment}: skipped \u2014 ${s.reason}`);
7445
+ }
7446
+ if (result.ok) {
7447
+ log.success(
7448
+ chalk20.green(
7449
+ "\u2713 identity consistent \u2014 every published document and sibling backend matches the live pool"
7450
+ )
7451
+ );
7452
+ return;
7453
+ }
7454
+ log.error(chalk20.red(`\u2718 ${String(result.findings.length)} identity drift finding(s):`));
7455
+ for (const f of result.findings) {
7456
+ console.error(
7457
+ chalk20.red(
7458
+ ` [${f.environment}] ${f.subject}: ${FINDING_LABEL[f.kind]}
7459
+ expected (live pool): ${f.expected}
7460
+ found: ${f.actual ?? "(unset)"}`
7461
+ )
7462
+ );
7463
+ }
7464
+ }
7465
+ async function fetchPublishedIdentity(portalUrl) {
7466
+ try {
7467
+ const base = portalUrl.replace(/\/+$/, "");
7468
+ const res = await fetch(`${base}/.well-known/biffo-identity.json`, {
7469
+ cache: "no-store"
7470
+ });
7471
+ if (!res.ok) return null;
7472
+ const data = await res.json();
7473
+ return { userPoolId: data?.userPoolId ?? null };
7474
+ } catch {
7475
+ return null;
7476
+ }
7477
+ }
7478
+ async function resolveConfig4(options) {
7479
+ if (options.config) {
7480
+ const raw = JSON.parse(readFileSync22(resolve17(options.config), "utf8"));
7481
+ const result = BiffoConfigSchema.safeParse(raw);
7482
+ if (!result.success) {
7483
+ log.error(`Invalid config at ${options.config}:`);
7484
+ result.error.issues.forEach((i) => log.error(` ${i.path.join(".")} \u2014 ${i.message}`));
7485
+ process.exit(1);
7486
+ }
7487
+ return result.data;
7488
+ }
7489
+ if (options.project) {
7490
+ const cfg = loadProjectConfig(options.project);
7491
+ if (!cfg) {
7492
+ log.error(
7493
+ `Project "${options.project}" not found in ~/.biffo/projects/. Run biffo init first or pass --config <path>.`
7494
+ );
7495
+ process.exit(1);
7496
+ }
7497
+ return cfg;
7498
+ }
7499
+ const localConfigPath = resolve17(process.cwd(), "biffo.config.json");
7500
+ if (existsSync29(localConfigPath)) {
7501
+ const raw = JSON.parse(readFileSync22(localConfigPath, "utf8"));
7502
+ const result = BiffoConfigSchema.safeParse(raw);
7503
+ if (result.success) return result.data;
7504
+ if (isTemplatePlaceholderConfig(raw)) {
7505
+ log.warn(
7506
+ `Ignoring ${localConfigPath} \u2014 it is the unsubstituted Biffo template placeholder, not this project's config.`
7507
+ );
7508
+ } else {
7509
+ log.error(`Invalid config at ${localConfigPath}:`);
7510
+ result.error.issues.forEach((i) => log.error(` ${i.path.join(".")} \u2014 ${i.message}`));
7511
+ log.error("Refusing to fall back to a saved project while a local config file is present.");
7512
+ log.error("Fix biffo.config.json, or pass --project <name> / --config <path> explicitly.");
7513
+ process.exit(1);
7514
+ }
7515
+ }
7516
+ const projects = listProjectConfigs();
7517
+ if (projects.length === 0) {
7518
+ log.error(
7519
+ "No biffo.config.json found in the current directory and no projects in ~/.biffo/projects/."
7520
+ );
7521
+ log.error("Run biffo init first, or pass --project <name> or --config <path>.");
7522
+ log.error(
7523
+ "A Biffo instance's resolved config is not committed to its repo \u2014 it lives in ~/.biffo/projects/ on the machine that ran biffo init. On a second machine, copy it there or pass it with --config <path>."
7524
+ );
7525
+ process.exit(1);
7526
+ }
7527
+ if (projects.length === 1) {
7528
+ log.info(`Using project: ${projects[0].project.name}`);
7529
+ return projects[0];
7530
+ }
7531
+ log.error("Multiple projects found in ~/.biffo/projects/. Pass --project <name> to choose one:");
7532
+ for (const p of projects) log.error(` ${p.project.name}`);
7533
+ process.exit(1);
7534
+ }
7535
+
7536
+ // src/commands/sibling.ts
7537
+ var siblingCommand = new Command22("sibling").description(
7263
7538
  "Create and manage sibling apps that share a Biffo core project (ADR-0007)"
7264
7539
  );
7265
7540
  siblingCommand.addCommand(siblingCreateCommand);
7541
+ siblingCommand.addCommand(siblingCheckIdentityCommand);
7266
7542
 
7267
7543
  // src/commands/check.ts
7268
- import { Command as Command22 } from "commander";
7544
+ import { Command as Command23 } from "commander";
7269
7545
 
7270
7546
  // src/scripts/check-core-ownership.ts
7271
7547
  import { execa as execa5 } from "execa";
@@ -7291,8 +7567,8 @@ async function runOwnershipCheck(argv) {
7291
7567
  const { stdout } = await execa5("git", ["diff", "--cached", "--name-status"], { cwd: root });
7292
7568
  ({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
7293
7569
  if (messageFile) {
7294
- const { readFileSync: readFileSync23, existsSync: existsSync30 } = await import("fs");
7295
- if (existsSync30(messageFile)) commitMessage = readFileSync23(messageFile, "utf8");
7570
+ const { readFileSync: readFileSync24, existsSync: existsSync31 } = await import("fs");
7571
+ if (existsSync31(messageFile)) commitMessage = readFileSync24(messageFile, "utf8");
7296
7572
  }
7297
7573
  } else {
7298
7574
  const base = process.env["GITHUB_BASE_REF"] ?? args[0];
@@ -7396,7 +7672,7 @@ ${BOLD}If the divergence is deliberate${OFF}
7396
7672
  import { execa as execa6 } from "execa";
7397
7673
 
7398
7674
  // src/lib/plugin-terraform-guard.ts
7399
- import { existsSync as existsSync29, readFileSync as readFileSync22, readdirSync as readdirSync12 } from "fs";
7675
+ import { existsSync as existsSync30, readFileSync as readFileSync23, readdirSync as readdirSync12 } from "fs";
7400
7676
  import { dirname as dirname9, join as join30, relative as relative6, sep as sep3 } from "path";
7401
7677
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
7402
7678
  var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
@@ -7424,7 +7700,7 @@ function findPluginManifests(root) {
7424
7700
  function readSubscriptions(absManifestPath) {
7425
7701
  let parsed;
7426
7702
  try {
7427
- parsed = JSON.parse(readFileSync22(absManifestPath, "utf8"));
7703
+ parsed = JSON.parse(readFileSync23(absManifestPath, "utf8"));
7428
7704
  } catch {
7429
7705
  return null;
7430
7706
  }
@@ -7439,14 +7715,14 @@ function readSubscriptions(absManifestPath) {
7439
7715
  }
7440
7716
  function checkPluginTerraform(root) {
7441
7717
  const violations = [];
7442
- const coreManifest = existsSync29(join30(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
7718
+ const coreManifest = existsSync30(join30(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
7443
7719
  for (const manifest of findPluginManifests(root)) {
7444
7720
  if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
7445
7721
  const absManifest = join30(root, manifest);
7446
7722
  const subscriptions = readSubscriptions(absManifest);
7447
7723
  if (subscriptions === null) continue;
7448
7724
  const pluginDir2 = dirname9(absManifest);
7449
- if (existsSync29(join30(pluginDir2, "terraform"))) continue;
7725
+ if (existsSync30(join30(pluginDir2, "terraform"))) continue;
7450
7726
  const relPluginDir = relative6(root, pluginDir2).split(sep3).join("/");
7451
7727
  violations.push({
7452
7728
  manifest,
@@ -7565,7 +7841,7 @@ async function runReleaseSubjectCheck(argv) {
7565
7841
  }
7566
7842
 
7567
7843
  // src/commands/check.ts
7568
- var checkCommand = new Command22("check").description(
7844
+ var checkCommand = new Command23("check").description(
7569
7845
  "Repo guards (ownership, release subject, plugin terraform) \u2014 run in CI and git hooks"
7570
7846
  );
7571
7847
  checkCommand.command("ownership").description("Refuse changes to template-owned paths in an instance (#370)").argument("[base]", "Base branch to diff against; defaults to $GITHUB_BASE_REF").option("--staged <messageFile>", "Check staged changes instead of a branch diff (commit hook)").allowExcessArguments(true).action(async () => {
@@ -7585,17 +7861,17 @@ function rawArgsAfter(subcommand) {
7585
7861
  // src/commands/teardown.ts
7586
7862
  import { execSync as execSync7 } from "child_process";
7587
7863
  import { GetCallerIdentityCommand as GetCallerIdentityCommand3, STSClient as STSClient3 } from "@aws-sdk/client-sts";
7588
- import chalk20 from "chalk";
7589
- import { Command as Command23 } from "commander";
7864
+ import chalk21 from "chalk";
7865
+ import { Command as Command24 } from "commander";
7590
7866
  import inquirer8 from "inquirer";
7591
- var teardownCommand = new Command23("teardown").description(
7867
+ var teardownCommand = new Command24("teardown").description(
7592
7868
  "Destroy all infrastructure then remove the repo, IAM role, and state bucket \u2014 single command"
7593
7869
  ).option("--project <name>", "Project name to tear down (reads session if omitted)").option("--skip-destroy", "Skip terraform destroy (only use if infrastructure is already gone)").option(
7594
7870
  "--confirm <name>",
7595
7871
  "Pre-confirm by supplying the project name (the scriptable form of the typed confirmation)"
7596
7872
  ).option("-y, --yes", "Skip the typed project-name confirmation entirely").action(
7597
7873
  async (options) => {
7598
- console.log(chalk20.bold("\n Biffo \u2014 Teardown\n"));
7874
+ console.log(chalk21.bold("\n Biffo \u2014 Teardown\n"));
7599
7875
  const githubToken = resolveGithubToken4();
7600
7876
  const sts = new STSClient3({});
7601
7877
  const { Account: accountId } = await sts.send(new GetCallerIdentityCommand3({}));
@@ -7617,7 +7893,7 @@ var teardownCommand = new Command23("teardown").description(
7617
7893
  adminEmail = session.config.admin?.email ?? "noop@example.com";
7618
7894
  adminUsername = session.config.admin?.username ?? "noop";
7619
7895
  domain = session.config.project.domain ?? "";
7620
- console.log(chalk20.yellow(" Loaded session for: ") + chalk20.bold(projectName));
7896
+ console.log(chalk21.yellow(" Loaded session for: ") + chalk21.bold(projectName));
7621
7897
  if (org && repo) console.log(` Repository: ${org}/${repo}`);
7622
7898
  console.log();
7623
7899
  } else if (savedConfig) {
@@ -7630,7 +7906,7 @@ var teardownCommand = new Command23("teardown").description(
7630
7906
  adminEmail = savedConfig.admin.email;
7631
7907
  adminUsername = savedConfig.admin.username;
7632
7908
  domain = resolveDnsConfig(savedConfig).domain;
7633
- console.log(chalk20.yellow(" Loaded config for: ") + chalk20.bold(projectName));
7909
+ console.log(chalk21.yellow(" Loaded config for: ") + chalk21.bold(projectName));
7634
7910
  console.log(` Repository: ${org}/${repo}`);
7635
7911
  console.log();
7636
7912
  } else {
@@ -7701,35 +7977,35 @@ var teardownCommand = new Command23("teardown").description(
7701
7977
  throw err;
7702
7978
  }
7703
7979
  }
7704
- console.log(chalk20.red.bold(" This will permanently delete:\n"));
7980
+ console.log(chalk21.red.bold(" This will permanently delete:\n"));
7705
7981
  if (deployedEnvs.length > 0 || hasGlobal) {
7706
- console.log(chalk20.red(" Infrastructure (via GitHub Actions terraform destroy):"));
7982
+ console.log(chalk21.red(" Infrastructure (via GitHub Actions terraform destroy):"));
7707
7983
  for (const env of deployedEnvs) {
7708
7984
  console.log(
7709
- ` ${chalk20.red("\u2717")} ${env} \u2014 VPC, RDS, Lambda, Cognito, CloudFront, EventBridge`
7985
+ ` ${chalk21.red("\u2717")} ${env} \u2014 VPC, RDS, Lambda, Cognito, CloudFront, EventBridge`
7710
7986
  );
7711
7987
  }
7712
7988
  if (hasGlobal) {
7713
- console.log(` ${chalk20.red("\u2717")} global \u2014 Route 53 hosted zone, ACM certificate`);
7989
+ console.log(` ${chalk21.red("\u2717")} global \u2014 Route 53 hosted zone, ACM certificate`);
7714
7990
  }
7715
7991
  console.log();
7716
7992
  }
7717
7993
  for (const line of formatSiblingPlan(siblings, options.skipDestroy === true)) {
7718
7994
  console.log(line);
7719
7995
  }
7720
- console.log(chalk20.red(" Biffo resources:"));
7721
- console.log(` ${chalk20.red("\u2717")} GitHub repository ${chalk20.bold(`${org}/${repo}`)}`);
7996
+ console.log(chalk21.red(" Biffo resources:"));
7997
+ console.log(` ${chalk21.red("\u2717")} GitHub repository ${chalk21.bold(`${org}/${repo}`)}`);
7722
7998
  console.log(
7723
- ` ${chalk20.red("\u2717")} IAM role ${chalk20.bold(`biffo-github-actions-${projectName}`)}`
7999
+ ` ${chalk21.red("\u2717")} IAM role ${chalk21.bold(`biffo-github-actions-${projectName}`)}`
7724
8000
  );
7725
8001
  console.log(
7726
- ` ${chalk20.red("\u2717")} S3 bucket ${chalk20.bold(stateBucket)} (all versions)`
8002
+ ` ${chalk21.red("\u2717")} S3 bucket ${chalk21.bold(stateBucket)} (all versions)`
7727
8003
  );
7728
- console.log(` ${chalk20.red("\u2717")} Local session file`);
8004
+ console.log(` ${chalk21.red("\u2717")} Local session file`);
7729
8005
  if (options.skipDestroy) {
7730
8006
  console.log();
7731
8007
  console.log(
7732
- chalk20.yellow(
8008
+ chalk21.yellow(
7733
8009
  " --skip-destroy: NO terraform destroy runs, for this project or any sibling.\n Everything above is deleted, but any infrastructure still standing is left\n standing \u2014 and, with the state buckets gone, orphaned."
7734
8010
  )
7735
8011
  );
@@ -7917,30 +8193,30 @@ async function assertSiblingsAreDestroyable(github, siblings) {
7917
8193
  }
7918
8194
  function formatSiblingPlan(siblings, skipDestroy) {
7919
8195
  if (siblings.length === 0) return [];
7920
- const lines = [chalk20.red(` Sibling apps (${siblings.length}) \u2014 ADR-0007:`)];
8196
+ const lines = [chalk21.red(` Sibling apps (${siblings.length}) \u2014 ADR-0007:`)];
7921
8197
  for (const s of siblings) {
7922
8198
  const envs = s.environments.join(", ");
7923
8199
  if (s.repoState === "gone") {
7924
8200
  lines.push(
7925
- ` ${chalk20.yellow("!")} ${chalk20.bold(`${s.org}/${s.repo}`)} \u2014 repo already deleted; its ${envs} infrastructure CANNOT be destroyed and will be left standing`
8201
+ ` ${chalk21.yellow("!")} ${chalk21.bold(`${s.org}/${s.repo}`)} \u2014 repo already deleted; its ${envs} infrastructure CANNOT be destroyed and will be left standing`
7926
8202
  );
7927
8203
  continue;
7928
8204
  }
7929
8205
  lines.push(
7930
- ` ${chalk20.red("\u2717")} GitHub repository ${chalk20.bold(`${s.org}/${s.repo}`)} ` + (s.pathPrefix === ROOT_SIBLING_NAME ? "(the application, routed at /)" : `(routed at /${s.pathPrefix})`)
8206
+ ` ${chalk21.red("\u2717")} GitHub repository ${chalk21.bold(`${s.org}/${s.repo}`)} ` + (s.pathPrefix === ROOT_SIBLING_NAME ? "(the application, routed at /)" : `(routed at /${s.pathPrefix})`)
7931
8207
  );
7932
8208
  lines.push(
7933
- ` ${chalk20.red("\u2717")} ${skipDestroy ? "infrastructure NOT destroyed (--skip-destroy)" : `${envs} infrastructure \u2014 S3 site bucket, Lambda, API Gateway`}`
8209
+ ` ${chalk21.red("\u2717")} ${skipDestroy ? "infrastructure NOT destroyed (--skip-destroy)" : `${envs} infrastructure \u2014 S3 site bucket, Lambda, API Gateway`}`
7934
8210
  );
7935
8211
  lines.push(
7936
- ` ${chalk20.red("\u2717")} IAM role ${chalk20.bold(`biffo-github-actions-${s.projectName}`)}`
8212
+ ` ${chalk21.red("\u2717")} IAM role ${chalk21.bold(`biffo-github-actions-${s.projectName}`)}`
7937
8213
  );
7938
8214
  lines.push(
7939
- ` ${chalk20.red("\u2717")} S3 bucket ${chalk20.bold(`${s.projectName}-terraform-state-${s.accountId}`)}`
8215
+ ` ${chalk21.red("\u2717")} S3 bucket ${chalk21.bold(`${s.projectName}-terraform-state-${s.accountId}`)}`
7940
8216
  );
7941
8217
  if (s.pendingRegistrationPr !== void 0) {
7942
8218
  lines.push(
7943
- chalk20.dim(` registration PR #${s.pendingRegistrationPr} is still open \u2014 never routed`)
8219
+ chalk21.dim(` registration PR #${s.pendingRegistrationPr} is still open \u2014 never routed`)
7944
8220
  );
7945
8221
  }
7946
8222
  }
@@ -7978,7 +8254,7 @@ async function confirmTeardown(projectName, options) {
7978
8254
  {
7979
8255
  type: "input",
7980
8256
  name: "confirm",
7981
- message: `Type ${chalk20.bold(projectName)} to confirm:`
8257
+ message: `Type ${chalk21.bold(projectName)} to confirm:`
7982
8258
  }
7983
8259
  ]);
7984
8260
  return confirm === projectName;
@@ -7996,7 +8272,7 @@ function resolveGithubToken4() {
7996
8272
  }
7997
8273
 
7998
8274
  // src/index.ts
7999
- var program = new Command24();
8275
+ var program = new Command25();
8000
8276
  function cliVersion() {
8001
8277
  try {
8002
8278
  return getLatestCoreVersion();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.80.0",
3
+ "version": "0.82.0",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",