@biffo/cli 0.213.0 → 0.214.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.
|
@@ -567,6 +567,7 @@ fi
|
|
|
567
567
|
if [ -f scripts/biffo.sh ]; then
|
|
568
568
|
run_check plugin-tf sh scripts/biffo.sh check plugin-terraform
|
|
569
569
|
run_check plugin-names sh scripts/biffo.sh check plugin-collisions
|
|
570
|
+
run_check adr-numbering sh scripts/biffo.sh check adr-numbering
|
|
570
571
|
else
|
|
571
572
|
skip biffo-guards "no scripts/biffo.sh in this repo"
|
|
572
573
|
fi
|
|
@@ -567,6 +567,7 @@ fi
|
|
|
567
567
|
if [ -f scripts/biffo.sh ]; then
|
|
568
568
|
run_check plugin-tf sh scripts/biffo.sh check plugin-terraform
|
|
569
569
|
run_check plugin-names sh scripts/biffo.sh check plugin-collisions
|
|
570
|
+
run_check adr-numbering sh scripts/biffo.sh check adr-numbering
|
|
570
571
|
else
|
|
571
572
|
skip biffo-guards "no scripts/biffo.sh in this repo"
|
|
572
573
|
fi
|
package/dist/index.js
CHANGED
|
@@ -9124,9 +9124,61 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
|
9124
9124
|
// src/commands/check.ts
|
|
9125
9125
|
import { Command as Command23 } from "commander";
|
|
9126
9126
|
|
|
9127
|
+
// src/scripts/check-adr-numbering.ts
|
|
9128
|
+
import { existsSync as existsSync32 } from "fs";
|
|
9129
|
+
import { join as join31 } from "path";
|
|
9130
|
+
import { execa as execa5 } from "execa";
|
|
9131
|
+
|
|
9132
|
+
// src/lib/adr-numbering-guard.ts
|
|
9133
|
+
import { existsSync as existsSync31, readdirSync as readdirSync13 } from "fs";
|
|
9134
|
+
var ADR_FILENAME = /^(\d{4})-.+\.md$/;
|
|
9135
|
+
function adrNumbersIn(adrDir) {
|
|
9136
|
+
const claims = /* @__PURE__ */ new Map();
|
|
9137
|
+
if (!existsSync31(adrDir)) return claims;
|
|
9138
|
+
for (const entry of readdirSync13(adrDir).sort()) {
|
|
9139
|
+
const match = ADR_FILENAME.exec(entry);
|
|
9140
|
+
if (!match) continue;
|
|
9141
|
+
const number = match[1];
|
|
9142
|
+
claims.set(number, [...claims.get(number) ?? [], entry]);
|
|
9143
|
+
}
|
|
9144
|
+
return claims;
|
|
9145
|
+
}
|
|
9146
|
+
function findAdrNumberCollisions(adrDir) {
|
|
9147
|
+
const collisions = [];
|
|
9148
|
+
for (const [number, files] of [...adrNumbersIn(adrDir).entries()].sort()) {
|
|
9149
|
+
if (files.length > 1) collisions.push({ number, files: [...files].sort() });
|
|
9150
|
+
}
|
|
9151
|
+
return collisions;
|
|
9152
|
+
}
|
|
9153
|
+
function formatAdrNumberCollisions(collisions) {
|
|
9154
|
+
return collisions.map(
|
|
9155
|
+
(c) => ` ADR-${c.number} is claimed by: ${c.files.join(", ")}
|
|
9156
|
+
Pick a different number for the newer one \u2014 citing "ADR-${c.number}" is
|
|
9157
|
+
ambiguous while both exist.`
|
|
9158
|
+
).join("\n");
|
|
9159
|
+
}
|
|
9160
|
+
|
|
9161
|
+
// src/scripts/check-adr-numbering.ts
|
|
9162
|
+
async function runAdrNumberingCheck() {
|
|
9163
|
+
const root = (await execa5("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
9164
|
+
const adrDir = join31(root, "docs", "ADR");
|
|
9165
|
+
if (!existsSync32(adrDir)) {
|
|
9166
|
+
console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
|
|
9167
|
+
return;
|
|
9168
|
+
}
|
|
9169
|
+
const collisions = findAdrNumberCollisions(adrDir);
|
|
9170
|
+
if (collisions.length > 0) {
|
|
9171
|
+
console.error("\u2717 ADR numbering guard: two ADRs in docs/ADR/ share a number\n");
|
|
9172
|
+
console.error(formatAdrNumberCollisions(collisions));
|
|
9173
|
+
console.error("\nSee tabsii-platform#449 for how this class of collision happens.");
|
|
9174
|
+
process.exit(1);
|
|
9175
|
+
}
|
|
9176
|
+
console.log("\u2713 ADR numbering guard: OK");
|
|
9177
|
+
}
|
|
9178
|
+
|
|
9127
9179
|
// src/scripts/check-branch-protection.ts
|
|
9128
9180
|
import { Octokit as Octokit2 } from "@octokit/rest";
|
|
9129
|
-
import { execa as
|
|
9181
|
+
import { execa as execa6 } from "execa";
|
|
9130
9182
|
|
|
9131
9183
|
// src/lib/branch-protection-apply.ts
|
|
9132
9184
|
var CONTEXT_CONSISTENCY_THRESHOLD = 2 / 3;
|
|
@@ -9253,7 +9305,7 @@ async function resolveRepo(explicit) {
|
|
|
9253
9305
|
}
|
|
9254
9306
|
return { owner, repo };
|
|
9255
9307
|
}
|
|
9256
|
-
const { stdout } = await
|
|
9308
|
+
const { stdout } = await execa6("git", ["remote", "get-url", "origin"]);
|
|
9257
9309
|
const m = /github\.com[:/]([^/]+)\/(.+?)(?:\.git)?$/.exec(stdout.trim());
|
|
9258
9310
|
if (!m?.[1] || !m[2]) {
|
|
9259
9311
|
console.error(
|
|
@@ -9385,7 +9437,7 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
|
|
|
9385
9437
|
}
|
|
9386
9438
|
|
|
9387
9439
|
// src/scripts/check-core-ownership.ts
|
|
9388
|
-
import { execa as
|
|
9440
|
+
import { execa as execa7 } from "execa";
|
|
9389
9441
|
var BOLD = "\x1B[1m";
|
|
9390
9442
|
var DIM = "\x1B[2m";
|
|
9391
9443
|
var RED = "\x1B[31m";
|
|
@@ -9396,7 +9448,7 @@ async function runOwnershipCheck(argv) {
|
|
|
9396
9448
|
const stagedFlag = args.indexOf("--staged");
|
|
9397
9449
|
const staged = stagedFlag !== -1;
|
|
9398
9450
|
const messageFile = staged ? args[stagedFlag + 1] : void 0;
|
|
9399
|
-
const root = (await
|
|
9451
|
+
const root = (await execa7("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
9400
9452
|
if (!isInstanceRepo(root)) {
|
|
9401
9453
|
console.log("\u2713 core ownership guard: skipped \u2014 this is the template, which owns these paths.");
|
|
9402
9454
|
return;
|
|
@@ -9405,11 +9457,11 @@ async function runOwnershipCheck(argv) {
|
|
|
9405
9457
|
let deletedFiles = [];
|
|
9406
9458
|
let commitMessage = "";
|
|
9407
9459
|
if (staged) {
|
|
9408
|
-
const { stdout } = await
|
|
9460
|
+
const { stdout } = await execa7("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
9409
9461
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
9410
9462
|
if (messageFile) {
|
|
9411
|
-
const { readFileSync: readFileSync26, existsSync:
|
|
9412
|
-
if (
|
|
9463
|
+
const { readFileSync: readFileSync26, existsSync: existsSync37 } = await import("fs");
|
|
9464
|
+
if (existsSync37(messageFile)) commitMessage = readFileSync26(messageFile, "utf8");
|
|
9413
9465
|
}
|
|
9414
9466
|
} else {
|
|
9415
9467
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -9417,18 +9469,18 @@ async function runOwnershipCheck(argv) {
|
|
|
9417
9469
|
console.error("No base ref: set GITHUB_BASE_REF or pass a base branch as the first argument.");
|
|
9418
9470
|
process.exit(2);
|
|
9419
9471
|
}
|
|
9420
|
-
await
|
|
9421
|
-
const { stdout } = await
|
|
9472
|
+
await execa7("git", ["fetch", "--quiet", "origin", base], { cwd: root, reject: false });
|
|
9473
|
+
const { stdout } = await execa7("git", ["diff", "--name-status", `origin/${base}...HEAD`], {
|
|
9422
9474
|
cwd: root
|
|
9423
9475
|
});
|
|
9424
9476
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
9425
|
-
const { stdout: log2 } = await
|
|
9477
|
+
const { stdout: log2 } = await execa7("git", ["log", "--format=%B", `origin/${base}..HEAD`], {
|
|
9426
9478
|
cwd: root,
|
|
9427
9479
|
reject: false
|
|
9428
9480
|
});
|
|
9429
9481
|
commitMessage = log2;
|
|
9430
9482
|
}
|
|
9431
|
-
const { stdout: gitBranch } = await
|
|
9483
|
+
const { stdout: gitBranch } = await execa7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
9432
9484
|
cwd: root,
|
|
9433
9485
|
reject: false
|
|
9434
9486
|
});
|
|
@@ -9510,34 +9562,34 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
9510
9562
|
}
|
|
9511
9563
|
|
|
9512
9564
|
// src/scripts/check-plugin-collisions.ts
|
|
9513
|
-
import { existsSync as
|
|
9514
|
-
import { join as
|
|
9515
|
-
import { execa as
|
|
9565
|
+
import { existsSync as existsSync34 } from "fs";
|
|
9566
|
+
import { join as join33 } from "path";
|
|
9567
|
+
import { execa as execa8 } from "execa";
|
|
9516
9568
|
|
|
9517
9569
|
// src/lib/plugin-collision-guard.ts
|
|
9518
|
-
import { existsSync as
|
|
9519
|
-
import { join as
|
|
9570
|
+
import { existsSync as existsSync33, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
|
|
9571
|
+
import { join as join32 } from "path";
|
|
9520
9572
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
9521
9573
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
9522
9574
|
function subdirectories(dir) {
|
|
9523
|
-
if (!
|
|
9524
|
-
return
|
|
9575
|
+
if (!existsSync33(dir)) return [];
|
|
9576
|
+
return readdirSync14(dir).filter((entry) => {
|
|
9525
9577
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
9526
9578
|
try {
|
|
9527
|
-
return statSync7(
|
|
9579
|
+
return statSync7(join32(dir, entry)).isDirectory();
|
|
9528
9580
|
} catch {
|
|
9529
9581
|
return false;
|
|
9530
9582
|
}
|
|
9531
9583
|
});
|
|
9532
9584
|
}
|
|
9533
9585
|
function regularPackagesOf(pluginDir2) {
|
|
9534
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
9586
|
+
return subdirectories(pluginDir2).filter((name) => existsSync33(join32(pluginDir2, name, "__init__.py"))).sort();
|
|
9535
9587
|
}
|
|
9536
9588
|
function bareTestModulesOf(pluginDir2) {
|
|
9537
|
-
const testsDir =
|
|
9538
|
-
if (!
|
|
9539
|
-
if (
|
|
9540
|
-
return
|
|
9589
|
+
const testsDir = join32(pluginDir2, "tests");
|
|
9590
|
+
if (!existsSync33(testsDir)) return [];
|
|
9591
|
+
if (existsSync33(join32(testsDir, "__init__.py"))) return [];
|
|
9592
|
+
return readdirSync14(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
9541
9593
|
}
|
|
9542
9594
|
function findCollisions(servicesDir, pluginDirs) {
|
|
9543
9595
|
const plugins = (pluginDirs ?? subdirectories(servicesDir)).filter((name) => !name.startsWith("_")).filter((name) => name !== "api").sort();
|
|
@@ -9545,7 +9597,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
9545
9597
|
const gather = (kind, namesOf) => {
|
|
9546
9598
|
const claims = /* @__PURE__ */ new Map();
|
|
9547
9599
|
for (const plugin of plugins) {
|
|
9548
|
-
for (const name of namesOf(
|
|
9600
|
+
for (const name of namesOf(join32(servicesDir, plugin))) {
|
|
9549
9601
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
9550
9602
|
}
|
|
9551
9603
|
}
|
|
@@ -9582,9 +9634,9 @@ function formatCollisions(collisions) {
|
|
|
9582
9634
|
|
|
9583
9635
|
// src/scripts/check-plugin-collisions.ts
|
|
9584
9636
|
async function runPluginCollisionCheck() {
|
|
9585
|
-
const root = (await
|
|
9586
|
-
const servicesDir =
|
|
9587
|
-
if (!
|
|
9637
|
+
const root = (await execa8("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
9638
|
+
const servicesDir = join33(root, "services");
|
|
9639
|
+
if (!existsSync34(servicesDir)) {
|
|
9588
9640
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
9589
9641
|
return;
|
|
9590
9642
|
}
|
|
@@ -9601,11 +9653,11 @@ async function runPluginCollisionCheck() {
|
|
|
9601
9653
|
}
|
|
9602
9654
|
|
|
9603
9655
|
// src/scripts/check-plugin-terraform.ts
|
|
9604
|
-
import { execa as
|
|
9656
|
+
import { execa as execa9 } from "execa";
|
|
9605
9657
|
|
|
9606
9658
|
// src/lib/plugin-terraform-guard.ts
|
|
9607
|
-
import { existsSync as
|
|
9608
|
-
import { dirname as dirname9, join as
|
|
9659
|
+
import { existsSync as existsSync35, readFileSync as readFileSync24, readdirSync as readdirSync15 } from "fs";
|
|
9660
|
+
import { dirname as dirname9, join as join34, relative as relative6, sep as sep3 } from "path";
|
|
9609
9661
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
9610
9662
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
9611
9663
|
function findPluginManifests(root) {
|
|
@@ -9613,16 +9665,16 @@ function findPluginManifests(root) {
|
|
|
9613
9665
|
const walk = (dir) => {
|
|
9614
9666
|
let entries;
|
|
9615
9667
|
try {
|
|
9616
|
-
entries =
|
|
9668
|
+
entries = readdirSync15(dir, { withFileTypes: true });
|
|
9617
9669
|
} catch {
|
|
9618
9670
|
return;
|
|
9619
9671
|
}
|
|
9620
9672
|
for (const entry of entries) {
|
|
9621
9673
|
if (entry.isDirectory()) {
|
|
9622
9674
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
9623
|
-
walk(
|
|
9675
|
+
walk(join34(dir, entry.name));
|
|
9624
9676
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
9625
|
-
found.push(relative6(root,
|
|
9677
|
+
found.push(relative6(root, join34(dir, entry.name)).split(sep3).join("/"));
|
|
9626
9678
|
}
|
|
9627
9679
|
}
|
|
9628
9680
|
};
|
|
@@ -9647,14 +9699,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
9647
9699
|
}
|
|
9648
9700
|
function checkPluginTerraform(root) {
|
|
9649
9701
|
const violations = [];
|
|
9650
|
-
const coreManifest =
|
|
9702
|
+
const coreManifest = existsSync35(join34(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
9651
9703
|
for (const manifest of findPluginManifests(root)) {
|
|
9652
9704
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
9653
|
-
const absManifest =
|
|
9705
|
+
const absManifest = join34(root, manifest);
|
|
9654
9706
|
const subscriptions = readSubscriptions(absManifest);
|
|
9655
9707
|
if (subscriptions === null) continue;
|
|
9656
9708
|
const pluginDir2 = dirname9(absManifest);
|
|
9657
|
-
if (
|
|
9709
|
+
if (existsSync35(join34(pluginDir2, "terraform"))) continue;
|
|
9658
9710
|
const relPluginDir = relative6(root, pluginDir2).split(sep3).join("/");
|
|
9659
9711
|
violations.push({
|
|
9660
9712
|
manifest,
|
|
@@ -9674,7 +9726,7 @@ function formatViolations(violations) {
|
|
|
9674
9726
|
|
|
9675
9727
|
// src/scripts/check-plugin-terraform.ts
|
|
9676
9728
|
async function runPluginTerraformCheck() {
|
|
9677
|
-
const root = (await
|
|
9729
|
+
const root = (await execa9("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
9678
9730
|
const violations = checkPluginTerraform(root);
|
|
9679
9731
|
if (violations.length > 0) {
|
|
9680
9732
|
console.error("\u2717 plugin Terraform guard: event subscriptions with no infrastructure\n");
|
|
@@ -9685,7 +9737,7 @@ async function runPluginTerraformCheck() {
|
|
|
9685
9737
|
}
|
|
9686
9738
|
|
|
9687
9739
|
// src/scripts/check-release-subject.ts
|
|
9688
|
-
import { execa as
|
|
9740
|
+
import { execa as execa10 } from "execa";
|
|
9689
9741
|
|
|
9690
9742
|
// src/lib/release-version.ts
|
|
9691
9743
|
var MINOR_TYPES = /* @__PURE__ */ new Set(["feat"]);
|
|
@@ -9723,13 +9775,13 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
9723
9775
|
console.error("No base ref: set GITHUB_BASE_REF or pass a base branch as the first argument.");
|
|
9724
9776
|
process.exit(2);
|
|
9725
9777
|
}
|
|
9726
|
-
const root = (await
|
|
9727
|
-
await
|
|
9728
|
-
const { stdout } = await
|
|
9778
|
+
const root = (await execa10("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
9779
|
+
await execa10("git", ["fetch", "--quiet", "origin", base], { cwd: root, reject: false });
|
|
9780
|
+
const { stdout } = await execa10("git", ["diff", "--name-only", `origin/${base}...HEAD`], {
|
|
9729
9781
|
cwd: root
|
|
9730
9782
|
});
|
|
9731
9783
|
const changedFiles = stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
9732
|
-
const subject = process.env["PR_TITLE"]?.trim() || (await
|
|
9784
|
+
const subject = process.env["PR_TITLE"]?.trim() || (await execa10("git", ["log", "-1", "--format=%s"], { cwd: root })).stdout.trim();
|
|
9733
9785
|
const manifest = readCoreManifest(root);
|
|
9734
9786
|
const { unparseable, bump, templateOwnedChanges, skippedAsInstance } = checkReleaseSubject(
|
|
9735
9787
|
changedFiles,
|
|
@@ -9788,6 +9840,11 @@ checkCommand.command("plugin-collisions").description("Refuse two vendored plugi
|
|
|
9788
9840
|
checkCommand.command("plugin-terraform").description("Verify every template-owned plugin declaring infra ships a Terraform module").action(async () => {
|
|
9789
9841
|
await runPluginTerraformCheck();
|
|
9790
9842
|
});
|
|
9843
|
+
checkCommand.command("adr-numbering").description(
|
|
9844
|
+
"Refuse two ADRs in this repo's own docs/ADR/ claiming the same number (tabsii-platform#449)"
|
|
9845
|
+
).action(async () => {
|
|
9846
|
+
await runAdrNumberingCheck();
|
|
9847
|
+
});
|
|
9791
9848
|
checkCommand.command("branch-protection").description(
|
|
9792
9849
|
"Verify dev/staging/main are actually protected \u2014 scaffolding skips this on a 403 (#715)"
|
|
9793
9850
|
).option("--repo <owner/name>", "Repo to audit; defaults to this checkout's origin remote").option(
|
|
@@ -9802,8 +9859,8 @@ function rawArgsAfter(subcommand) {
|
|
|
9802
9859
|
}
|
|
9803
9860
|
|
|
9804
9861
|
// src/commands/doctor.ts
|
|
9805
|
-
import { existsSync as
|
|
9806
|
-
import { join as
|
|
9862
|
+
import { existsSync as existsSync36, readFileSync as readFileSync25 } from "fs";
|
|
9863
|
+
import { join as join35, resolve as resolve18 } from "path";
|
|
9807
9864
|
import chalk21 from "chalk";
|
|
9808
9865
|
import { Command as Command24 } from "commander";
|
|
9809
9866
|
|
|
@@ -9978,8 +10035,8 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
|
|
|
9978
10035
|
return runDoctorChecks(facts);
|
|
9979
10036
|
}
|
|
9980
10037
|
function readLocalCoreVersion(cwd) {
|
|
9981
|
-
const path =
|
|
9982
|
-
if (!
|
|
10038
|
+
const path = join35(cwd, INSTANCE_CORE_FILE);
|
|
10039
|
+
if (!existsSync36(path)) return null;
|
|
9983
10040
|
try {
|
|
9984
10041
|
return parseCoreRecord(readFileSync25(path, "utf8"));
|
|
9985
10042
|
} catch {
|
|
@@ -9996,8 +10053,8 @@ function parseCoreRecord(contents) {
|
|
|
9996
10053
|
}
|
|
9997
10054
|
}
|
|
9998
10055
|
function readFossil(cwd) {
|
|
9999
|
-
const path =
|
|
10000
|
-
if (!
|
|
10056
|
+
const path = join35(cwd, CORE_VERSION_FILE);
|
|
10057
|
+
if (!existsSync36(path)) return null;
|
|
10001
10058
|
try {
|
|
10002
10059
|
const value = readFileSync25(path, "utf8").trim();
|
|
10003
10060
|
return value === "" ? null : value;
|