@biffo/cli 0.97.0 → 0.99.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/index.js +341 -78
- 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 Command26 } 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((resolve19) => setTimeout(resolve19, 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((resolve19) => setTimeout(resolve19, 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((resolve19) => setTimeout(resolve19, 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((resolve19) => setTimeout(resolve19, 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((resolve19) => setTimeout(resolve19, 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((resolve19) => setTimeout(resolve19, intervalMs));
|
|
1312
1312
|
}
|
|
1313
1313
|
throw new Error(
|
|
1314
1314
|
`Workflow ${workflowId} did not complete within ${timeoutMs / 1e3 / 60} minutes`
|
|
@@ -2094,26 +2094,82 @@ function declaredVariables(moduleDir) {
|
|
|
2094
2094
|
}
|
|
2095
2095
|
return names;
|
|
2096
2096
|
}
|
|
2097
|
+
var USER_FACING_PLUGIN_OUTPUTS = [
|
|
2098
|
+
"function_url_domain",
|
|
2099
|
+
"frontend_bucket_regional_domain",
|
|
2100
|
+
"frontend_bucket_name"
|
|
2101
|
+
];
|
|
2102
|
+
function rootPluginOutputName(pluginName, output) {
|
|
2103
|
+
return `plugin_${pluginName}_${output}`;
|
|
2104
|
+
}
|
|
2105
|
+
function pluginOutputsFromRoot(pluginName, rootOutputs) {
|
|
2106
|
+
const flat = {};
|
|
2107
|
+
for (const key of USER_FACING_PLUGIN_OUTPUTS) {
|
|
2108
|
+
const value = rootOutputs[rootPluginOutputName(pluginName, key)];
|
|
2109
|
+
if (value !== void 0) flat[key] = value;
|
|
2110
|
+
}
|
|
2111
|
+
return flat;
|
|
2112
|
+
}
|
|
2113
|
+
function declaredOutputs(moduleDir) {
|
|
2114
|
+
const names = /* @__PURE__ */ new Set();
|
|
2115
|
+
let entries;
|
|
2116
|
+
try {
|
|
2117
|
+
entries = readdirSync3(moduleDir, { withFileTypes: true });
|
|
2118
|
+
} catch {
|
|
2119
|
+
return names;
|
|
2120
|
+
}
|
|
2121
|
+
for (const entry of entries) {
|
|
2122
|
+
if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
|
|
2123
|
+
let contents;
|
|
2124
|
+
try {
|
|
2125
|
+
contents = readFileSync7(join10(moduleDir, entry.name), "utf8");
|
|
2126
|
+
} catch {
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
for (const match of contents.matchAll(/^\s*output\s+"([^"]+)"/gm)) {
|
|
2130
|
+
names.add(match[1]);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return names;
|
|
2134
|
+
}
|
|
2097
2135
|
function renderArguments(args, indent) {
|
|
2098
2136
|
const width = Math.max(...args.map(([key]) => key.length));
|
|
2099
2137
|
return args.map(([key, value]) => `${indent}${key.padEnd(width)} = ${value}`).join("\n");
|
|
2100
2138
|
}
|
|
2101
|
-
function renderModuleBlock(pluginName, declared, handler, source) {
|
|
2139
|
+
function renderModuleBlock(pluginName, declared, handler, source, declaredOutputNames) {
|
|
2102
2140
|
const args = standardArguments(pluginName, handler).filter(([key]) => declared.has(key));
|
|
2103
2141
|
const quoted = JSON.stringify(pluginName);
|
|
2104
|
-
|
|
2142
|
+
const outputBlock = (name, description, attr) => [
|
|
2143
|
+
"",
|
|
2144
|
+
`output "${name}" {`,
|
|
2145
|
+
` description = "${description}"`,
|
|
2146
|
+
` value = try(module.plugin_${pluginName}[${quoted}].${attr}, null)`,
|
|
2147
|
+
"}"
|
|
2148
|
+
];
|
|
2149
|
+
const lines = [
|
|
2105
2150
|
`module "plugin_${pluginName}" {`,
|
|
2106
2151
|
` source = "${source}"`,
|
|
2107
2152
|
` for_each = contains(var.enabled_plugins, ${quoted}) ? { ${quoted} = true } : {}`,
|
|
2108
2153
|
"",
|
|
2109
2154
|
renderArguments(args, " "),
|
|
2110
2155
|
"}",
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
]
|
|
2156
|
+
...outputBlock(
|
|
2157
|
+
`plugin_${pluginName}_function_arn`,
|
|
2158
|
+
`Lambda ARN of the ${pluginName} plugin, or null when it is not in enabled_plugins.`,
|
|
2159
|
+
"function_arn"
|
|
2160
|
+
)
|
|
2161
|
+
];
|
|
2162
|
+
for (const output of USER_FACING_PLUGIN_OUTPUTS) {
|
|
2163
|
+
if (!declaredOutputNames.has(output)) continue;
|
|
2164
|
+
lines.push(
|
|
2165
|
+
...outputBlock(
|
|
2166
|
+
rootPluginOutputName(pluginName, output),
|
|
2167
|
+
`${output.replace(/_/g, " ")} of the ${pluginName} plugin (ADR-0018 register step), or null when it is not enabled.`,
|
|
2168
|
+
output
|
|
2169
|
+
)
|
|
2170
|
+
);
|
|
2171
|
+
}
|
|
2172
|
+
return lines.join("\n");
|
|
2117
2173
|
}
|
|
2118
2174
|
var GENERATED_HEADER = `# ---------------------------------------------------------------------------
|
|
2119
2175
|
# GENERATED FILE \u2014 DO NOT EDIT BY HAND.
|
|
@@ -2143,7 +2199,8 @@ function renderGeneratedTerraform(plugins) {
|
|
|
2143
2199
|
p.name,
|
|
2144
2200
|
p.declaredVariables,
|
|
2145
2201
|
p.handler ?? DEFAULT_PLUGIN_HANDLER,
|
|
2146
|
-
p.source ?? THIRD_PARTY_TERRAFORM(p.name)
|
|
2202
|
+
p.source ?? THIRD_PARTY_TERRAFORM(p.name),
|
|
2203
|
+
p.declaredOutputs ?? /* @__PURE__ */ new Set()
|
|
2147
2204
|
)
|
|
2148
2205
|
);
|
|
2149
2206
|
return `${GENERATED_HEADER}
|
|
@@ -2165,6 +2222,7 @@ function syncPluginTerraform(cwd) {
|
|
|
2165
2222
|
return {
|
|
2166
2223
|
name,
|
|
2167
2224
|
declaredVariables: declaredVariables(moduleDir),
|
|
2225
|
+
declaredOutputs: declaredOutputs(moduleDir),
|
|
2168
2226
|
source: pluginModuleSource(cwd, name)
|
|
2169
2227
|
};
|
|
2170
2228
|
});
|
|
@@ -2862,7 +2920,7 @@ var AwsAdapter = class {
|
|
|
2862
2920
|
const code = err.Code;
|
|
2863
2921
|
if (code === "OperationAborted" && attempt < maxAttempts) {
|
|
2864
2922
|
log.info(` Waiting for S3 to release "${bucketName}"... (${attempt}/${maxAttempts})`);
|
|
2865
|
-
await new Promise((
|
|
2923
|
+
await new Promise((resolve19) => setTimeout(resolve19, retryDelayMs));
|
|
2866
2924
|
} else if (code === "OperationAborted") {
|
|
2867
2925
|
return false;
|
|
2868
2926
|
} else {
|
|
@@ -5977,7 +6035,7 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
|
|
|
5977
6035
|
}
|
|
5978
6036
|
|
|
5979
6037
|
// src/commands/plugin.ts
|
|
5980
|
-
import { Command as
|
|
6038
|
+
import { Command as Command21 } from "commander";
|
|
5981
6039
|
|
|
5982
6040
|
// src/commands/plugin-create.ts
|
|
5983
6041
|
import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
|
|
@@ -6135,6 +6193,22 @@ var RouteDefSchema = z6.object({
|
|
|
6135
6193
|
});
|
|
6136
6194
|
}
|
|
6137
6195
|
});
|
|
6196
|
+
var USER_PATH_SEGMENT = /^[a-z][a-z0-9_-]*$/;
|
|
6197
|
+
var HANDLER_IMPORT_PATH = /^[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)+$/;
|
|
6198
|
+
var REL_DIR = /^[\w][\w./-]*$/;
|
|
6199
|
+
var NON_EMPTY_GROUP = "required_group must be a non-empty Cognito group name.";
|
|
6200
|
+
var UserIngressSchema = z6.object({
|
|
6201
|
+
path: z6.string().regex(USER_PATH_SEGMENT, "must be a single lowercase path segment, e.g. api").default("api"),
|
|
6202
|
+
required_group: z6.string().min(1, NON_EMPTY_GROUP),
|
|
6203
|
+
handler: z6.string().regex(HANDLER_IMPORT_PATH, "must be a dotted import path, e.g. 'ideation.app.handler'")
|
|
6204
|
+
}).strict();
|
|
6205
|
+
var UserFrontendSchema = z6.object({
|
|
6206
|
+
dir: z6.string().regex(
|
|
6207
|
+
REL_DIR,
|
|
6208
|
+
"must be a repo-relative path with no leading slash or traversal, e.g. web/dist"
|
|
6209
|
+
),
|
|
6210
|
+
required_group: z6.string().min(1, NON_EMPTY_GROUP)
|
|
6211
|
+
}).strict();
|
|
6138
6212
|
var PluginManifestSchema = z6.object({
|
|
6139
6213
|
name: z6.string().regex(/^[a-z][a-z0-9-]*$/, "must be a lowercase kebab-case slug"),
|
|
6140
6214
|
version: z6.string().regex(/^\d+\.\d+\.\d+$/, "must be a full semver, e.g. 1.2.3"),
|
|
@@ -6149,7 +6223,11 @@ var PluginManifestSchema = z6.object({
|
|
|
6149
6223
|
// lib/plugin-terraform-guard.ts. Kept loose deliberately: the authoritative
|
|
6150
6224
|
// schema is the registry's, and this consumer only needs to count them.
|
|
6151
6225
|
event_subscriptions: z6.array(z6.object({ source: z6.string(), detail_type: z6.string() }).passthrough()).default([]),
|
|
6152
|
-
required_core_version: z6.string().default(">=0.0.0")
|
|
6226
|
+
required_core_version: z6.string().default(">=0.0.0"),
|
|
6227
|
+
// ADR-0018 user-facing surfaces. Optional: a plugin without them is an
|
|
6228
|
+
// ordinary (data/event/CRUD) plugin.
|
|
6229
|
+
user_ingress: UserIngressSchema.optional(),
|
|
6230
|
+
user_frontend: UserFrontendSchema.optional()
|
|
6153
6231
|
}).superRefine((manifest, ctx) => {
|
|
6154
6232
|
const tableNames = new Set(manifest.tables.map((t) => t.name));
|
|
6155
6233
|
for (const route of manifest.api_routes) {
|
|
@@ -6817,6 +6895,28 @@ async function runPluginInstall(target, options, deps) {
|
|
|
6817
6895
|
console.log(chalk15.dim(` git push`));
|
|
6818
6896
|
console.log(chalk15.dim(` biffo deploy <environment> --app-only
|
|
6819
6897
|
`));
|
|
6898
|
+
if (manifest.user_ingress || manifest.user_frontend) {
|
|
6899
|
+
const group = manifest.user_ingress?.required_group ?? manifest.user_frontend?.required_group;
|
|
6900
|
+
console.log(chalk15.bold(` "${pluginName}" is a user-facing plugin (ADR-0018).`));
|
|
6901
|
+
console.log(
|
|
6902
|
+
` It serves an authenticated${group ? ` ${group}-gated` : ""} app at <base>/${pluginName}/ and an api at <base>/${pluginName}/api/*.`
|
|
6903
|
+
);
|
|
6904
|
+
console.log(
|
|
6905
|
+
" Two more steps route it onto the shared CloudFront (apply is pipeline-only):\n"
|
|
6906
|
+
);
|
|
6907
|
+
console.log(
|
|
6908
|
+
" 1. After the deploy above applies, register its CDN origins from the plugin module outputs, then redeploy so the CDN gains its behaviours:"
|
|
6909
|
+
);
|
|
6910
|
+
console.log(chalk15.dim(` biffo plugin wire ${pluginName} <environment>`));
|
|
6911
|
+
console.log(chalk15.dim(` git push && biffo deploy <environment>`));
|
|
6912
|
+
console.log(" 2. Build and sync its frontend to the bucket that first apply created");
|
|
6913
|
+
console.log(
|
|
6914
|
+
chalk15.dim(
|
|
6915
|
+
` (the plugin's own deploy workflow does this \u2014 see ${relTargetDir}/.github/workflows/).
|
|
6916
|
+
`
|
|
6917
|
+
)
|
|
6918
|
+
);
|
|
6919
|
+
}
|
|
6820
6920
|
} finally {
|
|
6821
6921
|
source.cleanup();
|
|
6822
6922
|
}
|
|
@@ -7278,24 +7378,187 @@ function printDryRun6(entry, currentVersion) {
|
|
|
7278
7378
|
`);
|
|
7279
7379
|
}
|
|
7280
7380
|
|
|
7381
|
+
// src/commands/plugin-wire.ts
|
|
7382
|
+
import { existsSync as existsSync30, readFileSync as readFileSync23 } from "fs";
|
|
7383
|
+
import { resolve as resolve17 } from "path";
|
|
7384
|
+
import chalk20 from "chalk";
|
|
7385
|
+
import { Command as Command20 } from "commander";
|
|
7386
|
+
|
|
7387
|
+
// src/lib/plugin-origin.ts
|
|
7388
|
+
import { existsSync as existsSync29, readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
|
|
7389
|
+
import { join as join30 } from "path";
|
|
7390
|
+
var PLUGIN_API_TFVARS = "plugin-apis.auto.tfvars.json";
|
|
7391
|
+
var SIBLINGS_TFVARS = "siblings.auto.tfvars.json";
|
|
7392
|
+
function upsertPluginApiOrigin(existing, entry) {
|
|
7393
|
+
return [...existing.filter((p) => p.name !== entry.name), entry];
|
|
7394
|
+
}
|
|
7395
|
+
function serializePluginApiRegistry(origins) {
|
|
7396
|
+
return JSON.stringify({ plugin_api_origins: origins }, null, 2) + "\n";
|
|
7397
|
+
}
|
|
7398
|
+
function readArray(path, key) {
|
|
7399
|
+
if (!existsSync29(path)) return [];
|
|
7400
|
+
try {
|
|
7401
|
+
const parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
7402
|
+
const arr = parsed[key];
|
|
7403
|
+
return Array.isArray(arr) ? arr : [];
|
|
7404
|
+
} catch {
|
|
7405
|
+
return [];
|
|
7406
|
+
}
|
|
7407
|
+
}
|
|
7408
|
+
function registerUserFacingPlugin(cwd, environment, reg) {
|
|
7409
|
+
const envDir = join30(cwd, "infra", "environments", environment);
|
|
7410
|
+
const apiPath = join30(envDir, PLUGIN_API_TFVARS);
|
|
7411
|
+
const siblingPath = join30(envDir, SIBLINGS_TFVARS);
|
|
7412
|
+
const apis = upsertPluginApiOrigin(readArray(apiPath, "plugin_api_origins"), {
|
|
7413
|
+
name: reg.name,
|
|
7414
|
+
function_url_domain: reg.functionUrlDomain
|
|
7415
|
+
});
|
|
7416
|
+
writeFileSync9(apiPath, serializePluginApiRegistry(apis));
|
|
7417
|
+
const siblings = upsertSiblingOrigin(readArray(siblingPath, "sibling_origins"), {
|
|
7418
|
+
name: reg.name,
|
|
7419
|
+
bucket_regional_domain: reg.bucketRegionalDomain
|
|
7420
|
+
});
|
|
7421
|
+
writeFileSync9(siblingPath, serializeRegistry(siblings));
|
|
7422
|
+
return [
|
|
7423
|
+
`infra/environments/${environment}/${PLUGIN_API_TFVARS}`,
|
|
7424
|
+
`infra/environments/${environment}/${SIBLINGS_TFVARS}`
|
|
7425
|
+
];
|
|
7426
|
+
}
|
|
7427
|
+
var REQUIRED_PLUGIN_OUTPUTS = [
|
|
7428
|
+
"function_url_domain",
|
|
7429
|
+
"frontend_bucket_regional_domain",
|
|
7430
|
+
"frontend_bucket_name"
|
|
7431
|
+
];
|
|
7432
|
+
function wireUserFacingPluginFromOutputs(cwd, environment, name, outputs) {
|
|
7433
|
+
const missing = REQUIRED_PLUGIN_OUTPUTS.filter((k) => !outputs[k]?.trim());
|
|
7434
|
+
if (missing.length > 0) {
|
|
7435
|
+
throw new Error(
|
|
7436
|
+
`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.`
|
|
7437
|
+
);
|
|
7438
|
+
}
|
|
7439
|
+
const registeredPaths = registerUserFacingPlugin(cwd, environment, {
|
|
7440
|
+
name,
|
|
7441
|
+
functionUrlDomain: outputs["function_url_domain"],
|
|
7442
|
+
bucketRegionalDomain: outputs["frontend_bucket_regional_domain"]
|
|
7443
|
+
});
|
|
7444
|
+
return { registeredPaths, frontendBucketName: outputs["frontend_bucket_name"] };
|
|
7445
|
+
}
|
|
7446
|
+
|
|
7447
|
+
// src/commands/plugin-wire.ts
|
|
7448
|
+
var VALID_ENVIRONMENTS2 = ["dev", "staging", "prod"];
|
|
7449
|
+
async function runPluginWire(pluginName, environment, config, deps, cwd = process.cwd()) {
|
|
7450
|
+
const awsConfig2 = config.cloud.config;
|
|
7451
|
+
const stateBucket = awsConfig2.tf_state_bucket ?? `${config.project.name}-terraform-state-${awsConfig2.account_id}`;
|
|
7452
|
+
const stateKey = `${environment}/terraform.tfstate`;
|
|
7453
|
+
log.info(`Reading Terraform outputs from s3://${stateBucket}/${stateKey}...`);
|
|
7454
|
+
const outputs = await deps.readOutputs(stateBucket, stateKey);
|
|
7455
|
+
const pluginOutputs = pluginOutputsFromRoot(pluginName, outputs);
|
|
7456
|
+
const targets = wireUserFacingPluginFromOutputs(cwd, environment, pluginName, pluginOutputs);
|
|
7457
|
+
console.log(chalk20.bold(`
|
|
7458
|
+
Registered "${pluginName}" CDN origins for ${environment}
|
|
7459
|
+
`));
|
|
7460
|
+
for (const path of targets.registeredPaths) console.log(` ${chalk20.green("~")} ${path}`);
|
|
7461
|
+
console.log(
|
|
7462
|
+
"\n Commit these and redeploy so the CDN gains its behaviours (apply is pipeline-only):"
|
|
7463
|
+
);
|
|
7464
|
+
console.log(chalk20.dim(` git add ${targets.registeredPaths.join(" ")}`));
|
|
7465
|
+
console.log(chalk20.dim(` git commit -m "infra(cdn): route ${pluginName} (${environment})"`));
|
|
7466
|
+
console.log(chalk20.dim(` git push && biffo deploy ${environment}
|
|
7467
|
+
`));
|
|
7468
|
+
console.log(
|
|
7469
|
+
` Then build and sync the frontend to its bucket: ${chalk20.bold(targets.frontendBucketName)}`
|
|
7470
|
+
);
|
|
7471
|
+
console.log(
|
|
7472
|
+
chalk20.dim(
|
|
7473
|
+
" (the plugin's own deploy workflow does this \u2014 it reads the bucket from the same outputs.)\n"
|
|
7474
|
+
)
|
|
7475
|
+
);
|
|
7476
|
+
return targets;
|
|
7477
|
+
}
|
|
7478
|
+
var pluginWireCommand = new Command20("wire").description(
|
|
7479
|
+
"Register a user-facing plugin's CDN origins from its deployed outputs (ADR-0018 register step): biffo plugin wire <name> <environment>"
|
|
7480
|
+
).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(
|
|
7481
|
+
async (name, environment, options) => {
|
|
7482
|
+
if (!VALID_ENVIRONMENTS2.includes(environment)) {
|
|
7483
|
+
log.error(
|
|
7484
|
+
`Unknown environment: ${environment}. Must be one of: ${VALID_ENVIRONMENTS2.join(", ")}`
|
|
7485
|
+
);
|
|
7486
|
+
process.exit(1);
|
|
7487
|
+
}
|
|
7488
|
+
const config = await resolveConfig4(options);
|
|
7489
|
+
const aws = new AwsAdapter(config);
|
|
7490
|
+
try {
|
|
7491
|
+
await runPluginWire(name, environment, config, {
|
|
7492
|
+
readOutputs: (bucket, key) => aws.readTerraformOutputs(bucket, key)
|
|
7493
|
+
});
|
|
7494
|
+
} catch (err) {
|
|
7495
|
+
log.error(err.message);
|
|
7496
|
+
process.exit(1);
|
|
7497
|
+
}
|
|
7498
|
+
}
|
|
7499
|
+
);
|
|
7500
|
+
async function resolveConfig4(options) {
|
|
7501
|
+
if (options.config) {
|
|
7502
|
+
const raw = JSON.parse(readFileSync23(resolve17(options.config), "utf8"));
|
|
7503
|
+
const result = BiffoConfigSchema.safeParse(raw);
|
|
7504
|
+
if (!result.success) {
|
|
7505
|
+
log.error(`Invalid config at ${options.config}:`);
|
|
7506
|
+
result.error.issues.forEach((i) => log.error(` ${i.path.join(".")} \u2014 ${i.message}`));
|
|
7507
|
+
process.exit(1);
|
|
7508
|
+
}
|
|
7509
|
+
return result.data;
|
|
7510
|
+
}
|
|
7511
|
+
if (options.project) {
|
|
7512
|
+
const cfg = loadProjectConfig(options.project);
|
|
7513
|
+
if (!cfg) {
|
|
7514
|
+
log.error(
|
|
7515
|
+
`Project "${options.project}" not found in ~/.biffo/projects/. Run biffo init first or pass --config <path>.`
|
|
7516
|
+
);
|
|
7517
|
+
process.exit(1);
|
|
7518
|
+
}
|
|
7519
|
+
return cfg;
|
|
7520
|
+
}
|
|
7521
|
+
const localConfigPath = resolve17(process.cwd(), "biffo.config.json");
|
|
7522
|
+
if (existsSync30(localConfigPath)) {
|
|
7523
|
+
const raw = JSON.parse(readFileSync23(localConfigPath, "utf8"));
|
|
7524
|
+
const result = BiffoConfigSchema.safeParse(raw);
|
|
7525
|
+
if (result.success) return result.data;
|
|
7526
|
+
if (isTemplatePlaceholderConfig(raw)) {
|
|
7527
|
+
log.warn(
|
|
7528
|
+
`Ignoring ${localConfigPath} \u2014 it is the unsubstituted Biffo template placeholder, not this project's config.`
|
|
7529
|
+
);
|
|
7530
|
+
} else {
|
|
7531
|
+
log.error(`Invalid config at ${localConfigPath}:`);
|
|
7532
|
+
result.error.issues.forEach((i) => log.error(` ${i.path.join(".")} \u2014 ${i.message}`));
|
|
7533
|
+
log.error("Refusing to fall back to a saved project while a local config file is present.");
|
|
7534
|
+
process.exit(1);
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7537
|
+
log.error(
|
|
7538
|
+
"No config found. Run inside a Biffo project, or pass --project <name> / --config <path>."
|
|
7539
|
+
);
|
|
7540
|
+
return process.exit(1);
|
|
7541
|
+
}
|
|
7542
|
+
|
|
7281
7543
|
// src/commands/plugin.ts
|
|
7282
|
-
var pluginCommand = new
|
|
7544
|
+
var pluginCommand = new Command21("plugin").description("Manage Biffo plugins");
|
|
7283
7545
|
pluginCommand.addCommand(pluginCreateCommand);
|
|
7284
7546
|
pluginCommand.addCommand(pluginListCommand);
|
|
7285
7547
|
pluginCommand.addCommand(pluginInstallCommand);
|
|
7548
|
+
pluginCommand.addCommand(pluginWireCommand);
|
|
7286
7549
|
pluginCommand.addCommand(pluginUninstallCommand);
|
|
7287
7550
|
pluginCommand.addCommand(pluginUpgradeCommand);
|
|
7288
7551
|
pluginCommand.addCommand(pluginSyncMigrationsCommand);
|
|
7289
7552
|
pluginCommand.addCommand(pluginInfoCommand);
|
|
7290
7553
|
|
|
7291
7554
|
// src/commands/sibling.ts
|
|
7292
|
-
import { Command as
|
|
7555
|
+
import { Command as Command23 } from "commander";
|
|
7293
7556
|
|
|
7294
7557
|
// src/commands/sibling-check-identity.ts
|
|
7295
|
-
import { existsSync as
|
|
7296
|
-
import { resolve as
|
|
7297
|
-
import
|
|
7298
|
-
import { Command as
|
|
7558
|
+
import { existsSync as existsSync31, readFileSync as readFileSync24 } from "fs";
|
|
7559
|
+
import { resolve as resolve18 } from "path";
|
|
7560
|
+
import chalk21 from "chalk";
|
|
7561
|
+
import { Command as Command22 } from "commander";
|
|
7299
7562
|
|
|
7300
7563
|
// src/lib/sibling-identity-check.ts
|
|
7301
7564
|
function checkSiblingIdentity(envs) {
|
|
@@ -7345,19 +7608,19 @@ function checkSiblingIdentity(envs) {
|
|
|
7345
7608
|
}
|
|
7346
7609
|
|
|
7347
7610
|
// src/commands/sibling-check-identity.ts
|
|
7348
|
-
var
|
|
7611
|
+
var VALID_ENVIRONMENTS3 = ["dev", "staging", "prod"];
|
|
7349
7612
|
var SIBLING_CORE_POOL_VAR = "CORE_COGNITO_USER_POOL_ID";
|
|
7350
|
-
var siblingCheckIdentityCommand = new
|
|
7613
|
+
var siblingCheckIdentityCommand = new Command22("check-identity").description(
|
|
7351
7614
|
"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."
|
|
7352
7615
|
).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) => {
|
|
7353
|
-
if (options.env && !
|
|
7616
|
+
if (options.env && !VALID_ENVIRONMENTS3.includes(options.env)) {
|
|
7354
7617
|
log.error(
|
|
7355
|
-
`Unknown environment: ${options.env}. Must be one of: ${
|
|
7618
|
+
`Unknown environment: ${options.env}. Must be one of: ${VALID_ENVIRONMENTS3.join(", ")}`
|
|
7356
7619
|
);
|
|
7357
7620
|
process.exit(1);
|
|
7358
7621
|
}
|
|
7359
|
-
const environments = options.env ? [options.env] : [...
|
|
7360
|
-
const config = await
|
|
7622
|
+
const environments = options.env ? [options.env] : [...VALID_ENVIRONMENTS3];
|
|
7623
|
+
const config = await resolveConfig5(options);
|
|
7361
7624
|
const { org, repo } = config.source_control.config;
|
|
7362
7625
|
const awsConfig2 = config.cloud.config;
|
|
7363
7626
|
const stateBucket = awsConfig2.tf_state_bucket ?? `${config.project.name}-terraform-state-${awsConfig2.account_id}`;
|
|
@@ -7367,7 +7630,7 @@ var siblingCheckIdentityCommand = new Command21("check-identity").description(
|
|
|
7367
7630
|
github: new GitHubAdapter(token),
|
|
7368
7631
|
fetchIdentityDoc: fetchPublishedIdentity
|
|
7369
7632
|
};
|
|
7370
|
-
console.log(
|
|
7633
|
+
console.log(chalk21.bold(`
|
|
7371
7634
|
Biffo \u2014 Sibling identity check (${config.project.name})
|
|
7372
7635
|
`));
|
|
7373
7636
|
let result;
|
|
@@ -7454,16 +7717,16 @@ function printIdentityReport(result) {
|
|
|
7454
7717
|
}
|
|
7455
7718
|
if (result.ok) {
|
|
7456
7719
|
log.success(
|
|
7457
|
-
|
|
7720
|
+
chalk21.green(
|
|
7458
7721
|
"\u2713 identity consistent \u2014 every published document and sibling backend matches the live pool"
|
|
7459
7722
|
)
|
|
7460
7723
|
);
|
|
7461
7724
|
return;
|
|
7462
7725
|
}
|
|
7463
|
-
log.error(
|
|
7726
|
+
log.error(chalk21.red(`\u2718 ${String(result.findings.length)} identity drift finding(s):`));
|
|
7464
7727
|
for (const f of result.findings) {
|
|
7465
7728
|
console.error(
|
|
7466
|
-
|
|
7729
|
+
chalk21.red(
|
|
7467
7730
|
` [${f.environment}] ${f.subject}: ${FINDING_LABEL[f.kind]}
|
|
7468
7731
|
expected (live pool): ${f.expected}
|
|
7469
7732
|
found: ${f.actual ?? "(unset)"}`
|
|
@@ -7484,9 +7747,9 @@ async function fetchPublishedIdentity(portalUrl) {
|
|
|
7484
7747
|
return null;
|
|
7485
7748
|
}
|
|
7486
7749
|
}
|
|
7487
|
-
async function
|
|
7750
|
+
async function resolveConfig5(options) {
|
|
7488
7751
|
if (options.config) {
|
|
7489
|
-
const raw = JSON.parse(
|
|
7752
|
+
const raw = JSON.parse(readFileSync24(resolve18(options.config), "utf8"));
|
|
7490
7753
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
7491
7754
|
if (!result.success) {
|
|
7492
7755
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -7505,9 +7768,9 @@ async function resolveConfig4(options) {
|
|
|
7505
7768
|
}
|
|
7506
7769
|
return cfg;
|
|
7507
7770
|
}
|
|
7508
|
-
const localConfigPath =
|
|
7509
|
-
if (
|
|
7510
|
-
const raw = JSON.parse(
|
|
7771
|
+
const localConfigPath = resolve18(process.cwd(), "biffo.config.json");
|
|
7772
|
+
if (existsSync31(localConfigPath)) {
|
|
7773
|
+
const raw = JSON.parse(readFileSync24(localConfigPath, "utf8"));
|
|
7511
7774
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
7512
7775
|
if (result.success) return result.data;
|
|
7513
7776
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -7543,14 +7806,14 @@ async function resolveConfig4(options) {
|
|
|
7543
7806
|
}
|
|
7544
7807
|
|
|
7545
7808
|
// src/commands/sibling.ts
|
|
7546
|
-
var siblingCommand = new
|
|
7809
|
+
var siblingCommand = new Command23("sibling").description(
|
|
7547
7810
|
"Create and manage sibling apps that share a Biffo core project (ADR-0007)"
|
|
7548
7811
|
);
|
|
7549
7812
|
siblingCommand.addCommand(siblingCreateCommand);
|
|
7550
7813
|
siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
7551
7814
|
|
|
7552
7815
|
// src/commands/check.ts
|
|
7553
|
-
import { Command as
|
|
7816
|
+
import { Command as Command24 } from "commander";
|
|
7554
7817
|
|
|
7555
7818
|
// src/scripts/check-core-ownership.ts
|
|
7556
7819
|
import { execa as execa5 } from "execa";
|
|
@@ -7576,8 +7839,8 @@ async function runOwnershipCheck(argv) {
|
|
|
7576
7839
|
const { stdout } = await execa5("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
7577
7840
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
7578
7841
|
if (messageFile) {
|
|
7579
|
-
const { readFileSync:
|
|
7580
|
-
if (
|
|
7842
|
+
const { readFileSync: readFileSync26, existsSync: existsSync33 } = await import("fs");
|
|
7843
|
+
if (existsSync33(messageFile)) commitMessage = readFileSync26(messageFile, "utf8");
|
|
7581
7844
|
}
|
|
7582
7845
|
} else {
|
|
7583
7846
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -7681,8 +7944,8 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
7681
7944
|
import { execa as execa6 } from "execa";
|
|
7682
7945
|
|
|
7683
7946
|
// src/lib/plugin-terraform-guard.ts
|
|
7684
|
-
import { existsSync as
|
|
7685
|
-
import { dirname as dirname9, join as
|
|
7947
|
+
import { existsSync as existsSync32, readFileSync as readFileSync25, readdirSync as readdirSync12 } from "fs";
|
|
7948
|
+
import { dirname as dirname9, join as join31, relative as relative6, sep as sep3 } from "path";
|
|
7686
7949
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
7687
7950
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
7688
7951
|
function findPluginManifests(root) {
|
|
@@ -7697,9 +7960,9 @@ function findPluginManifests(root) {
|
|
|
7697
7960
|
for (const entry of entries) {
|
|
7698
7961
|
if (entry.isDirectory()) {
|
|
7699
7962
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
7700
|
-
walk(
|
|
7963
|
+
walk(join31(dir, entry.name));
|
|
7701
7964
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
7702
|
-
found.push(relative6(root,
|
|
7965
|
+
found.push(relative6(root, join31(dir, entry.name)).split(sep3).join("/"));
|
|
7703
7966
|
}
|
|
7704
7967
|
}
|
|
7705
7968
|
};
|
|
@@ -7709,7 +7972,7 @@ function findPluginManifests(root) {
|
|
|
7709
7972
|
function readSubscriptions(absManifestPath) {
|
|
7710
7973
|
let parsed;
|
|
7711
7974
|
try {
|
|
7712
|
-
parsed = JSON.parse(
|
|
7975
|
+
parsed = JSON.parse(readFileSync25(absManifestPath, "utf8"));
|
|
7713
7976
|
} catch {
|
|
7714
7977
|
return null;
|
|
7715
7978
|
}
|
|
@@ -7724,14 +7987,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
7724
7987
|
}
|
|
7725
7988
|
function checkPluginTerraform(root) {
|
|
7726
7989
|
const violations = [];
|
|
7727
|
-
const coreManifest =
|
|
7990
|
+
const coreManifest = existsSync32(join31(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
7728
7991
|
for (const manifest of findPluginManifests(root)) {
|
|
7729
7992
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
7730
|
-
const absManifest =
|
|
7993
|
+
const absManifest = join31(root, manifest);
|
|
7731
7994
|
const subscriptions = readSubscriptions(absManifest);
|
|
7732
7995
|
if (subscriptions === null) continue;
|
|
7733
7996
|
const pluginDir2 = dirname9(absManifest);
|
|
7734
|
-
if (
|
|
7997
|
+
if (existsSync32(join31(pluginDir2, "terraform"))) continue;
|
|
7735
7998
|
const relPluginDir = relative6(root, pluginDir2).split(sep3).join("/");
|
|
7736
7999
|
violations.push({
|
|
7737
8000
|
manifest,
|
|
@@ -7850,7 +8113,7 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
7850
8113
|
}
|
|
7851
8114
|
|
|
7852
8115
|
// src/commands/check.ts
|
|
7853
|
-
var checkCommand = new
|
|
8116
|
+
var checkCommand = new Command24("check").description(
|
|
7854
8117
|
"Repo guards (ownership, release subject, plugin terraform) \u2014 run in CI and git hooks"
|
|
7855
8118
|
);
|
|
7856
8119
|
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 () => {
|
|
@@ -7870,17 +8133,17 @@ function rawArgsAfter(subcommand) {
|
|
|
7870
8133
|
// src/commands/teardown.ts
|
|
7871
8134
|
import { execSync as execSync7 } from "child_process";
|
|
7872
8135
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand3, STSClient as STSClient3 } from "@aws-sdk/client-sts";
|
|
7873
|
-
import
|
|
7874
|
-
import { Command as
|
|
8136
|
+
import chalk22 from "chalk";
|
|
8137
|
+
import { Command as Command25 } from "commander";
|
|
7875
8138
|
import inquirer8 from "inquirer";
|
|
7876
|
-
var teardownCommand = new
|
|
8139
|
+
var teardownCommand = new Command25("teardown").description(
|
|
7877
8140
|
"Destroy all infrastructure then remove the repo, IAM role, and state bucket \u2014 single command"
|
|
7878
8141
|
).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(
|
|
7879
8142
|
"--confirm <name>",
|
|
7880
8143
|
"Pre-confirm by supplying the project name (the scriptable form of the typed confirmation)"
|
|
7881
8144
|
).option("-y, --yes", "Skip the typed project-name confirmation entirely").action(
|
|
7882
8145
|
async (options) => {
|
|
7883
|
-
console.log(
|
|
8146
|
+
console.log(chalk22.bold("\n Biffo \u2014 Teardown\n"));
|
|
7884
8147
|
const githubToken = resolveGithubToken4();
|
|
7885
8148
|
const sts = new STSClient3({});
|
|
7886
8149
|
const { Account: accountId } = await sts.send(new GetCallerIdentityCommand3({}));
|
|
@@ -7902,7 +8165,7 @@ var teardownCommand = new Command24("teardown").description(
|
|
|
7902
8165
|
adminEmail = session.config.admin?.email ?? "noop@example.com";
|
|
7903
8166
|
adminUsername = session.config.admin?.username ?? "noop";
|
|
7904
8167
|
domain = session.config.project.domain ?? "";
|
|
7905
|
-
console.log(
|
|
8168
|
+
console.log(chalk22.yellow(" Loaded session for: ") + chalk22.bold(projectName));
|
|
7906
8169
|
if (org && repo) console.log(` Repository: ${org}/${repo}`);
|
|
7907
8170
|
console.log();
|
|
7908
8171
|
} else if (savedConfig) {
|
|
@@ -7915,7 +8178,7 @@ var teardownCommand = new Command24("teardown").description(
|
|
|
7915
8178
|
adminEmail = savedConfig.admin.email;
|
|
7916
8179
|
adminUsername = savedConfig.admin.username;
|
|
7917
8180
|
domain = resolveDnsConfig(savedConfig).domain;
|
|
7918
|
-
console.log(
|
|
8181
|
+
console.log(chalk22.yellow(" Loaded config for: ") + chalk22.bold(projectName));
|
|
7919
8182
|
console.log(` Repository: ${org}/${repo}`);
|
|
7920
8183
|
console.log();
|
|
7921
8184
|
} else {
|
|
@@ -7986,35 +8249,35 @@ var teardownCommand = new Command24("teardown").description(
|
|
|
7986
8249
|
throw err;
|
|
7987
8250
|
}
|
|
7988
8251
|
}
|
|
7989
|
-
console.log(
|
|
8252
|
+
console.log(chalk22.red.bold(" This will permanently delete:\n"));
|
|
7990
8253
|
if (deployedEnvs.length > 0 || hasGlobal) {
|
|
7991
|
-
console.log(
|
|
8254
|
+
console.log(chalk22.red(" Infrastructure (via GitHub Actions terraform destroy):"));
|
|
7992
8255
|
for (const env of deployedEnvs) {
|
|
7993
8256
|
console.log(
|
|
7994
|
-
` ${
|
|
8257
|
+
` ${chalk22.red("\u2717")} ${env} \u2014 VPC, RDS, Lambda, Cognito, CloudFront, EventBridge`
|
|
7995
8258
|
);
|
|
7996
8259
|
}
|
|
7997
8260
|
if (hasGlobal) {
|
|
7998
|
-
console.log(` ${
|
|
8261
|
+
console.log(` ${chalk22.red("\u2717")} global \u2014 Route 53 hosted zone, ACM certificate`);
|
|
7999
8262
|
}
|
|
8000
8263
|
console.log();
|
|
8001
8264
|
}
|
|
8002
8265
|
for (const line of formatSiblingPlan(siblings, options.skipDestroy === true)) {
|
|
8003
8266
|
console.log(line);
|
|
8004
8267
|
}
|
|
8005
|
-
console.log(
|
|
8006
|
-
console.log(` ${
|
|
8268
|
+
console.log(chalk22.red(" Biffo resources:"));
|
|
8269
|
+
console.log(` ${chalk22.red("\u2717")} GitHub repository ${chalk22.bold(`${org}/${repo}`)}`);
|
|
8007
8270
|
console.log(
|
|
8008
|
-
` ${
|
|
8271
|
+
` ${chalk22.red("\u2717")} IAM role ${chalk22.bold(`biffo-github-actions-${projectName}`)}`
|
|
8009
8272
|
);
|
|
8010
8273
|
console.log(
|
|
8011
|
-
` ${
|
|
8274
|
+
` ${chalk22.red("\u2717")} S3 bucket ${chalk22.bold(stateBucket)} (all versions)`
|
|
8012
8275
|
);
|
|
8013
|
-
console.log(` ${
|
|
8276
|
+
console.log(` ${chalk22.red("\u2717")} Local session file`);
|
|
8014
8277
|
if (options.skipDestroy) {
|
|
8015
8278
|
console.log();
|
|
8016
8279
|
console.log(
|
|
8017
|
-
|
|
8280
|
+
chalk22.yellow(
|
|
8018
8281
|
" --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."
|
|
8019
8282
|
)
|
|
8020
8283
|
);
|
|
@@ -8202,30 +8465,30 @@ async function assertSiblingsAreDestroyable(github, siblings) {
|
|
|
8202
8465
|
}
|
|
8203
8466
|
function formatSiblingPlan(siblings, skipDestroy) {
|
|
8204
8467
|
if (siblings.length === 0) return [];
|
|
8205
|
-
const lines = [
|
|
8468
|
+
const lines = [chalk22.red(` Sibling apps (${siblings.length}) \u2014 ADR-0007:`)];
|
|
8206
8469
|
for (const s of siblings) {
|
|
8207
8470
|
const envs = s.environments.join(", ");
|
|
8208
8471
|
if (s.repoState === "gone") {
|
|
8209
8472
|
lines.push(
|
|
8210
|
-
` ${
|
|
8473
|
+
` ${chalk22.yellow("!")} ${chalk22.bold(`${s.org}/${s.repo}`)} \u2014 repo already deleted; its ${envs} infrastructure CANNOT be destroyed and will be left standing`
|
|
8211
8474
|
);
|
|
8212
8475
|
continue;
|
|
8213
8476
|
}
|
|
8214
8477
|
lines.push(
|
|
8215
|
-
` ${
|
|
8478
|
+
` ${chalk22.red("\u2717")} GitHub repository ${chalk22.bold(`${s.org}/${s.repo}`)} ` + (s.pathPrefix === ROOT_SIBLING_NAME ? "(the application, routed at /)" : `(routed at /${s.pathPrefix})`)
|
|
8216
8479
|
);
|
|
8217
8480
|
lines.push(
|
|
8218
|
-
` ${
|
|
8481
|
+
` ${chalk22.red("\u2717")} ${skipDestroy ? "infrastructure NOT destroyed (--skip-destroy)" : `${envs} infrastructure \u2014 S3 site bucket, Lambda, API Gateway`}`
|
|
8219
8482
|
);
|
|
8220
8483
|
lines.push(
|
|
8221
|
-
` ${
|
|
8484
|
+
` ${chalk22.red("\u2717")} IAM role ${chalk22.bold(`biffo-github-actions-${s.projectName}`)}`
|
|
8222
8485
|
);
|
|
8223
8486
|
lines.push(
|
|
8224
|
-
` ${
|
|
8487
|
+
` ${chalk22.red("\u2717")} S3 bucket ${chalk22.bold(`${s.projectName}-terraform-state-${s.accountId}`)}`
|
|
8225
8488
|
);
|
|
8226
8489
|
if (s.pendingRegistrationPr !== void 0) {
|
|
8227
8490
|
lines.push(
|
|
8228
|
-
|
|
8491
|
+
chalk22.dim(` registration PR #${s.pendingRegistrationPr} is still open \u2014 never routed`)
|
|
8229
8492
|
);
|
|
8230
8493
|
}
|
|
8231
8494
|
}
|
|
@@ -8263,7 +8526,7 @@ async function confirmTeardown(projectName, options) {
|
|
|
8263
8526
|
{
|
|
8264
8527
|
type: "input",
|
|
8265
8528
|
name: "confirm",
|
|
8266
|
-
message: `Type ${
|
|
8529
|
+
message: `Type ${chalk22.bold(projectName)} to confirm:`
|
|
8267
8530
|
}
|
|
8268
8531
|
]);
|
|
8269
8532
|
return confirm === projectName;
|
|
@@ -8281,7 +8544,7 @@ function resolveGithubToken4() {
|
|
|
8281
8544
|
}
|
|
8282
8545
|
|
|
8283
8546
|
// src/index.ts
|
|
8284
|
-
var program = new
|
|
8547
|
+
var program = new Command26();
|
|
8285
8548
|
function cliVersion() {
|
|
8286
8549
|
try {
|
|
8287
8550
|
return getLatestCoreVersion();
|