@biffo/cli 0.108.3 → 0.108.5
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/index.js +77 -334
- 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
|
|
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((
|
|
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((
|
|
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((
|
|
1042
|
+
await new Promise((resolve18) => setTimeout(resolve18, protectionIntervalMs));
|
|
1043
1043
|
}
|
|
1044
1044
|
}
|
|
1045
1045
|
}
|
|
@@ -1265,7 +1265,7 @@ var GitHubAdapter = class {
|
|
|
1265
1265
|
} catch (err) {
|
|
1266
1266
|
if (err.status !== 404 || Date.now() >= deadline) throw err;
|
|
1267
1267
|
log.info(`Workflow ${workflowId} not yet indexed by GitHub Actions, retrying...`);
|
|
1268
|
-
await new Promise((
|
|
1268
|
+
await new Promise((resolve18) => setTimeout(resolve18, intervalMs));
|
|
1269
1269
|
}
|
|
1270
1270
|
}
|
|
1271
1271
|
}
|
|
@@ -1284,7 +1284,7 @@ var GitHubAdapter = class {
|
|
|
1284
1284
|
} catch (err) {
|
|
1285
1285
|
if (err.status !== 404 || Date.now() >= deadline) throw err;
|
|
1286
1286
|
log.info(`Workflow ${workflowId} not yet indexed by GitHub Actions, retrying...`);
|
|
1287
|
-
await new Promise((
|
|
1287
|
+
await new Promise((resolve18) => setTimeout(resolve18, intervalMs));
|
|
1288
1288
|
}
|
|
1289
1289
|
}
|
|
1290
1290
|
}
|
|
@@ -1308,7 +1308,7 @@ var GitHubAdapter = class {
|
|
|
1308
1308
|
} else {
|
|
1309
1309
|
log.info(" Waiting for run to be queued...");
|
|
1310
1310
|
}
|
|
1311
|
-
await new Promise((
|
|
1311
|
+
await new Promise((resolve18) => setTimeout(resolve18, intervalMs));
|
|
1312
1312
|
}
|
|
1313
1313
|
throw new Error(
|
|
1314
1314
|
`Workflow ${workflowId} did not complete within ${timeoutMs / 1e3 / 60} minutes`
|
|
@@ -1998,15 +1998,6 @@ function standardArguments(pluginName, handler) {
|
|
|
1998
1998
|
["event_bus_name", "module.events.event_bus_name"],
|
|
1999
1999
|
["core_api_url", "module.api_gateway.api_endpoint"],
|
|
2000
2000
|
["core_api_execution_arn", "module.api_gateway.execution_arn"],
|
|
2001
|
-
// User-facing plugin inputs (ADR-0018 §1): the shared-Cognito coordinates its
|
|
2002
|
-
// Lambda verifies the founder's JWT against, and the CloudFront distribution
|
|
2003
|
-
// ARN its frontend bucket policy grants read to. Emitted ONLY for a plugin whose
|
|
2004
|
-
// module declares them (declaredVariables gate), so an event-only plugin is
|
|
2005
|
-
// unaffected — same graceful-degradation contract as core_api_execution_arn.
|
|
2006
|
-
["cognito_user_pool_id", "module.auth.user_pool_id"],
|
|
2007
|
-
["cognito_client_id", "module.auth.client_id"],
|
|
2008
|
-
["cognito_region", "var.aws_region"],
|
|
2009
|
-
["cdn_distribution_arn", "module.cdn.distribution_arn"],
|
|
2010
2001
|
["tags", "local.tags"]
|
|
2011
2002
|
];
|
|
2012
2003
|
}
|
|
@@ -2092,58 +2083,13 @@ function declaredVariables(moduleDir) {
|
|
|
2092
2083
|
}
|
|
2093
2084
|
return names;
|
|
2094
2085
|
}
|
|
2095
|
-
var USER_FACING_PLUGIN_OUTPUTS = [
|
|
2096
|
-
"function_url_domain",
|
|
2097
|
-
"frontend_bucket_regional_domain",
|
|
2098
|
-
"frontend_bucket_name"
|
|
2099
|
-
];
|
|
2100
|
-
function rootPluginOutputName(pluginName, output) {
|
|
2101
|
-
return `plugin_${pluginName}_${output}`;
|
|
2102
|
-
}
|
|
2103
|
-
function pluginOutputsFromRoot(pluginName, rootOutputs) {
|
|
2104
|
-
const flat = {};
|
|
2105
|
-
for (const key of USER_FACING_PLUGIN_OUTPUTS) {
|
|
2106
|
-
const value = rootOutputs[rootPluginOutputName(pluginName, key)];
|
|
2107
|
-
if (value !== void 0) flat[key] = value;
|
|
2108
|
-
}
|
|
2109
|
-
return flat;
|
|
2110
|
-
}
|
|
2111
|
-
function declaredOutputs(moduleDir) {
|
|
2112
|
-
const names = /* @__PURE__ */ new Set();
|
|
2113
|
-
let entries;
|
|
2114
|
-
try {
|
|
2115
|
-
entries = readdirSync3(moduleDir, { withFileTypes: true });
|
|
2116
|
-
} catch {
|
|
2117
|
-
return names;
|
|
2118
|
-
}
|
|
2119
|
-
for (const entry of entries) {
|
|
2120
|
-
if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
|
|
2121
|
-
let contents;
|
|
2122
|
-
try {
|
|
2123
|
-
contents = readFileSync7(join10(moduleDir, entry.name), "utf8");
|
|
2124
|
-
} catch {
|
|
2125
|
-
continue;
|
|
2126
|
-
}
|
|
2127
|
-
for (const match of contents.matchAll(/^\s*output\s+"([^"]+)"/gm)) {
|
|
2128
|
-
names.add(match[1]);
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
return names;
|
|
2132
|
-
}
|
|
2133
2086
|
function renderArguments(args, indent) {
|
|
2134
2087
|
const width = Math.max(...args.map(([key]) => key.length));
|
|
2135
2088
|
return args.map(([key, value]) => `${indent}${key.padEnd(width)} = ${value}`).join("\n");
|
|
2136
2089
|
}
|
|
2137
|
-
function renderModuleBlock(pluginName, declared, handler, source
|
|
2090
|
+
function renderModuleBlock(pluginName, declared, handler, source) {
|
|
2138
2091
|
const args = standardArguments(pluginName, handler).filter(([key]) => declared.has(key));
|
|
2139
2092
|
const quoted = JSON.stringify(pluginName);
|
|
2140
|
-
const outputBlock = (name, description, attr) => [
|
|
2141
|
-
"",
|
|
2142
|
-
`output "${name}" {`,
|
|
2143
|
-
` description = "${description}"`,
|
|
2144
|
-
` value = try(module.plugin_${pluginName}[${quoted}].${attr}, null)`,
|
|
2145
|
-
"}"
|
|
2146
|
-
];
|
|
2147
2093
|
const lines = [
|
|
2148
2094
|
`module "plugin_${pluginName}" {`,
|
|
2149
2095
|
` source = "${source}"`,
|
|
@@ -2151,22 +2097,12 @@ function renderModuleBlock(pluginName, declared, handler, source, declaredOutput
|
|
|
2151
2097
|
"",
|
|
2152
2098
|
renderArguments(args, " "),
|
|
2153
2099
|
"}",
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2100
|
+
"",
|
|
2101
|
+
`output "plugin_${pluginName}_function_arn" {`,
|
|
2102
|
+
` description = "Lambda ARN of the ${pluginName} plugin, or null when it is not in enabled_plugins."`,
|
|
2103
|
+
` value = try(module.plugin_${pluginName}[${quoted}].function_arn, null)`,
|
|
2104
|
+
"}"
|
|
2159
2105
|
];
|
|
2160
|
-
for (const output of USER_FACING_PLUGIN_OUTPUTS) {
|
|
2161
|
-
if (!declaredOutputNames.has(output)) continue;
|
|
2162
|
-
lines.push(
|
|
2163
|
-
...outputBlock(
|
|
2164
|
-
rootPluginOutputName(pluginName, output),
|
|
2165
|
-
`${output.replace(/_/g, " ")} of the ${pluginName} plugin (ADR-0018 register step), or null when it is not enabled.`,
|
|
2166
|
-
output
|
|
2167
|
-
)
|
|
2168
|
-
);
|
|
2169
|
-
}
|
|
2170
2106
|
return lines.join("\n");
|
|
2171
2107
|
}
|
|
2172
2108
|
var GENERATED_HEADER = `# ---------------------------------------------------------------------------
|
|
@@ -2197,8 +2133,7 @@ function renderGeneratedTerraform(plugins) {
|
|
|
2197
2133
|
p.name,
|
|
2198
2134
|
p.declaredVariables,
|
|
2199
2135
|
p.handler ?? DEFAULT_PLUGIN_HANDLER,
|
|
2200
|
-
p.source ?? THIRD_PARTY_TERRAFORM(p.name)
|
|
2201
|
-
p.declaredOutputs ?? /* @__PURE__ */ new Set()
|
|
2136
|
+
p.source ?? THIRD_PARTY_TERRAFORM(p.name)
|
|
2202
2137
|
)
|
|
2203
2138
|
);
|
|
2204
2139
|
return `${GENERATED_HEADER}
|
|
@@ -2219,7 +2154,6 @@ function syncPluginTerraform(cwd) {
|
|
|
2219
2154
|
return {
|
|
2220
2155
|
name,
|
|
2221
2156
|
declaredVariables: declaredVariables(moduleDir),
|
|
2222
|
-
declaredOutputs: declaredOutputs(moduleDir),
|
|
2223
2157
|
source: pluginModuleSource(cwd, name)
|
|
2224
2158
|
};
|
|
2225
2159
|
});
|
|
@@ -2917,7 +2851,7 @@ var AwsAdapter = class {
|
|
|
2917
2851
|
const code = err.Code;
|
|
2918
2852
|
if (code === "OperationAborted" && attempt < maxAttempts) {
|
|
2919
2853
|
log.info(` Waiting for S3 to release "${bucketName}"... (${attempt}/${maxAttempts})`);
|
|
2920
|
-
await new Promise((
|
|
2854
|
+
await new Promise((resolve18) => setTimeout(resolve18, retryDelayMs));
|
|
2921
2855
|
} else if (code === "OperationAborted") {
|
|
2922
2856
|
return false;
|
|
2923
2857
|
} else {
|
|
@@ -6032,7 +5966,7 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
|
|
|
6032
5966
|
}
|
|
6033
5967
|
|
|
6034
5968
|
// src/commands/plugin.ts
|
|
6035
|
-
import { Command as
|
|
5969
|
+
import { Command as Command20 } from "commander";
|
|
6036
5970
|
|
|
6037
5971
|
// src/commands/plugin-create.ts
|
|
6038
5972
|
import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
|
|
@@ -6190,19 +6124,13 @@ var RouteDefSchema = z6.object({
|
|
|
6190
6124
|
});
|
|
6191
6125
|
}
|
|
6192
6126
|
});
|
|
6193
|
-
var USER_PATH_SEGMENT = /^[a-z][a-z0-9_-]*$/;
|
|
6194
|
-
var HANDLER_IMPORT_PATH = /^[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)+$/;
|
|
6195
6127
|
var REL_DIR = /^[\w][\w./-]*$/;
|
|
6196
6128
|
var NON_EMPTY_GROUP = "required_group must be a non-empty Cognito group name.";
|
|
6197
6129
|
var APP_REF = /^[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*:[a-zA-Z_]\w*$/;
|
|
6198
6130
|
var UserIngressSchema = z6.object({
|
|
6199
6131
|
required_group: z6.string().min(1, NON_EMPTY_GROUP),
|
|
6200
|
-
app: z6.string().regex(APP_REF, "must be an ASGI app reference '<module>:<attr>', e.g. 'ideation.app:app'")
|
|
6201
|
-
|
|
6202
|
-
path: z6.string().regex(USER_PATH_SEGMENT, "must be a single lowercase path segment, e.g. api").default("api")
|
|
6203
|
-
}).strict().refine((ui) => Boolean(ui.app ?? ui.handler), {
|
|
6204
|
-
message: "user_ingress must declare `app` (ADR-0021, preferred) or `handler` (legacy)"
|
|
6205
|
-
});
|
|
6132
|
+
app: z6.string().regex(APP_REF, "must be an ASGI app reference '<module>:<attr>', e.g. 'ideation.app:app'")
|
|
6133
|
+
}).strict();
|
|
6206
6134
|
var UserFrontendSchema = z6.object({
|
|
6207
6135
|
dir: z6.string().regex(
|
|
6208
6136
|
REL_DIR,
|
|
@@ -7027,28 +6955,6 @@ async function runPluginInstall(target, options, deps) {
|
|
|
7027
6955
|
console.log(chalk15.dim(` git push`));
|
|
7028
6956
|
console.log(chalk15.dim(` biffo deploy <environment> --app-only
|
|
7029
6957
|
`));
|
|
7030
|
-
if (manifest.user_ingress || manifest.user_frontend) {
|
|
7031
|
-
const group = manifest.user_ingress?.required_group ?? manifest.user_frontend?.required_group;
|
|
7032
|
-
console.log(chalk15.bold(` "${pluginName}" is a user-facing plugin (ADR-0018).`));
|
|
7033
|
-
console.log(
|
|
7034
|
-
` It serves an authenticated${group ? ` ${group}-gated` : ""} app at <base>/${pluginName}/ and an api at <base>/${pluginName}/api/*.`
|
|
7035
|
-
);
|
|
7036
|
-
console.log(
|
|
7037
|
-
" Two more steps route it onto the shared CloudFront (apply is pipeline-only):\n"
|
|
7038
|
-
);
|
|
7039
|
-
console.log(
|
|
7040
|
-
" 1. After the deploy above applies, register its CDN origins from the plugin module outputs, then redeploy so the CDN gains its behaviours:"
|
|
7041
|
-
);
|
|
7042
|
-
console.log(chalk15.dim(` biffo plugin wire ${pluginName} <environment>`));
|
|
7043
|
-
console.log(chalk15.dim(` git push && biffo deploy <environment>`));
|
|
7044
|
-
console.log(" 2. Build and sync its frontend to the bucket that first apply created");
|
|
7045
|
-
console.log(
|
|
7046
|
-
chalk15.dim(
|
|
7047
|
-
` (the plugin's own deploy workflow does this \u2014 see ${relTargetDir}/.github/workflows/).
|
|
7048
|
-
`
|
|
7049
|
-
)
|
|
7050
|
-
);
|
|
7051
|
-
}
|
|
7052
6958
|
} finally {
|
|
7053
6959
|
source.cleanup();
|
|
7054
6960
|
}
|
|
@@ -7510,187 +7416,24 @@ function printDryRun6(entry, currentVersion) {
|
|
|
7510
7416
|
`);
|
|
7511
7417
|
}
|
|
7512
7418
|
|
|
7513
|
-
// src/commands/plugin-wire.ts
|
|
7514
|
-
import { existsSync as existsSync31, readFileSync as readFileSync24 } from "fs";
|
|
7515
|
-
import { resolve as resolve17 } from "path";
|
|
7516
|
-
import chalk20 from "chalk";
|
|
7517
|
-
import { Command as Command20 } from "commander";
|
|
7518
|
-
|
|
7519
|
-
// src/lib/plugin-origin.ts
|
|
7520
|
-
import { existsSync as existsSync30, readFileSync as readFileSync23, writeFileSync as writeFileSync10 } from "fs";
|
|
7521
|
-
import { join as join31 } from "path";
|
|
7522
|
-
var PLUGIN_API_TFVARS = "plugin-apis.auto.tfvars.json";
|
|
7523
|
-
var SIBLINGS_TFVARS = "siblings.auto.tfvars.json";
|
|
7524
|
-
function upsertPluginApiOrigin(existing, entry) {
|
|
7525
|
-
return [...existing.filter((p) => p.name !== entry.name), entry];
|
|
7526
|
-
}
|
|
7527
|
-
function serializePluginApiRegistry(origins) {
|
|
7528
|
-
return JSON.stringify({ plugin_api_origins: origins }, null, 2) + "\n";
|
|
7529
|
-
}
|
|
7530
|
-
function readArray(path, key) {
|
|
7531
|
-
if (!existsSync30(path)) return [];
|
|
7532
|
-
try {
|
|
7533
|
-
const parsed = JSON.parse(readFileSync23(path, "utf8"));
|
|
7534
|
-
const arr = parsed[key];
|
|
7535
|
-
return Array.isArray(arr) ? arr : [];
|
|
7536
|
-
} catch {
|
|
7537
|
-
return [];
|
|
7538
|
-
}
|
|
7539
|
-
}
|
|
7540
|
-
function registerUserFacingPlugin(cwd, environment, reg) {
|
|
7541
|
-
const envDir = join31(cwd, "infra", "environments", environment);
|
|
7542
|
-
const apiPath = join31(envDir, PLUGIN_API_TFVARS);
|
|
7543
|
-
const siblingPath = join31(envDir, SIBLINGS_TFVARS);
|
|
7544
|
-
const apis = upsertPluginApiOrigin(readArray(apiPath, "plugin_api_origins"), {
|
|
7545
|
-
name: reg.name,
|
|
7546
|
-
function_url_domain: reg.functionUrlDomain
|
|
7547
|
-
});
|
|
7548
|
-
writeFileSync10(apiPath, serializePluginApiRegistry(apis));
|
|
7549
|
-
const siblings = upsertSiblingOrigin(readArray(siblingPath, "sibling_origins"), {
|
|
7550
|
-
name: reg.name,
|
|
7551
|
-
bucket_regional_domain: reg.bucketRegionalDomain
|
|
7552
|
-
});
|
|
7553
|
-
writeFileSync10(siblingPath, serializeRegistry(siblings));
|
|
7554
|
-
return [
|
|
7555
|
-
`infra/environments/${environment}/${PLUGIN_API_TFVARS}`,
|
|
7556
|
-
`infra/environments/${environment}/${SIBLINGS_TFVARS}`
|
|
7557
|
-
];
|
|
7558
|
-
}
|
|
7559
|
-
var REQUIRED_PLUGIN_OUTPUTS = [
|
|
7560
|
-
"function_url_domain",
|
|
7561
|
-
"frontend_bucket_regional_domain",
|
|
7562
|
-
"frontend_bucket_name"
|
|
7563
|
-
];
|
|
7564
|
-
function wireUserFacingPluginFromOutputs(cwd, environment, name, outputs) {
|
|
7565
|
-
const missing = REQUIRED_PLUGIN_OUTPUTS.filter((k) => !outputs[k]?.trim());
|
|
7566
|
-
if (missing.length > 0) {
|
|
7567
|
-
throw new Error(
|
|
7568
|
-
`Cannot register the user-facing plugin "${name}" for ${environment}: its Terraform apply did not expose ${missing.join(", ")}. A registration written without these would route ${name}/api/* and ${name}/* at nothing (the frontend 403s, the api 502s). Confirm the plugin module applied for ${environment} before wiring.`
|
|
7569
|
-
);
|
|
7570
|
-
}
|
|
7571
|
-
const registeredPaths = registerUserFacingPlugin(cwd, environment, {
|
|
7572
|
-
name,
|
|
7573
|
-
functionUrlDomain: outputs["function_url_domain"],
|
|
7574
|
-
bucketRegionalDomain: outputs["frontend_bucket_regional_domain"]
|
|
7575
|
-
});
|
|
7576
|
-
return { registeredPaths, frontendBucketName: outputs["frontend_bucket_name"] };
|
|
7577
|
-
}
|
|
7578
|
-
|
|
7579
|
-
// src/commands/plugin-wire.ts
|
|
7580
|
-
var VALID_ENVIRONMENTS2 = ["dev", "staging", "prod"];
|
|
7581
|
-
async function runPluginWire(pluginName, environment, config, deps, cwd = process.cwd()) {
|
|
7582
|
-
const awsConfig2 = config.cloud.config;
|
|
7583
|
-
const stateBucket = awsConfig2.tf_state_bucket ?? `${config.project.name}-terraform-state-${awsConfig2.account_id}`;
|
|
7584
|
-
const stateKey = `${environment}/terraform.tfstate`;
|
|
7585
|
-
log.info(`Reading Terraform outputs from s3://${stateBucket}/${stateKey}...`);
|
|
7586
|
-
const outputs = await deps.readOutputs(stateBucket, stateKey);
|
|
7587
|
-
const pluginOutputs = pluginOutputsFromRoot(pluginName, outputs);
|
|
7588
|
-
const targets = wireUserFacingPluginFromOutputs(cwd, environment, pluginName, pluginOutputs);
|
|
7589
|
-
console.log(chalk20.bold(`
|
|
7590
|
-
Registered "${pluginName}" CDN origins for ${environment}
|
|
7591
|
-
`));
|
|
7592
|
-
for (const path of targets.registeredPaths) console.log(` ${chalk20.green("~")} ${path}`);
|
|
7593
|
-
console.log(
|
|
7594
|
-
"\n Commit these and redeploy so the CDN gains its behaviours (apply is pipeline-only):"
|
|
7595
|
-
);
|
|
7596
|
-
console.log(chalk20.dim(` git add ${targets.registeredPaths.join(" ")}`));
|
|
7597
|
-
console.log(chalk20.dim(` git commit -m "infra(cdn): route ${pluginName} (${environment})"`));
|
|
7598
|
-
console.log(chalk20.dim(` git push && biffo deploy ${environment}
|
|
7599
|
-
`));
|
|
7600
|
-
console.log(
|
|
7601
|
-
` Then build and sync the frontend to its bucket: ${chalk20.bold(targets.frontendBucketName)}`
|
|
7602
|
-
);
|
|
7603
|
-
console.log(
|
|
7604
|
-
chalk20.dim(
|
|
7605
|
-
" (the plugin's own deploy workflow does this \u2014 it reads the bucket from the same outputs.)\n"
|
|
7606
|
-
)
|
|
7607
|
-
);
|
|
7608
|
-
return targets;
|
|
7609
|
-
}
|
|
7610
|
-
var pluginWireCommand = new Command20("wire").description(
|
|
7611
|
-
"Register a user-facing plugin's CDN origins from its deployed outputs (ADR-0018 register step): biffo plugin wire <name> <environment>"
|
|
7612
|
-
).argument("<name>", "Plugin name, e.g. ideation").argument("<environment>", "Target environment: 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(
|
|
7613
|
-
async (name, environment, options) => {
|
|
7614
|
-
if (!VALID_ENVIRONMENTS2.includes(environment)) {
|
|
7615
|
-
log.error(
|
|
7616
|
-
`Unknown environment: ${environment}. Must be one of: ${VALID_ENVIRONMENTS2.join(", ")}`
|
|
7617
|
-
);
|
|
7618
|
-
process.exit(1);
|
|
7619
|
-
}
|
|
7620
|
-
const config = await resolveConfig4(options);
|
|
7621
|
-
const aws = new AwsAdapter(config);
|
|
7622
|
-
try {
|
|
7623
|
-
await runPluginWire(name, environment, config, {
|
|
7624
|
-
readOutputs: (bucket, key) => aws.readTerraformOutputs(bucket, key)
|
|
7625
|
-
});
|
|
7626
|
-
} catch (err) {
|
|
7627
|
-
log.error(err.message);
|
|
7628
|
-
process.exit(1);
|
|
7629
|
-
}
|
|
7630
|
-
}
|
|
7631
|
-
);
|
|
7632
|
-
async function resolveConfig4(options) {
|
|
7633
|
-
if (options.config) {
|
|
7634
|
-
const raw = JSON.parse(readFileSync24(resolve17(options.config), "utf8"));
|
|
7635
|
-
const result = BiffoConfigSchema.safeParse(raw);
|
|
7636
|
-
if (!result.success) {
|
|
7637
|
-
log.error(`Invalid config at ${options.config}:`);
|
|
7638
|
-
result.error.issues.forEach((i) => log.error(` ${i.path.join(".")} \u2014 ${i.message}`));
|
|
7639
|
-
process.exit(1);
|
|
7640
|
-
}
|
|
7641
|
-
return result.data;
|
|
7642
|
-
}
|
|
7643
|
-
if (options.project) {
|
|
7644
|
-
const cfg = loadProjectConfig(options.project);
|
|
7645
|
-
if (!cfg) {
|
|
7646
|
-
log.error(
|
|
7647
|
-
`Project "${options.project}" not found in ~/.biffo/projects/. Run biffo init first or pass --config <path>.`
|
|
7648
|
-
);
|
|
7649
|
-
process.exit(1);
|
|
7650
|
-
}
|
|
7651
|
-
return cfg;
|
|
7652
|
-
}
|
|
7653
|
-
const localConfigPath = resolve17(process.cwd(), "biffo.config.json");
|
|
7654
|
-
if (existsSync31(localConfigPath)) {
|
|
7655
|
-
const raw = JSON.parse(readFileSync24(localConfigPath, "utf8"));
|
|
7656
|
-
const result = BiffoConfigSchema.safeParse(raw);
|
|
7657
|
-
if (result.success) return result.data;
|
|
7658
|
-
if (isTemplatePlaceholderConfig(raw)) {
|
|
7659
|
-
log.warn(
|
|
7660
|
-
`Ignoring ${localConfigPath} \u2014 it is the unsubstituted Biffo template placeholder, not this project's config.`
|
|
7661
|
-
);
|
|
7662
|
-
} else {
|
|
7663
|
-
log.error(`Invalid config at ${localConfigPath}:`);
|
|
7664
|
-
result.error.issues.forEach((i) => log.error(` ${i.path.join(".")} \u2014 ${i.message}`));
|
|
7665
|
-
log.error("Refusing to fall back to a saved project while a local config file is present.");
|
|
7666
|
-
process.exit(1);
|
|
7667
|
-
}
|
|
7668
|
-
}
|
|
7669
|
-
log.error(
|
|
7670
|
-
"No config found. Run inside a Biffo project, or pass --project <name> / --config <path>."
|
|
7671
|
-
);
|
|
7672
|
-
return process.exit(1);
|
|
7673
|
-
}
|
|
7674
|
-
|
|
7675
7419
|
// src/commands/plugin.ts
|
|
7676
|
-
var pluginCommand = new
|
|
7420
|
+
var pluginCommand = new Command20("plugin").description("Manage Biffo plugins");
|
|
7677
7421
|
pluginCommand.addCommand(pluginCreateCommand);
|
|
7678
7422
|
pluginCommand.addCommand(pluginListCommand);
|
|
7679
7423
|
pluginCommand.addCommand(pluginInstallCommand);
|
|
7680
|
-
pluginCommand.addCommand(pluginWireCommand);
|
|
7681
7424
|
pluginCommand.addCommand(pluginUninstallCommand);
|
|
7682
7425
|
pluginCommand.addCommand(pluginUpgradeCommand);
|
|
7683
7426
|
pluginCommand.addCommand(pluginSyncMigrationsCommand);
|
|
7684
7427
|
pluginCommand.addCommand(pluginInfoCommand);
|
|
7685
7428
|
|
|
7686
7429
|
// src/commands/sibling.ts
|
|
7687
|
-
import { Command as
|
|
7430
|
+
import { Command as Command22 } from "commander";
|
|
7688
7431
|
|
|
7689
7432
|
// src/commands/sibling-check-identity.ts
|
|
7690
|
-
import { existsSync as
|
|
7691
|
-
import { resolve as
|
|
7692
|
-
import
|
|
7693
|
-
import { Command as
|
|
7433
|
+
import { existsSync as existsSync30, readFileSync as readFileSync23 } from "fs";
|
|
7434
|
+
import { resolve as resolve17 } from "path";
|
|
7435
|
+
import chalk20 from "chalk";
|
|
7436
|
+
import { Command as Command21 } from "commander";
|
|
7694
7437
|
|
|
7695
7438
|
// src/lib/sibling-identity-check.ts
|
|
7696
7439
|
function checkSiblingIdentity(envs) {
|
|
@@ -7740,19 +7483,19 @@ function checkSiblingIdentity(envs) {
|
|
|
7740
7483
|
}
|
|
7741
7484
|
|
|
7742
7485
|
// src/commands/sibling-check-identity.ts
|
|
7743
|
-
var
|
|
7486
|
+
var VALID_ENVIRONMENTS2 = ["dev", "staging", "prod"];
|
|
7744
7487
|
var SIBLING_CORE_POOL_VAR = "CORE_COGNITO_USER_POOL_ID";
|
|
7745
|
-
var siblingCheckIdentityCommand = new
|
|
7488
|
+
var siblingCheckIdentityCommand = new Command21("check-identity").description(
|
|
7746
7489
|
"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."
|
|
7747
7490
|
).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) => {
|
|
7748
|
-
if (options.env && !
|
|
7491
|
+
if (options.env && !VALID_ENVIRONMENTS2.includes(options.env)) {
|
|
7749
7492
|
log.error(
|
|
7750
|
-
`Unknown environment: ${options.env}. Must be one of: ${
|
|
7493
|
+
`Unknown environment: ${options.env}. Must be one of: ${VALID_ENVIRONMENTS2.join(", ")}`
|
|
7751
7494
|
);
|
|
7752
7495
|
process.exit(1);
|
|
7753
7496
|
}
|
|
7754
|
-
const environments = options.env ? [options.env] : [...
|
|
7755
|
-
const config = await
|
|
7497
|
+
const environments = options.env ? [options.env] : [...VALID_ENVIRONMENTS2];
|
|
7498
|
+
const config = await resolveConfig4(options);
|
|
7756
7499
|
const { org, repo } = config.source_control.config;
|
|
7757
7500
|
const awsConfig2 = config.cloud.config;
|
|
7758
7501
|
const stateBucket = awsConfig2.tf_state_bucket ?? `${config.project.name}-terraform-state-${awsConfig2.account_id}`;
|
|
@@ -7762,7 +7505,7 @@ var siblingCheckIdentityCommand = new Command22("check-identity").description(
|
|
|
7762
7505
|
github: new GitHubAdapter(token),
|
|
7763
7506
|
fetchIdentityDoc: fetchPublishedIdentity
|
|
7764
7507
|
};
|
|
7765
|
-
console.log(
|
|
7508
|
+
console.log(chalk20.bold(`
|
|
7766
7509
|
Biffo \u2014 Sibling identity check (${config.project.name})
|
|
7767
7510
|
`));
|
|
7768
7511
|
let result;
|
|
@@ -7849,16 +7592,16 @@ function printIdentityReport(result) {
|
|
|
7849
7592
|
}
|
|
7850
7593
|
if (result.ok) {
|
|
7851
7594
|
log.success(
|
|
7852
|
-
|
|
7595
|
+
chalk20.green(
|
|
7853
7596
|
"\u2713 identity consistent \u2014 every published document and sibling backend matches the live pool"
|
|
7854
7597
|
)
|
|
7855
7598
|
);
|
|
7856
7599
|
return;
|
|
7857
7600
|
}
|
|
7858
|
-
log.error(
|
|
7601
|
+
log.error(chalk20.red(`\u2718 ${String(result.findings.length)} identity drift finding(s):`));
|
|
7859
7602
|
for (const f of result.findings) {
|
|
7860
7603
|
console.error(
|
|
7861
|
-
|
|
7604
|
+
chalk20.red(
|
|
7862
7605
|
` [${f.environment}] ${f.subject}: ${FINDING_LABEL[f.kind]}
|
|
7863
7606
|
expected (live pool): ${f.expected}
|
|
7864
7607
|
found: ${f.actual ?? "(unset)"}`
|
|
@@ -7879,9 +7622,9 @@ async function fetchPublishedIdentity(portalUrl) {
|
|
|
7879
7622
|
return null;
|
|
7880
7623
|
}
|
|
7881
7624
|
}
|
|
7882
|
-
async function
|
|
7625
|
+
async function resolveConfig4(options) {
|
|
7883
7626
|
if (options.config) {
|
|
7884
|
-
const raw = JSON.parse(
|
|
7627
|
+
const raw = JSON.parse(readFileSync23(resolve17(options.config), "utf8"));
|
|
7885
7628
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
7886
7629
|
if (!result.success) {
|
|
7887
7630
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -7900,9 +7643,9 @@ async function resolveConfig5(options) {
|
|
|
7900
7643
|
}
|
|
7901
7644
|
return cfg;
|
|
7902
7645
|
}
|
|
7903
|
-
const localConfigPath =
|
|
7904
|
-
if (
|
|
7905
|
-
const raw = JSON.parse(
|
|
7646
|
+
const localConfigPath = resolve17(process.cwd(), "biffo.config.json");
|
|
7647
|
+
if (existsSync30(localConfigPath)) {
|
|
7648
|
+
const raw = JSON.parse(readFileSync23(localConfigPath, "utf8"));
|
|
7906
7649
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
7907
7650
|
if (result.success) return result.data;
|
|
7908
7651
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -7938,14 +7681,14 @@ async function resolveConfig5(options) {
|
|
|
7938
7681
|
}
|
|
7939
7682
|
|
|
7940
7683
|
// src/commands/sibling.ts
|
|
7941
|
-
var siblingCommand = new
|
|
7684
|
+
var siblingCommand = new Command22("sibling").description(
|
|
7942
7685
|
"Create and manage sibling apps that share a Biffo core project (ADR-0007)"
|
|
7943
7686
|
);
|
|
7944
7687
|
siblingCommand.addCommand(siblingCreateCommand);
|
|
7945
7688
|
siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
7946
7689
|
|
|
7947
7690
|
// src/commands/check.ts
|
|
7948
|
-
import { Command as
|
|
7691
|
+
import { Command as Command23 } from "commander";
|
|
7949
7692
|
|
|
7950
7693
|
// src/scripts/check-core-ownership.ts
|
|
7951
7694
|
import { execa as execa5 } from "execa";
|
|
@@ -7971,8 +7714,8 @@ async function runOwnershipCheck(argv) {
|
|
|
7971
7714
|
const { stdout } = await execa5("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
7972
7715
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
7973
7716
|
if (messageFile) {
|
|
7974
|
-
const { readFileSync:
|
|
7975
|
-
if (
|
|
7717
|
+
const { readFileSync: readFileSync25, existsSync: existsSync32 } = await import("fs");
|
|
7718
|
+
if (existsSync32(messageFile)) commitMessage = readFileSync25(messageFile, "utf8");
|
|
7976
7719
|
}
|
|
7977
7720
|
} else {
|
|
7978
7721
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -8076,8 +7819,8 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
8076
7819
|
import { execa as execa6 } from "execa";
|
|
8077
7820
|
|
|
8078
7821
|
// src/lib/plugin-terraform-guard.ts
|
|
8079
|
-
import { existsSync as
|
|
8080
|
-
import { dirname as dirname9, join as
|
|
7822
|
+
import { existsSync as existsSync31, readFileSync as readFileSync24, readdirSync as readdirSync13 } from "fs";
|
|
7823
|
+
import { dirname as dirname9, join as join31, relative as relative6, sep as sep3 } from "path";
|
|
8081
7824
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
8082
7825
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
8083
7826
|
function findPluginManifests(root) {
|
|
@@ -8092,9 +7835,9 @@ function findPluginManifests(root) {
|
|
|
8092
7835
|
for (const entry of entries) {
|
|
8093
7836
|
if (entry.isDirectory()) {
|
|
8094
7837
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
8095
|
-
walk(
|
|
7838
|
+
walk(join31(dir, entry.name));
|
|
8096
7839
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
8097
|
-
found.push(relative6(root,
|
|
7840
|
+
found.push(relative6(root, join31(dir, entry.name)).split(sep3).join("/"));
|
|
8098
7841
|
}
|
|
8099
7842
|
}
|
|
8100
7843
|
};
|
|
@@ -8104,7 +7847,7 @@ function findPluginManifests(root) {
|
|
|
8104
7847
|
function readSubscriptions(absManifestPath) {
|
|
8105
7848
|
let parsed;
|
|
8106
7849
|
try {
|
|
8107
|
-
parsed = JSON.parse(
|
|
7850
|
+
parsed = JSON.parse(readFileSync24(absManifestPath, "utf8"));
|
|
8108
7851
|
} catch {
|
|
8109
7852
|
return null;
|
|
8110
7853
|
}
|
|
@@ -8119,14 +7862,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
8119
7862
|
}
|
|
8120
7863
|
function checkPluginTerraform(root) {
|
|
8121
7864
|
const violations = [];
|
|
8122
|
-
const coreManifest =
|
|
7865
|
+
const coreManifest = existsSync31(join31(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
8123
7866
|
for (const manifest of findPluginManifests(root)) {
|
|
8124
7867
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
8125
|
-
const absManifest =
|
|
7868
|
+
const absManifest = join31(root, manifest);
|
|
8126
7869
|
const subscriptions = readSubscriptions(absManifest);
|
|
8127
7870
|
if (subscriptions === null) continue;
|
|
8128
7871
|
const pluginDir2 = dirname9(absManifest);
|
|
8129
|
-
if (
|
|
7872
|
+
if (existsSync31(join31(pluginDir2, "terraform"))) continue;
|
|
8130
7873
|
const relPluginDir = relative6(root, pluginDir2).split(sep3).join("/");
|
|
8131
7874
|
violations.push({
|
|
8132
7875
|
manifest,
|
|
@@ -8245,7 +7988,7 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
8245
7988
|
}
|
|
8246
7989
|
|
|
8247
7990
|
// src/commands/check.ts
|
|
8248
|
-
var checkCommand = new
|
|
7991
|
+
var checkCommand = new Command23("check").description(
|
|
8249
7992
|
"Repo guards (ownership, release subject, plugin terraform) \u2014 run in CI and git hooks"
|
|
8250
7993
|
);
|
|
8251
7994
|
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 () => {
|
|
@@ -8265,17 +8008,17 @@ function rawArgsAfter(subcommand) {
|
|
|
8265
8008
|
// src/commands/teardown.ts
|
|
8266
8009
|
import { execSync as execSync7 } from "child_process";
|
|
8267
8010
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand3, STSClient as STSClient3 } from "@aws-sdk/client-sts";
|
|
8268
|
-
import
|
|
8269
|
-
import { Command as
|
|
8011
|
+
import chalk21 from "chalk";
|
|
8012
|
+
import { Command as Command24 } from "commander";
|
|
8270
8013
|
import inquirer8 from "inquirer";
|
|
8271
|
-
var teardownCommand = new
|
|
8014
|
+
var teardownCommand = new Command24("teardown").description(
|
|
8272
8015
|
"Destroy all infrastructure then remove the repo, IAM role, and state bucket \u2014 single command"
|
|
8273
8016
|
).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(
|
|
8274
8017
|
"--confirm <name>",
|
|
8275
8018
|
"Pre-confirm by supplying the project name (the scriptable form of the typed confirmation)"
|
|
8276
8019
|
).option("-y, --yes", "Skip the typed project-name confirmation entirely").action(
|
|
8277
8020
|
async (options) => {
|
|
8278
|
-
console.log(
|
|
8021
|
+
console.log(chalk21.bold("\n Biffo \u2014 Teardown\n"));
|
|
8279
8022
|
const githubToken = resolveGithubToken4();
|
|
8280
8023
|
const sts = new STSClient3({});
|
|
8281
8024
|
const { Account: accountId } = await sts.send(new GetCallerIdentityCommand3({}));
|
|
@@ -8297,7 +8040,7 @@ var teardownCommand = new Command25("teardown").description(
|
|
|
8297
8040
|
adminEmail = session.config.admin?.email ?? "noop@example.com";
|
|
8298
8041
|
adminUsername = session.config.admin?.username ?? "noop";
|
|
8299
8042
|
domain = session.config.project.domain ?? "";
|
|
8300
|
-
console.log(
|
|
8043
|
+
console.log(chalk21.yellow(" Loaded session for: ") + chalk21.bold(projectName));
|
|
8301
8044
|
if (org && repo) console.log(` Repository: ${org}/${repo}`);
|
|
8302
8045
|
console.log();
|
|
8303
8046
|
} else if (savedConfig) {
|
|
@@ -8310,7 +8053,7 @@ var teardownCommand = new Command25("teardown").description(
|
|
|
8310
8053
|
adminEmail = savedConfig.admin.email;
|
|
8311
8054
|
adminUsername = savedConfig.admin.username;
|
|
8312
8055
|
domain = resolveDnsConfig(savedConfig).domain;
|
|
8313
|
-
console.log(
|
|
8056
|
+
console.log(chalk21.yellow(" Loaded config for: ") + chalk21.bold(projectName));
|
|
8314
8057
|
console.log(` Repository: ${org}/${repo}`);
|
|
8315
8058
|
console.log();
|
|
8316
8059
|
} else {
|
|
@@ -8381,35 +8124,35 @@ var teardownCommand = new Command25("teardown").description(
|
|
|
8381
8124
|
throw err;
|
|
8382
8125
|
}
|
|
8383
8126
|
}
|
|
8384
|
-
console.log(
|
|
8127
|
+
console.log(chalk21.red.bold(" This will permanently delete:\n"));
|
|
8385
8128
|
if (deployedEnvs.length > 0 || hasGlobal) {
|
|
8386
|
-
console.log(
|
|
8129
|
+
console.log(chalk21.red(" Infrastructure (via GitHub Actions terraform destroy):"));
|
|
8387
8130
|
for (const env of deployedEnvs) {
|
|
8388
8131
|
console.log(
|
|
8389
|
-
` ${
|
|
8132
|
+
` ${chalk21.red("\u2717")} ${env} \u2014 VPC, RDS, Lambda, Cognito, CloudFront, EventBridge`
|
|
8390
8133
|
);
|
|
8391
8134
|
}
|
|
8392
8135
|
if (hasGlobal) {
|
|
8393
|
-
console.log(` ${
|
|
8136
|
+
console.log(` ${chalk21.red("\u2717")} global \u2014 Route 53 hosted zone, ACM certificate`);
|
|
8394
8137
|
}
|
|
8395
8138
|
console.log();
|
|
8396
8139
|
}
|
|
8397
8140
|
for (const line of formatSiblingPlan(siblings, options.skipDestroy === true)) {
|
|
8398
8141
|
console.log(line);
|
|
8399
8142
|
}
|
|
8400
|
-
console.log(
|
|
8401
|
-
console.log(` ${
|
|
8143
|
+
console.log(chalk21.red(" Biffo resources:"));
|
|
8144
|
+
console.log(` ${chalk21.red("\u2717")} GitHub repository ${chalk21.bold(`${org}/${repo}`)}`);
|
|
8402
8145
|
console.log(
|
|
8403
|
-
` ${
|
|
8146
|
+
` ${chalk21.red("\u2717")} IAM role ${chalk21.bold(`biffo-github-actions-${projectName}`)}`
|
|
8404
8147
|
);
|
|
8405
8148
|
console.log(
|
|
8406
|
-
` ${
|
|
8149
|
+
` ${chalk21.red("\u2717")} S3 bucket ${chalk21.bold(stateBucket)} (all versions)`
|
|
8407
8150
|
);
|
|
8408
|
-
console.log(` ${
|
|
8151
|
+
console.log(` ${chalk21.red("\u2717")} Local session file`);
|
|
8409
8152
|
if (options.skipDestroy) {
|
|
8410
8153
|
console.log();
|
|
8411
8154
|
console.log(
|
|
8412
|
-
|
|
8155
|
+
chalk21.yellow(
|
|
8413
8156
|
" --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."
|
|
8414
8157
|
)
|
|
8415
8158
|
);
|
|
@@ -8597,30 +8340,30 @@ async function assertSiblingsAreDestroyable(github, siblings) {
|
|
|
8597
8340
|
}
|
|
8598
8341
|
function formatSiblingPlan(siblings, skipDestroy) {
|
|
8599
8342
|
if (siblings.length === 0) return [];
|
|
8600
|
-
const lines = [
|
|
8343
|
+
const lines = [chalk21.red(` Sibling apps (${siblings.length}) \u2014 ADR-0007:`)];
|
|
8601
8344
|
for (const s of siblings) {
|
|
8602
8345
|
const envs = s.environments.join(", ");
|
|
8603
8346
|
if (s.repoState === "gone") {
|
|
8604
8347
|
lines.push(
|
|
8605
|
-
` ${
|
|
8348
|
+
` ${chalk21.yellow("!")} ${chalk21.bold(`${s.org}/${s.repo}`)} \u2014 repo already deleted; its ${envs} infrastructure CANNOT be destroyed and will be left standing`
|
|
8606
8349
|
);
|
|
8607
8350
|
continue;
|
|
8608
8351
|
}
|
|
8609
8352
|
lines.push(
|
|
8610
|
-
` ${
|
|
8353
|
+
` ${chalk21.red("\u2717")} GitHub repository ${chalk21.bold(`${s.org}/${s.repo}`)} ` + (s.pathPrefix === ROOT_SIBLING_NAME ? "(the application, routed at /)" : `(routed at /${s.pathPrefix})`)
|
|
8611
8354
|
);
|
|
8612
8355
|
lines.push(
|
|
8613
|
-
` ${
|
|
8356
|
+
` ${chalk21.red("\u2717")} ${skipDestroy ? "infrastructure NOT destroyed (--skip-destroy)" : `${envs} infrastructure \u2014 S3 site bucket, Lambda, API Gateway`}`
|
|
8614
8357
|
);
|
|
8615
8358
|
lines.push(
|
|
8616
|
-
` ${
|
|
8359
|
+
` ${chalk21.red("\u2717")} IAM role ${chalk21.bold(`biffo-github-actions-${s.projectName}`)}`
|
|
8617
8360
|
);
|
|
8618
8361
|
lines.push(
|
|
8619
|
-
` ${
|
|
8362
|
+
` ${chalk21.red("\u2717")} S3 bucket ${chalk21.bold(`${s.projectName}-terraform-state-${s.accountId}`)}`
|
|
8620
8363
|
);
|
|
8621
8364
|
if (s.pendingRegistrationPr !== void 0) {
|
|
8622
8365
|
lines.push(
|
|
8623
|
-
|
|
8366
|
+
chalk21.dim(` registration PR #${s.pendingRegistrationPr} is still open \u2014 never routed`)
|
|
8624
8367
|
);
|
|
8625
8368
|
}
|
|
8626
8369
|
}
|
|
@@ -8658,7 +8401,7 @@ async function confirmTeardown(projectName, options) {
|
|
|
8658
8401
|
{
|
|
8659
8402
|
type: "input",
|
|
8660
8403
|
name: "confirm",
|
|
8661
|
-
message: `Type ${
|
|
8404
|
+
message: `Type ${chalk21.bold(projectName)} to confirm:`
|
|
8662
8405
|
}
|
|
8663
8406
|
]);
|
|
8664
8407
|
return confirm === projectName;
|
|
@@ -8676,7 +8419,7 @@ function resolveGithubToken4() {
|
|
|
8676
8419
|
}
|
|
8677
8420
|
|
|
8678
8421
|
// src/index.ts
|
|
8679
|
-
var program = new
|
|
8422
|
+
var program = new Command25();
|
|
8680
8423
|
function cliVersion() {
|
|
8681
8424
|
try {
|
|
8682
8425
|
return getLatestCoreVersion();
|