@biffo/cli 0.295.2 → 0.296.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 +161 -110
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11678,8 +11678,8 @@ async function runOwnershipCheck(argv) {
|
|
|
11678
11678
|
const { stdout } = await execa14("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
11679
11679
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
11680
11680
|
if (messageFile) {
|
|
11681
|
-
const { readFileSync: readFileSync45, existsSync:
|
|
11682
|
-
if (
|
|
11681
|
+
const { readFileSync: readFileSync45, existsSync: existsSync54 } = await import("fs");
|
|
11682
|
+
if (existsSync54(messageFile)) commitMessage = readFileSync45(messageFile, "utf8");
|
|
11683
11683
|
}
|
|
11684
11684
|
} else {
|
|
11685
11685
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -11993,16 +11993,56 @@ async function runEventBridgeLogPermissionCheck() {
|
|
|
11993
11993
|
console.log(`\u2713 EventBridge log permission guard: ${report.summary}`);
|
|
11994
11994
|
}
|
|
11995
11995
|
|
|
11996
|
-
// src/scripts/check-
|
|
11996
|
+
// src/scripts/check-instance-adoption.ts
|
|
11997
|
+
import { existsSync as existsSync43 } from "fs";
|
|
11998
|
+
import { join as join48 } from "path";
|
|
11997
11999
|
import { execa as execa16 } from "execa";
|
|
12000
|
+
async function runInstanceAdoptionCheck(opts = {}) {
|
|
12001
|
+
if (!opts.instanceDir) {
|
|
12002
|
+
console.error(
|
|
12003
|
+
"\u2717 instance-adoption guard: --instance-dir is required \u2014 the real instance tree to check adoption against. There is no self-check default: this repo IS the template, it has no instance tree of its own to examine, and a fabricated one would prove nothing about a real gap."
|
|
12004
|
+
);
|
|
12005
|
+
process.exit(2);
|
|
12006
|
+
}
|
|
12007
|
+
if (!existsSync43(opts.instanceDir)) {
|
|
12008
|
+
console.error(
|
|
12009
|
+
`\u2717 instance-adoption guard: --instance-dir ${opts.instanceDir} does not exist \u2014 cannot tell whether it is adopted, and that is not the same as a clean pass.`
|
|
12010
|
+
);
|
|
12011
|
+
process.exit(2);
|
|
12012
|
+
}
|
|
12013
|
+
const root = (await execa16("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12014
|
+
const theirsDir = opts.theirsDir ?? root;
|
|
12015
|
+
const instanceLabel = opts.instance ?? join48(opts.instanceDir).split("/").filter(Boolean).pop();
|
|
12016
|
+
const report = checkInstanceAdoption(theirsDir, opts.instanceDir);
|
|
12017
|
+
console.log(
|
|
12018
|
+
`examined ${report.examinedInstances} instance (${instanceLabel}) against ${report.registeredPairs} registered pair(s), ${report.applicablePairs} applicable to this instance, against template tree ${theirsDir}`
|
|
12019
|
+
);
|
|
12020
|
+
const unadopted = report.findings.filter((f) => f.status === "unadopted");
|
|
12021
|
+
if (unadopted.length === 0) {
|
|
12022
|
+
console.log(`\u2713 instance-adoption guard (${instanceLabel}): no adoption gaps`);
|
|
12023
|
+
return;
|
|
12024
|
+
}
|
|
12025
|
+
console.error(
|
|
12026
|
+
`\u2717 instance-adoption guard (${instanceLabel}): ${unadopted.length} adoption gap(s) \u2014 shipped but not consumed (#1538/#1570):`
|
|
12027
|
+
);
|
|
12028
|
+
for (const f of unadopted) {
|
|
12029
|
+
console.error(` ${f.pair.id} \u2014 ${f.pair.userFile} does not consume ${f.pair.templateFile}`);
|
|
12030
|
+
console.error(` ${f.pair.remedy}`);
|
|
12031
|
+
}
|
|
12032
|
+
console.error("\nSee biffo-template#1538/#1570/#1609.");
|
|
12033
|
+
process.exit(1);
|
|
12034
|
+
}
|
|
12035
|
+
|
|
12036
|
+
// src/scripts/check-lambda-output.ts
|
|
12037
|
+
import { execa as execa17 } from "execa";
|
|
11998
12038
|
|
|
11999
12039
|
// src/lib/lambda-output-guard.ts
|
|
12000
12040
|
import { readFileSync as readFileSync36 } from "fs";
|
|
12001
|
-
import { join as
|
|
12041
|
+
import { join as join50 } from "path";
|
|
12002
12042
|
|
|
12003
12043
|
// src/lib/terraform-input-guard.ts
|
|
12004
|
-
import { existsSync as
|
|
12005
|
-
import { join as
|
|
12044
|
+
import { existsSync as existsSync44, readdirSync as readdirSync21, readFileSync as readFileSync35, statSync as statSync13 } from "fs";
|
|
12045
|
+
import { join as join49 } from "path";
|
|
12006
12046
|
var GUARDED_SUBCOMMANDS = [
|
|
12007
12047
|
"init",
|
|
12008
12048
|
"plan",
|
|
@@ -12016,7 +12056,7 @@ function stripComments2(source) {
|
|
|
12016
12056
|
return source.split("\n").map((line) => line.replace(/(^|\s)#.*$/, "$1")).join("\n");
|
|
12017
12057
|
}
|
|
12018
12058
|
function vendoredPluginServiceDirs(repoRoot) {
|
|
12019
|
-
const servicesDir =
|
|
12059
|
+
const servicesDir = join49(repoRoot, "services");
|
|
12020
12060
|
const result = /* @__PURE__ */ new Set();
|
|
12021
12061
|
let entries;
|
|
12022
12062
|
try {
|
|
@@ -12025,9 +12065,9 @@ function vendoredPluginServiceDirs(repoRoot) {
|
|
|
12025
12065
|
return result;
|
|
12026
12066
|
}
|
|
12027
12067
|
for (const entry of entries) {
|
|
12028
|
-
const full =
|
|
12029
|
-
if (!
|
|
12030
|
-
if (
|
|
12068
|
+
const full = join49(servicesDir, entry);
|
|
12069
|
+
if (!existsSync44(full) || !statSync13(full).isDirectory()) continue;
|
|
12070
|
+
if (existsSync44(join49(full, "biffo.plugin.json"))) {
|
|
12031
12071
|
result.add(entry);
|
|
12032
12072
|
}
|
|
12033
12073
|
}
|
|
@@ -12048,7 +12088,7 @@ function findWorkflowFiles(repoRoot) {
|
|
|
12048
12088
|
if (entry === ".github" && relative11.startsWith("services/") && vendoredPluginDirs.has(relative11.slice("services/".length))) {
|
|
12049
12089
|
continue;
|
|
12050
12090
|
}
|
|
12051
|
-
const full =
|
|
12091
|
+
const full = join49(dir, entry);
|
|
12052
12092
|
const rel = relative11 ? `${relative11}/${entry}` : entry;
|
|
12053
12093
|
if (statSync13(full).isDirectory()) {
|
|
12054
12094
|
walk2(full, rel);
|
|
@@ -12092,7 +12132,7 @@ function checkWorkflowSource(file, rawSource) {
|
|
|
12092
12132
|
}
|
|
12093
12133
|
function checkTerraformInput(repoRoot) {
|
|
12094
12134
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
12095
|
-
(file) => checkWorkflowSource(file, readFileSync35(
|
|
12135
|
+
(file) => checkWorkflowSource(file, readFileSync35(join49(repoRoot, file), "utf8"))
|
|
12096
12136
|
);
|
|
12097
12137
|
}
|
|
12098
12138
|
|
|
@@ -12150,13 +12190,13 @@ function checkWorkflowSource2(file, rawSource) {
|
|
|
12150
12190
|
}
|
|
12151
12191
|
function checkLambdaOutput(repoRoot) {
|
|
12152
12192
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
12153
|
-
(file) => checkWorkflowSource2(file, readFileSync36(
|
|
12193
|
+
(file) => checkWorkflowSource2(file, readFileSync36(join50(repoRoot, file), "utf8"))
|
|
12154
12194
|
);
|
|
12155
12195
|
}
|
|
12156
12196
|
|
|
12157
12197
|
// src/scripts/check-lambda-output.ts
|
|
12158
12198
|
async function runLambdaOutputCheck() {
|
|
12159
|
-
const root = (await
|
|
12199
|
+
const root = (await execa17("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12160
12200
|
const files = findWorkflowFiles(root);
|
|
12161
12201
|
console.log(`audited ${files.length} workflow file(s) under ${root}`);
|
|
12162
12202
|
if (files.length === 0) {
|
|
@@ -12178,9 +12218,9 @@ async function runLambdaOutputCheck() {
|
|
|
12178
12218
|
}
|
|
12179
12219
|
|
|
12180
12220
|
// src/scripts/check-migration-body-change.ts
|
|
12181
|
-
import { execa as
|
|
12182
|
-
import { existsSync as
|
|
12183
|
-
import { join as
|
|
12221
|
+
import { execa as execa18 } from "execa";
|
|
12222
|
+
import { existsSync as existsSync45 } from "fs";
|
|
12223
|
+
import { join as join51 } from "path";
|
|
12184
12224
|
|
|
12185
12225
|
// src/lib/migration-body-change-guard.ts
|
|
12186
12226
|
function checkMigrationBodyChangeMarkers(diffs) {
|
|
@@ -12235,18 +12275,18 @@ var GREEN = "\x1B[32m";
|
|
|
12235
12275
|
var YELLOW2 = "\x1B[33m";
|
|
12236
12276
|
var OFF2 = "\x1B[0m";
|
|
12237
12277
|
async function showAt(ref, path, cwd) {
|
|
12238
|
-
const result = await
|
|
12278
|
+
const result = await execa18("git", ["show", `${ref}:${path}`], { cwd, reject: false });
|
|
12239
12279
|
return result.exitCode === 0 ? result.stdout : null;
|
|
12240
12280
|
}
|
|
12241
12281
|
async function runMigrationBodyChangeCheck(argv) {
|
|
12242
|
-
const root = (await
|
|
12282
|
+
const root = (await execa18("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12243
12283
|
if (isInstanceRepo(root)) {
|
|
12244
12284
|
console.log(
|
|
12245
12285
|
`\u2713 migration body-change guard: skipped \u2014 this is an instance (${INSTANCE_CORE_FILE} present). Its migrations/versions/ is a carried, user-owned copy, not the template source this guard protects.`
|
|
12246
12286
|
);
|
|
12247
12287
|
return;
|
|
12248
12288
|
}
|
|
12249
|
-
if (!
|
|
12289
|
+
if (!existsSync45(join51(root, MIGRATIONS_VERSIONS_DIR))) {
|
|
12250
12290
|
console.log(`\u2713 migration body-change guard: no ${MIGRATIONS_VERSIONS_DIR} in this repo.`);
|
|
12251
12291
|
return;
|
|
12252
12292
|
}
|
|
@@ -12255,8 +12295,8 @@ async function runMigrationBodyChangeCheck(argv) {
|
|
|
12255
12295
|
console.error("No base ref: set GITHUB_BASE_REF or pass a base branch as the first argument.");
|
|
12256
12296
|
process.exit(2);
|
|
12257
12297
|
}
|
|
12258
|
-
await
|
|
12259
|
-
const { stdout } = await
|
|
12298
|
+
await execa18("git", ["fetch", "--quiet", "origin", base], { cwd: root, reject: false });
|
|
12299
|
+
const { stdout } = await execa18(
|
|
12260
12300
|
"git",
|
|
12261
12301
|
[
|
|
12262
12302
|
"diff",
|
|
@@ -12349,8 +12389,8 @@ ${BOLD2}What to do${OFF2}
|
|
|
12349
12389
|
|
|
12350
12390
|
// src/scripts/check-pipe-trap.ts
|
|
12351
12391
|
import { readFileSync as readFileSync37, readdirSync as readdirSync22 } from "fs";
|
|
12352
|
-
import { join as
|
|
12353
|
-
import { execa as
|
|
12392
|
+
import { join as join52, relative as relative9 } from "path";
|
|
12393
|
+
import { execa as execa19 } from "execa";
|
|
12354
12394
|
|
|
12355
12395
|
// src/lib/pipe-trap-guard.ts
|
|
12356
12396
|
var STATUS_BEARING = [
|
|
@@ -12446,7 +12486,7 @@ function findPipeTraps(source) {
|
|
|
12446
12486
|
function shellFiles(root) {
|
|
12447
12487
|
const out = [];
|
|
12448
12488
|
for (const dir of ["scripts", ".githooks"]) {
|
|
12449
|
-
const full =
|
|
12489
|
+
const full = join52(root, dir);
|
|
12450
12490
|
let entries;
|
|
12451
12491
|
try {
|
|
12452
12492
|
entries = readdirSync22(full, { withFileTypes: true });
|
|
@@ -12456,13 +12496,13 @@ function shellFiles(root) {
|
|
|
12456
12496
|
for (const entry of entries) {
|
|
12457
12497
|
if (!entry.isFile()) continue;
|
|
12458
12498
|
if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
|
|
12459
|
-
out.push(
|
|
12499
|
+
out.push(join52(full, entry.name));
|
|
12460
12500
|
}
|
|
12461
12501
|
}
|
|
12462
12502
|
return out;
|
|
12463
12503
|
}
|
|
12464
12504
|
async function runPipeTrapCheck() {
|
|
12465
|
-
const root = (await
|
|
12505
|
+
const root = (await execa19("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12466
12506
|
const files = shellFiles(root);
|
|
12467
12507
|
console.log(`audited ${files.length} shell file(s) under scripts/ and .githooks/ under ${root}`);
|
|
12468
12508
|
if (files.length === 0) {
|
|
@@ -12489,11 +12529,11 @@ async function runPipeTrapCheck() {
|
|
|
12489
12529
|
}
|
|
12490
12530
|
|
|
12491
12531
|
// src/scripts/check-plugin-allowlist-convention.ts
|
|
12492
|
-
import { execa as
|
|
12532
|
+
import { execa as execa20 } from "execa";
|
|
12493
12533
|
|
|
12494
12534
|
// src/lib/plugin-allowlist-convention.ts
|
|
12495
12535
|
import { readFileSync as readFileSync38 } from "fs";
|
|
12496
|
-
import { join as
|
|
12536
|
+
import { join as join53 } from "path";
|
|
12497
12537
|
var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
|
|
12498
12538
|
var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
|
|
12499
12539
|
var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
|
|
@@ -12504,7 +12544,7 @@ var PLUGIN = "<plugin>";
|
|
|
12504
12544
|
var ACCOUNT = "<account>";
|
|
12505
12545
|
function read(repoRoot, relative11) {
|
|
12506
12546
|
try {
|
|
12507
|
-
return readFileSync38(
|
|
12547
|
+
return readFileSync38(join53(repoRoot, relative11), "utf8");
|
|
12508
12548
|
} catch {
|
|
12509
12549
|
throw new Error(`plugin-allowlist drift guard: cannot read ${relative11}`);
|
|
12510
12550
|
}
|
|
@@ -12600,7 +12640,7 @@ Plugins would be rejected by require_service_principal (ADR-0009). Fix the glob,
|
|
|
12600
12640
|
|
|
12601
12641
|
// src/scripts/check-plugin-allowlist-convention.ts
|
|
12602
12642
|
async function runPluginAllowlistConventionCheck() {
|
|
12603
|
-
const root = (await
|
|
12643
|
+
const root = (await execa20("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12604
12644
|
let violations;
|
|
12605
12645
|
try {
|
|
12606
12646
|
violations = checkAllowlistConvention(root);
|
|
@@ -12625,33 +12665,33 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
12625
12665
|
}
|
|
12626
12666
|
|
|
12627
12667
|
// src/scripts/check-plugin-collisions.ts
|
|
12628
|
-
import { existsSync as
|
|
12629
|
-
import { join as
|
|
12630
|
-
import { execa as
|
|
12668
|
+
import { existsSync as existsSync47 } from "fs";
|
|
12669
|
+
import { join as join55 } from "path";
|
|
12670
|
+
import { execa as execa21 } from "execa";
|
|
12631
12671
|
|
|
12632
12672
|
// src/lib/plugin-collision-guard.ts
|
|
12633
|
-
import { existsSync as
|
|
12634
|
-
import { join as
|
|
12673
|
+
import { existsSync as existsSync46, readdirSync as readdirSync23, statSync as statSync14 } from "fs";
|
|
12674
|
+
import { join as join54 } from "path";
|
|
12635
12675
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
12636
12676
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
12637
12677
|
function subdirectories(dir) {
|
|
12638
|
-
if (!
|
|
12678
|
+
if (!existsSync46(dir)) return [];
|
|
12639
12679
|
return readdirSync23(dir).filter((entry) => {
|
|
12640
12680
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
12641
12681
|
try {
|
|
12642
|
-
return statSync14(
|
|
12682
|
+
return statSync14(join54(dir, entry)).isDirectory();
|
|
12643
12683
|
} catch {
|
|
12644
12684
|
return false;
|
|
12645
12685
|
}
|
|
12646
12686
|
});
|
|
12647
12687
|
}
|
|
12648
12688
|
function regularPackagesOf(pluginDir2) {
|
|
12649
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
12689
|
+
return subdirectories(pluginDir2).filter((name) => existsSync46(join54(pluginDir2, name, "__init__.py"))).sort();
|
|
12650
12690
|
}
|
|
12651
12691
|
function bareTestModulesOf(pluginDir2) {
|
|
12652
|
-
const testsDir =
|
|
12653
|
-
if (!
|
|
12654
|
-
if (
|
|
12692
|
+
const testsDir = join54(pluginDir2, "tests");
|
|
12693
|
+
if (!existsSync46(testsDir)) return [];
|
|
12694
|
+
if (existsSync46(join54(testsDir, "__init__.py"))) return [];
|
|
12655
12695
|
return readdirSync23(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
12656
12696
|
}
|
|
12657
12697
|
function findCollisions(servicesDir, pluginDirs) {
|
|
@@ -12660,7 +12700,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
12660
12700
|
const gather = (kind, namesOf) => {
|
|
12661
12701
|
const claims = /* @__PURE__ */ new Map();
|
|
12662
12702
|
for (const plugin of plugins) {
|
|
12663
|
-
for (const name of namesOf(
|
|
12703
|
+
for (const name of namesOf(join54(servicesDir, plugin))) {
|
|
12664
12704
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
12665
12705
|
}
|
|
12666
12706
|
}
|
|
@@ -12697,9 +12737,9 @@ function formatCollisions(collisions) {
|
|
|
12697
12737
|
|
|
12698
12738
|
// src/scripts/check-plugin-collisions.ts
|
|
12699
12739
|
async function runPluginCollisionCheck() {
|
|
12700
|
-
const root = (await
|
|
12701
|
-
const servicesDir =
|
|
12702
|
-
if (!
|
|
12740
|
+
const root = (await execa21("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12741
|
+
const servicesDir = join55(root, "services");
|
|
12742
|
+
if (!existsSync47(servicesDir)) {
|
|
12703
12743
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
12704
12744
|
return;
|
|
12705
12745
|
}
|
|
@@ -12716,11 +12756,11 @@ async function runPluginCollisionCheck() {
|
|
|
12716
12756
|
}
|
|
12717
12757
|
|
|
12718
12758
|
// src/scripts/check-plugin-terraform.ts
|
|
12719
|
-
import { execa as
|
|
12759
|
+
import { execa as execa22 } from "execa";
|
|
12720
12760
|
|
|
12721
12761
|
// src/lib/plugin-terraform-guard.ts
|
|
12722
|
-
import { existsSync as
|
|
12723
|
-
import { dirname as dirname10, join as
|
|
12762
|
+
import { existsSync as existsSync48, readFileSync as readFileSync39, readdirSync as readdirSync24 } from "fs";
|
|
12763
|
+
import { dirname as dirname10, join as join56, relative as relative10, sep as sep4 } from "path";
|
|
12724
12764
|
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
12725
12765
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
12726
12766
|
function findPluginManifests(root) {
|
|
@@ -12735,9 +12775,9 @@ function findPluginManifests(root) {
|
|
|
12735
12775
|
for (const entry of entries) {
|
|
12736
12776
|
if (entry.isDirectory()) {
|
|
12737
12777
|
if (SKIP_DIRS3.has(entry.name)) continue;
|
|
12738
|
-
walk2(
|
|
12778
|
+
walk2(join56(dir, entry.name));
|
|
12739
12779
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
12740
|
-
found.push(relative10(root,
|
|
12780
|
+
found.push(relative10(root, join56(dir, entry.name)).split(sep4).join("/"));
|
|
12741
12781
|
}
|
|
12742
12782
|
}
|
|
12743
12783
|
};
|
|
@@ -12762,14 +12802,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
12762
12802
|
}
|
|
12763
12803
|
function checkPluginTerraform(root) {
|
|
12764
12804
|
const violations = [];
|
|
12765
|
-
const coreManifest =
|
|
12805
|
+
const coreManifest = existsSync48(join56(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
12766
12806
|
for (const manifest of findPluginManifests(root)) {
|
|
12767
12807
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
12768
|
-
const absManifest =
|
|
12808
|
+
const absManifest = join56(root, manifest);
|
|
12769
12809
|
const subscriptions = readSubscriptions(absManifest);
|
|
12770
12810
|
if (subscriptions === null) continue;
|
|
12771
12811
|
const pluginDir2 = dirname10(absManifest);
|
|
12772
|
-
if (
|
|
12812
|
+
if (existsSync48(join56(pluginDir2, "terraform"))) continue;
|
|
12773
12813
|
const relPluginDir = relative10(root, pluginDir2).split(sep4).join("/");
|
|
12774
12814
|
violations.push({
|
|
12775
12815
|
manifest,
|
|
@@ -12789,7 +12829,7 @@ function formatViolations(violations) {
|
|
|
12789
12829
|
|
|
12790
12830
|
// src/scripts/check-plugin-terraform.ts
|
|
12791
12831
|
async function runPluginTerraformCheck() {
|
|
12792
|
-
const root = (await
|
|
12832
|
+
const root = (await execa22("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12793
12833
|
const violations = checkPluginTerraform(root);
|
|
12794
12834
|
if (violations.length > 0) {
|
|
12795
12835
|
console.error("\u2717 plugin Terraform guard: event subscriptions with no infrastructure\n");
|
|
@@ -12800,13 +12840,13 @@ async function runPluginTerraformCheck() {
|
|
|
12800
12840
|
}
|
|
12801
12841
|
|
|
12802
12842
|
// src/scripts/check-plugin-tool-supply.ts
|
|
12803
|
-
import { existsSync as
|
|
12804
|
-
import { join as
|
|
12805
|
-
import { execa as
|
|
12843
|
+
import { existsSync as existsSync50 } from "fs";
|
|
12844
|
+
import { join as join58 } from "path";
|
|
12845
|
+
import { execa as execa23 } from "execa";
|
|
12806
12846
|
|
|
12807
12847
|
// src/lib/plugin-tool-supply-audit.ts
|
|
12808
|
-
import { existsSync as
|
|
12809
|
-
import { join as
|
|
12848
|
+
import { existsSync as existsSync49, readFileSync as readFileSync40, readdirSync as readdirSync25, statSync as statSync15 } from "fs";
|
|
12849
|
+
import { join as join57 } from "path";
|
|
12810
12850
|
|
|
12811
12851
|
// src/lib/openrouter-model-snapshot.ts
|
|
12812
12852
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -13223,7 +13263,7 @@ function listDirs(root) {
|
|
|
13223
13263
|
}
|
|
13224
13264
|
return entries.filter((e) => {
|
|
13225
13265
|
try {
|
|
13226
|
-
return statSync15(
|
|
13266
|
+
return statSync15(join57(root, e)).isDirectory();
|
|
13227
13267
|
} catch {
|
|
13228
13268
|
return false;
|
|
13229
13269
|
}
|
|
@@ -13239,7 +13279,7 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
13239
13279
|
return;
|
|
13240
13280
|
}
|
|
13241
13281
|
for (const entry of entries) {
|
|
13242
|
-
const p =
|
|
13282
|
+
const p = join57(dir, entry);
|
|
13243
13283
|
let st;
|
|
13244
13284
|
try {
|
|
13245
13285
|
st = statSync15(p);
|
|
@@ -13265,14 +13305,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
13265
13305
|
);
|
|
13266
13306
|
}
|
|
13267
13307
|
function pluginTerraformFiles(pluginDir2) {
|
|
13268
|
-
const tfDir =
|
|
13308
|
+
const tfDir = join57(pluginDir2, "terraform");
|
|
13269
13309
|
let entries;
|
|
13270
13310
|
try {
|
|
13271
13311
|
entries = readdirSync25(tfDir);
|
|
13272
13312
|
} catch {
|
|
13273
13313
|
return [];
|
|
13274
13314
|
}
|
|
13275
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
13315
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join57(tfDir, e)).sort();
|
|
13276
13316
|
}
|
|
13277
13317
|
function extractManifestTools(manifestText) {
|
|
13278
13318
|
let parsed;
|
|
@@ -13524,8 +13564,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
13524
13564
|
function normalizeModelId(id) {
|
|
13525
13565
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
13526
13566
|
}
|
|
13527
|
-
var CONFIG_PY_PATH =
|
|
13528
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
13567
|
+
var CONFIG_PY_PATH = join57("services", "api", "src", "api", "config.py");
|
|
13568
|
+
var ORCHESTRATION_SCHEMA_PATH = join57(
|
|
13529
13569
|
"services",
|
|
13530
13570
|
"api",
|
|
13531
13571
|
"src",
|
|
@@ -13537,10 +13577,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
13537
13577
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
13538
13578
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
13539
13579
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
13540
|
-
const configPath =
|
|
13541
|
-
const orchestrationPath =
|
|
13542
|
-
const configMissing = !
|
|
13543
|
-
const orchestrationSchemaMissing = !
|
|
13580
|
+
const configPath = join57(repoRoot, CONFIG_PY_PATH);
|
|
13581
|
+
const orchestrationPath = join57(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
13582
|
+
const configMissing = !existsSync49(configPath);
|
|
13583
|
+
const orchestrationSchemaMissing = !existsSync49(orchestrationPath);
|
|
13544
13584
|
const knownSet = new Set(knownModelIds);
|
|
13545
13585
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
13546
13586
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -13611,7 +13651,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
13611
13651
|
function discoverPluginDirs(pluginsRoot) {
|
|
13612
13652
|
return listDirs(pluginsRoot).filter((name) => {
|
|
13613
13653
|
try {
|
|
13614
|
-
return statSync15(
|
|
13654
|
+
return statSync15(join57(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
13615
13655
|
} catch {
|
|
13616
13656
|
return false;
|
|
13617
13657
|
}
|
|
@@ -13624,8 +13664,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
13624
13664
|
let terraformBlind = false;
|
|
13625
13665
|
let totalDeclaredTools = 0;
|
|
13626
13666
|
for (const name of pluginNames) {
|
|
13627
|
-
const pluginDir2 =
|
|
13628
|
-
const manifestText = readFileSync40(
|
|
13667
|
+
const pluginDir2 = join57(pluginsRoot, name);
|
|
13668
|
+
const manifestText = readFileSync40(join57(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
13629
13669
|
const manifest = extractManifestTools(manifestText);
|
|
13630
13670
|
if (manifest.parseError) {
|
|
13631
13671
|
findings.push({
|
|
@@ -13723,7 +13763,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
13723
13763
|
requiredEnvVars: envResult.envVars,
|
|
13724
13764
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
13725
13765
|
status: anyWired ? "ok" : "missing-env",
|
|
13726
|
-
detail: anyWired ? `${entry.predicate}() is satisfiable: at least one of ${JSON.stringify(envResult.envVars)} is wired in Terraform` : `${entry.predicate}() reads ${JSON.stringify(envResult.envVars)} \u2014 NONE of these are wired by any environment_variables block under ${
|
|
13766
|
+
detail: anyWired ? `${entry.predicate}() is satisfiable: at least one of ${JSON.stringify(envResult.envVars)} is wired in Terraform` : `${entry.predicate}() reads ${JSON.stringify(envResult.envVars)} \u2014 NONE of these are wired by any environment_variables block under ${join57(pluginDir2, "terraform")}, so this deployment can never supply it`
|
|
13727
13767
|
});
|
|
13728
13768
|
}
|
|
13729
13769
|
}
|
|
@@ -13754,10 +13794,10 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
13754
13794
|
|
|
13755
13795
|
// src/scripts/check-plugin-tool-supply.ts
|
|
13756
13796
|
async function runPluginToolSupplyCheck() {
|
|
13757
|
-
const root = (await
|
|
13797
|
+
const root = (await execa23("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13758
13798
|
let allOk = true;
|
|
13759
|
-
const pluginsRoot =
|
|
13760
|
-
if (!
|
|
13799
|
+
const pluginsRoot = join58(root, "services", "_plugins");
|
|
13800
|
+
if (!existsSync50(pluginsRoot)) {
|
|
13761
13801
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
13762
13802
|
} else {
|
|
13763
13803
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -13787,8 +13827,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
13787
13827
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
13788
13828
|
}
|
|
13789
13829
|
}
|
|
13790
|
-
const servicesApiRoot =
|
|
13791
|
-
if (!
|
|
13830
|
+
const servicesApiRoot = join58(root, "services", "api");
|
|
13831
|
+
if (!existsSync50(servicesApiRoot)) {
|
|
13792
13832
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
13793
13833
|
} else {
|
|
13794
13834
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -13834,7 +13874,7 @@ async function runPluginToolSupplyCheck() {
|
|
|
13834
13874
|
}
|
|
13835
13875
|
|
|
13836
13876
|
// src/scripts/check-release-subject.ts
|
|
13837
|
-
import { execa as
|
|
13877
|
+
import { execa as execa24 } from "execa";
|
|
13838
13878
|
|
|
13839
13879
|
// src/lib/release-version.ts
|
|
13840
13880
|
var MINOR_TYPES = /* @__PURE__ */ new Set(["feat"]);
|
|
@@ -13871,7 +13911,7 @@ async function fetchPrTitleViaGh({
|
|
|
13871
13911
|
PR_NUMBER,
|
|
13872
13912
|
GH_REPO
|
|
13873
13913
|
}) {
|
|
13874
|
-
const { stdout } = await
|
|
13914
|
+
const { stdout } = await execa24(
|
|
13875
13915
|
"gh",
|
|
13876
13916
|
["pr", "view", PR_NUMBER, "--repo", GH_REPO, "--json", "title", "--jq", ".title"],
|
|
13877
13917
|
{ env: { ...process.env, GH_TOKEN } }
|
|
@@ -13907,7 +13947,7 @@ async function resolveReleaseSubject({
|
|
|
13907
13947
|
);
|
|
13908
13948
|
}
|
|
13909
13949
|
}
|
|
13910
|
-
return (await
|
|
13950
|
+
return (await execa24("git", ["log", "-1", "--format=%s"], { cwd })).stdout.trim();
|
|
13911
13951
|
}
|
|
13912
13952
|
async function runReleaseSubjectCheck(argv) {
|
|
13913
13953
|
const base = process.env["GITHUB_BASE_REF"] ?? argv[0];
|
|
@@ -13915,9 +13955,9 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
13915
13955
|
console.error("No base ref: set GITHUB_BASE_REF or pass a base branch as the first argument.");
|
|
13916
13956
|
process.exit(2);
|
|
13917
13957
|
}
|
|
13918
|
-
const root = (await
|
|
13919
|
-
await
|
|
13920
|
-
const { stdout } = await
|
|
13958
|
+
const root = (await execa24("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13959
|
+
await execa24("git", ["fetch", "--quiet", "origin", base], { cwd: root, reject: false });
|
|
13960
|
+
const { stdout } = await execa24("git", ["diff", "--name-only", `origin/${base}...HEAD`], {
|
|
13921
13961
|
cwd: root
|
|
13922
13962
|
});
|
|
13923
13963
|
const changedFiles = stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
@@ -14168,13 +14208,13 @@ async function runSharedFileReductionCheck(args) {
|
|
|
14168
14208
|
}
|
|
14169
14209
|
|
|
14170
14210
|
// src/scripts/check-skeleton-drift.ts
|
|
14171
|
-
import { existsSync as
|
|
14172
|
-
import { join as
|
|
14173
|
-
import { execa as
|
|
14211
|
+
import { existsSync as existsSync51, readdirSync as readdirSync27 } from "fs";
|
|
14212
|
+
import { join as join60 } from "path";
|
|
14213
|
+
import { execa as execa25 } from "execa";
|
|
14174
14214
|
|
|
14175
14215
|
// src/lib/skeleton-drift-guard.ts
|
|
14176
14216
|
import { readFileSync as readFileSync43, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
|
|
14177
|
-
import { join as
|
|
14217
|
+
import { join as join59 } from "path";
|
|
14178
14218
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
14179
14219
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
14180
14220
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -14238,7 +14278,7 @@ function walk(dir, base = dir) {
|
|
|
14238
14278
|
}
|
|
14239
14279
|
for (const entry of entries) {
|
|
14240
14280
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
14241
|
-
const abs =
|
|
14281
|
+
const abs = join59(dir, entry);
|
|
14242
14282
|
let isDir;
|
|
14243
14283
|
try {
|
|
14244
14284
|
isDir = statSync16(abs).isDirectory();
|
|
@@ -14260,7 +14300,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
14260
14300
|
if (!rule.appliesTo(rel)) continue;
|
|
14261
14301
|
let contents;
|
|
14262
14302
|
try {
|
|
14263
|
-
contents = readFileSync43(
|
|
14303
|
+
contents = readFileSync43(join59(skeletonRoot, rel), "utf8");
|
|
14264
14304
|
} catch {
|
|
14265
14305
|
continue;
|
|
14266
14306
|
}
|
|
@@ -14289,23 +14329,23 @@ function formatViolations2(violations) {
|
|
|
14289
14329
|
|
|
14290
14330
|
// src/scripts/check-skeleton-drift.ts
|
|
14291
14331
|
function discoverSkeletons(root) {
|
|
14292
|
-
const skeletonsDir =
|
|
14332
|
+
const skeletonsDir = join60(root, "_skeletons");
|
|
14293
14333
|
let entries;
|
|
14294
14334
|
try {
|
|
14295
14335
|
entries = readdirSync27(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
14296
14336
|
} catch {
|
|
14297
14337
|
return [];
|
|
14298
14338
|
}
|
|
14299
|
-
return entries.filter((name) =>
|
|
14339
|
+
return entries.filter((name) => existsSync51(join60(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
14300
14340
|
}
|
|
14301
14341
|
async function runSkeletonDriftCheck() {
|
|
14302
|
-
const root = (await
|
|
14342
|
+
const root = (await execa25("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14303
14343
|
const skeletons = discoverSkeletons(root);
|
|
14304
14344
|
let filesConsidered = 0;
|
|
14305
14345
|
for (const name of skeletons) {
|
|
14306
|
-
const skeletonRoot =
|
|
14346
|
+
const skeletonRoot = join60(root, "_skeletons", name);
|
|
14307
14347
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
14308
|
-
if (
|
|
14348
|
+
if (existsSync51(join60(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
14309
14349
|
filesConsidered += 1;
|
|
14310
14350
|
}
|
|
14311
14351
|
}
|
|
@@ -14319,7 +14359,7 @@ async function runSkeletonDriftCheck() {
|
|
|
14319
14359
|
process.exit(1);
|
|
14320
14360
|
}
|
|
14321
14361
|
const violations = skeletons.flatMap(
|
|
14322
|
-
(name) => auditSkeleton(
|
|
14362
|
+
(name) => auditSkeleton(join60(root, "_skeletons", name), name)
|
|
14323
14363
|
);
|
|
14324
14364
|
if (violations.length > 0) {
|
|
14325
14365
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -14331,9 +14371,9 @@ async function runSkeletonDriftCheck() {
|
|
|
14331
14371
|
}
|
|
14332
14372
|
|
|
14333
14373
|
// src/scripts/check-terraform-input.ts
|
|
14334
|
-
import { execa as
|
|
14374
|
+
import { execa as execa26 } from "execa";
|
|
14335
14375
|
async function runTerraformInputCheck() {
|
|
14336
|
-
const root = (await
|
|
14376
|
+
const root = (await execa26("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14337
14377
|
const files = findWorkflowFiles(root);
|
|
14338
14378
|
console.log(`audited ${files.length} workflow file(s) under ${root}`);
|
|
14339
14379
|
if (files.length === 0) {
|
|
@@ -14356,7 +14396,7 @@ async function runTerraformInputCheck() {
|
|
|
14356
14396
|
|
|
14357
14397
|
// src/commands/check.ts
|
|
14358
14398
|
var checkCommand = new Command24("check").description(
|
|
14359
|
-
"Repo guards (ownership, release subject, plugin terraform, plugin collisions, eventbridge-log-permissions, plugin-tool-supply, core-direct-paths, cognito-invite-template, lambda-output, pipe-trap, codeql-suppression, skeleton-drift, terraform-input, plugin-allowlist-convention, migration-body-change) run in CI and git hooks, plus out-of-band audits (branch protection, plugin-staleness)"
|
|
14399
|
+
"Repo guards (ownership, release subject, plugin terraform, plugin collisions, eventbridge-log-permissions, plugin-tool-supply, core-direct-paths, instance-adoption, cognito-invite-template, lambda-output, pipe-trap, codeql-suppression, skeleton-drift, terraform-input, plugin-allowlist-convention, migration-body-change) run in CI and git hooks, plus out-of-band audits (branch protection, plugin-staleness)"
|
|
14360
14400
|
);
|
|
14361
14401
|
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 () => {
|
|
14362
14402
|
await runOwnershipCheck(rawArgsAfter("ownership"));
|
|
@@ -14408,6 +14448,17 @@ checkCommand.command("core-direct-paths").description(
|
|
|
14408
14448
|
await runCoreDirectPathsCheck(opts);
|
|
14409
14449
|
}
|
|
14410
14450
|
);
|
|
14451
|
+
checkCommand.command("instance-adoption").description(
|
|
14452
|
+
"Refuse a real instance tree that has not consumed a registered adoption channel its target template ships (#1538/#1570/#1609) \u2014 checkInstanceAdoption previously ran only inside `biffo core upgrade`, so a gap in an instance nobody happened to be upgrading went undetected for days (keiranholloway/biffo-platform, PR #174). --instance-dir is REQUIRED and has no self-check default: this repo is the template, not an instance."
|
|
14453
|
+
).option("--instance <name>", "Label for the report (defaults to the basename of --instance-dir)").option(
|
|
14454
|
+
"--instance-dir <dir>",
|
|
14455
|
+
"REQUIRED: the real instance tree to check adoption against (oursDir) \u2014 no self-check default exists (exits 2, cannot-tell, when omitted)"
|
|
14456
|
+
).option(
|
|
14457
|
+
"--theirs-dir <dir>",
|
|
14458
|
+
"Template tree that ships the channel (theirsDir); defaults to this repo root"
|
|
14459
|
+
).action(async (opts) => {
|
|
14460
|
+
await runInstanceAdoptionCheck(opts);
|
|
14461
|
+
});
|
|
14411
14462
|
checkCommand.command("cognito-invite-template").description(
|
|
14412
14463
|
"Refuse a Cognito invite_message_template missing a required member or placeholder (#356) \u2014 terraform validate is silent on this, so a fresh deploy fails on its first apply"
|
|
14413
14464
|
).action(async () => {
|
|
@@ -14474,8 +14525,8 @@ function rawArgsAfter(subcommand) {
|
|
|
14474
14525
|
}
|
|
14475
14526
|
|
|
14476
14527
|
// src/commands/doctor.ts
|
|
14477
|
-
import { existsSync as
|
|
14478
|
-
import { join as
|
|
14528
|
+
import { existsSync as existsSync52, readFileSync as readFileSync44 } from "fs";
|
|
14529
|
+
import { join as join61, resolve as resolve20 } from "path";
|
|
14479
14530
|
import chalk21 from "chalk";
|
|
14480
14531
|
import { Command as Command25 } from "commander";
|
|
14481
14532
|
|
|
@@ -14654,8 +14705,8 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
|
|
|
14654
14705
|
return runDoctorChecks(facts);
|
|
14655
14706
|
}
|
|
14656
14707
|
function readLocalCoreVersion(cwd) {
|
|
14657
|
-
const path =
|
|
14658
|
-
if (!
|
|
14708
|
+
const path = join61(cwd, INSTANCE_CORE_FILE);
|
|
14709
|
+
if (!existsSync52(path)) return null;
|
|
14659
14710
|
try {
|
|
14660
14711
|
return extractVersionField(readFileSync44(path, "utf8"));
|
|
14661
14712
|
} catch {
|
|
@@ -14677,8 +14728,8 @@ function extractVersionField(contents) {
|
|
|
14677
14728
|
return match?.[1] ?? null;
|
|
14678
14729
|
}
|
|
14679
14730
|
function readFossil(cwd) {
|
|
14680
|
-
const path =
|
|
14681
|
-
if (!
|
|
14731
|
+
const path = join61(cwd, CORE_VERSION_FILE);
|
|
14732
|
+
if (!existsSync52(path)) return null;
|
|
14682
14733
|
try {
|
|
14683
14734
|
const value = readFileSync44(path, "utf8").trim();
|
|
14684
14735
|
return value === "" ? null : value;
|
|
@@ -15129,13 +15180,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
15129
15180
|
import { Command as Command27 } from "commander";
|
|
15130
15181
|
|
|
15131
15182
|
// src/lib/packaged-scripts.ts
|
|
15132
|
-
import { existsSync as
|
|
15133
|
-
import { dirname as dirname11, join as
|
|
15183
|
+
import { existsSync as existsSync53 } from "fs";
|
|
15184
|
+
import { dirname as dirname11, join as join62 } from "path";
|
|
15134
15185
|
function findPackagedScript(startDir, relativePath) {
|
|
15135
15186
|
let dir = startDir;
|
|
15136
15187
|
for (; ; ) {
|
|
15137
|
-
const candidate =
|
|
15138
|
-
if (
|
|
15188
|
+
const candidate = join62(dir, relativePath);
|
|
15189
|
+
if (existsSync53(candidate)) return candidate;
|
|
15139
15190
|
const parent = dirname11(dir);
|
|
15140
15191
|
if (parent === dir) return null;
|
|
15141
15192
|
dir = parent;
|