@biffo/cli 0.280.0 → 0.281.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 +713 -334
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command28 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/core.ts
|
|
7
7
|
import { Command as Command4 } from "commander";
|
|
@@ -1082,6 +1082,23 @@ var GitAdapter = class {
|
|
|
1082
1082
|
const { stdout } = await execa2("git", ["remote", "get-url", remote], { cwd });
|
|
1083
1083
|
return stdout.trim();
|
|
1084
1084
|
}
|
|
1085
|
+
/**
|
|
1086
|
+
* The commit SHA `HEAD` currently resolves to on `repoUrl`'s default
|
|
1087
|
+
* branch, without cloning anything — a single `git ls-remote` round trip.
|
|
1088
|
+
* Built for plugin provenance/staleness (#1547): recording or checking a
|
|
1089
|
+
* source commit should not need a full clone when the answer is one
|
|
1090
|
+
* network call. Returns `null` on any failure (network, auth, a repo that
|
|
1091
|
+
* does not exist) rather than throwing — both call sites treat an unknown
|
|
1092
|
+
* SHA as an honest "could not determine", never as license to invent one.
|
|
1093
|
+
*/
|
|
1094
|
+
async resolveDefaultBranchSha(repoUrl) {
|
|
1095
|
+
const { stdout, exitCode } = await execa2("git", ["ls-remote", "--exit-code", repoUrl, "HEAD"], {
|
|
1096
|
+
reject: false
|
|
1097
|
+
});
|
|
1098
|
+
if (exitCode !== 0) return null;
|
|
1099
|
+
const sha = stdout.split(/\s+/)[0];
|
|
1100
|
+
return sha && /^[0-9a-f]{40}$/i.test(sha) ? sha : null;
|
|
1101
|
+
}
|
|
1085
1102
|
/**
|
|
1086
1103
|
* Prunes remote-tracking refs whose remote branch is gone, so `upstream:track`
|
|
1087
1104
|
* reports `[gone]` for a merged-and-deleted branch (#758).
|
|
@@ -1596,7 +1613,7 @@ var GitHubAdapter = class {
|
|
|
1596
1613
|
} catch (err) {
|
|
1597
1614
|
if (err.status !== 404) throw err;
|
|
1598
1615
|
}
|
|
1599
|
-
await new Promise((
|
|
1616
|
+
await new Promise((resolve21) => setTimeout(resolve21, intervalMs));
|
|
1600
1617
|
}
|
|
1601
1618
|
throw new Error(
|
|
1602
1619
|
`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.`
|
|
@@ -1611,7 +1628,7 @@ var GitHubAdapter = class {
|
|
|
1611
1628
|
} catch (err) {
|
|
1612
1629
|
if (err.status !== 404) throw err;
|
|
1613
1630
|
}
|
|
1614
|
-
await new Promise((
|
|
1631
|
+
await new Promise((resolve21) => setTimeout(resolve21, intervalMs));
|
|
1615
1632
|
}
|
|
1616
1633
|
throw new Error(
|
|
1617
1634
|
`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.`
|
|
@@ -1845,7 +1862,7 @@ var GitHubAdapter = class {
|
|
|
1845
1862
|
}
|
|
1846
1863
|
if (status !== 404 || Date.now() >= deadline) throw err;
|
|
1847
1864
|
log.info("Branch protection endpoint not yet ready, retrying...");
|
|
1848
|
-
await new Promise((
|
|
1865
|
+
await new Promise((resolve21) => setTimeout(resolve21, protectionIntervalMs));
|
|
1849
1866
|
}
|
|
1850
1867
|
}
|
|
1851
1868
|
}
|
|
@@ -2018,7 +2035,7 @@ var GitHubAdapter = class {
|
|
|
2018
2035
|
}
|
|
2019
2036
|
if (status !== 404 || Date.now() >= deadline) throw err;
|
|
2020
2037
|
log.info("Branch protection endpoint not yet ready, retrying...");
|
|
2021
|
-
await new Promise((
|
|
2038
|
+
await new Promise((resolve21) => setTimeout(resolve21, protectionIntervalMs));
|
|
2022
2039
|
}
|
|
2023
2040
|
}
|
|
2024
2041
|
} catch (err) {
|
|
@@ -2291,7 +2308,7 @@ var GitHubAdapter = class {
|
|
|
2291
2308
|
} catch (err) {
|
|
2292
2309
|
if (err.status !== 404 || Date.now() >= deadline) throw err;
|
|
2293
2310
|
log.info(`Workflow ${workflowId} not yet indexed by GitHub Actions, retrying...`);
|
|
2294
|
-
await new Promise((
|
|
2311
|
+
await new Promise((resolve21) => setTimeout(resolve21, intervalMs));
|
|
2295
2312
|
}
|
|
2296
2313
|
}
|
|
2297
2314
|
}
|
|
@@ -2310,7 +2327,7 @@ var GitHubAdapter = class {
|
|
|
2310
2327
|
} catch (err) {
|
|
2311
2328
|
if (err.status !== 404 || Date.now() >= deadline) throw err;
|
|
2312
2329
|
log.info(`Workflow ${workflowId} not yet indexed by GitHub Actions, retrying...`);
|
|
2313
|
-
await new Promise((
|
|
2330
|
+
await new Promise((resolve21) => setTimeout(resolve21, intervalMs));
|
|
2314
2331
|
}
|
|
2315
2332
|
}
|
|
2316
2333
|
}
|
|
@@ -2334,7 +2351,7 @@ var GitHubAdapter = class {
|
|
|
2334
2351
|
} else {
|
|
2335
2352
|
log.info(" Waiting for run to be queued...");
|
|
2336
2353
|
}
|
|
2337
|
-
await new Promise((
|
|
2354
|
+
await new Promise((resolve21) => setTimeout(resolve21, intervalMs));
|
|
2338
2355
|
}
|
|
2339
2356
|
throw new Error(
|
|
2340
2357
|
`Workflow ${workflowId} did not complete within ${timeoutMs / 1e3 / 60} minutes`
|
|
@@ -4301,7 +4318,7 @@ var AwsAdapter = class {
|
|
|
4301
4318
|
const code = err.Code;
|
|
4302
4319
|
if (code === "OperationAborted" && attempt < maxAttempts) {
|
|
4303
4320
|
log.info(` Waiting for S3 to release "${bucketName}"... (${attempt}/${maxAttempts})`);
|
|
4304
|
-
await new Promise((
|
|
4321
|
+
await new Promise((resolve21) => setTimeout(resolve21, retryDelayMs));
|
|
4305
4322
|
} else if (code === "OperationAborted") {
|
|
4306
4323
|
return false;
|
|
4307
4324
|
} else {
|
|
@@ -7504,7 +7521,7 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
|
|
|
7504
7521
|
}
|
|
7505
7522
|
|
|
7506
7523
|
// src/commands/plugin.ts
|
|
7507
|
-
import { Command as
|
|
7524
|
+
import { Command as Command21 } from "commander";
|
|
7508
7525
|
|
|
7509
7526
|
// src/commands/plugin-create.ts
|
|
7510
7527
|
import { existsSync as existsSync26, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
|
|
@@ -8428,8 +8445,8 @@ function printEntry(entry) {
|
|
|
8428
8445
|
}
|
|
8429
8446
|
|
|
8430
8447
|
// src/commands/plugin-install.ts
|
|
8431
|
-
import { cpSync as cpSync4, existsSync as
|
|
8432
|
-
import { join as
|
|
8448
|
+
import { cpSync as cpSync4, existsSync as existsSync29, mkdirSync as mkdirSync10, readFileSync as readFileSync22, statSync as statSync6 } from "fs";
|
|
8449
|
+
import { join as join31, relative as relative3, resolve as resolve12 } from "path";
|
|
8433
8450
|
import chalk15 from "chalk";
|
|
8434
8451
|
import { Command as Command15 } from "commander";
|
|
8435
8452
|
|
|
@@ -8475,10 +8492,90 @@ var PluginMigrationsAdapter = class {
|
|
|
8475
8492
|
}
|
|
8476
8493
|
};
|
|
8477
8494
|
|
|
8495
|
+
// src/lib/plugin-provenance.ts
|
|
8496
|
+
import { existsSync as existsSync27, readFileSync as readFileSync20, writeFileSync as writeFileSync10 } from "fs";
|
|
8497
|
+
import { join as join28 } from "path";
|
|
8498
|
+
import { execa as execa5 } from "execa";
|
|
8499
|
+
var PLUGIN_PROVENANCE_FILENAME = ".biffo-plugin-provenance.json";
|
|
8500
|
+
function isPluginProvenance(value) {
|
|
8501
|
+
if (typeof value !== "object" || value === null) return false;
|
|
8502
|
+
const v = value;
|
|
8503
|
+
return typeof v["origin"] === "string" && (typeof v["ref"] === "string" || v["ref"] === null) && (typeof v["sha"] === "string" || v["sha"] === null) && typeof v["recordedAt"] === "string" && typeof v["inTree"] === "boolean";
|
|
8504
|
+
}
|
|
8505
|
+
function readProvenance(pluginDir2) {
|
|
8506
|
+
const path = join28(pluginDir2, PLUGIN_PROVENANCE_FILENAME);
|
|
8507
|
+
if (!existsSync27(path)) return { status: "absent" };
|
|
8508
|
+
let parsed;
|
|
8509
|
+
try {
|
|
8510
|
+
parsed = JSON.parse(readFileSync20(path, "utf8"));
|
|
8511
|
+
} catch (err) {
|
|
8512
|
+
return {
|
|
8513
|
+
status: "invalid",
|
|
8514
|
+
reason: `could not parse ${PLUGIN_PROVENANCE_FILENAME}: ${err.message}`
|
|
8515
|
+
};
|
|
8516
|
+
}
|
|
8517
|
+
if (!isPluginProvenance(parsed)) {
|
|
8518
|
+
return {
|
|
8519
|
+
status: "invalid",
|
|
8520
|
+
reason: `${PLUGIN_PROVENANCE_FILENAME} does not have the expected shape`
|
|
8521
|
+
};
|
|
8522
|
+
}
|
|
8523
|
+
return { status: "present", record: parsed };
|
|
8524
|
+
}
|
|
8525
|
+
function writePluginProvenance(pluginDir2, record) {
|
|
8526
|
+
writeFileSync10(join28(pluginDir2, PLUGIN_PROVENANCE_FILENAME), `${JSON.stringify(record, null, 2)}
|
|
8527
|
+
`);
|
|
8528
|
+
}
|
|
8529
|
+
function reconcileProvenance(previous, next) {
|
|
8530
|
+
if (previous.status === "present" && sameProvenance(previous.record, next)) return previous.record;
|
|
8531
|
+
return next;
|
|
8532
|
+
}
|
|
8533
|
+
function sameProvenance(a, b) {
|
|
8534
|
+
return a.origin === b.origin && a.ref === b.ref && a.sha === b.sha && a.inTree === b.inTree;
|
|
8535
|
+
}
|
|
8536
|
+
function inTreePluginProvenance(relTargetDir) {
|
|
8537
|
+
return {
|
|
8538
|
+
origin: relTargetDir,
|
|
8539
|
+
ref: null,
|
|
8540
|
+
sha: null,
|
|
8541
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8542
|
+
inTree: true
|
|
8543
|
+
};
|
|
8544
|
+
}
|
|
8545
|
+
async function resolveLocalProvenance(sourceDir, origin) {
|
|
8546
|
+
const recordedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8547
|
+
if (!await isGitWorkingTree(sourceDir)) {
|
|
8548
|
+
return { origin, ref: null, sha: null, recordedAt, inTree: false };
|
|
8549
|
+
}
|
|
8550
|
+
const sha = await tryGit(sourceDir, ["rev-parse", "HEAD"]);
|
|
8551
|
+
const rawRef = await tryGit(sourceDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
8552
|
+
const ref = rawRef && rawRef !== "HEAD" ? rawRef : null;
|
|
8553
|
+
return { origin, ref, sha, recordedAt, inTree: false };
|
|
8554
|
+
}
|
|
8555
|
+
function resolveRegistryProvenance(repoUrl, sha) {
|
|
8556
|
+
return { origin: repoUrl, ref: null, sha, recordedAt: (/* @__PURE__ */ new Date()).toISOString(), inTree: false };
|
|
8557
|
+
}
|
|
8558
|
+
async function isGitWorkingTree(dir) {
|
|
8559
|
+
try {
|
|
8560
|
+
await execa5("git", ["rev-parse", "--is-inside-work-tree"], { cwd: dir });
|
|
8561
|
+
return true;
|
|
8562
|
+
} catch {
|
|
8563
|
+
return false;
|
|
8564
|
+
}
|
|
8565
|
+
}
|
|
8566
|
+
async function tryGit(cwd, args) {
|
|
8567
|
+
try {
|
|
8568
|
+
const { stdout } = await execa5("git", args, { cwd });
|
|
8569
|
+
return stdout.trim() || null;
|
|
8570
|
+
} catch {
|
|
8571
|
+
return null;
|
|
8572
|
+
}
|
|
8573
|
+
}
|
|
8574
|
+
|
|
8478
8575
|
// src/lib/plugin-source-copy.ts
|
|
8479
8576
|
import { copyFileSync as copyFileSync2, cpSync as cpSync3, mkdirSync as mkdirSync9 } from "fs";
|
|
8480
|
-
import { basename, dirname as dirname9, join as
|
|
8481
|
-
import { execa as
|
|
8577
|
+
import { basename, dirname as dirname9, join as join29 } from "path";
|
|
8578
|
+
import { execa as execa6 } from "execa";
|
|
8482
8579
|
var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
8483
8580
|
".git",
|
|
8484
8581
|
".venv",
|
|
@@ -8491,12 +8588,12 @@ var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
|
8491
8588
|
".terraform"
|
|
8492
8589
|
]);
|
|
8493
8590
|
async function copyPluginSource(sourceDir, targetDir) {
|
|
8494
|
-
if (await
|
|
8591
|
+
if (await isGitWorkingTree2(sourceDir)) {
|
|
8495
8592
|
const files = await listGitFiles(sourceDir);
|
|
8496
8593
|
for (const relPath of files) {
|
|
8497
|
-
const destPath =
|
|
8594
|
+
const destPath = join29(targetDir, relPath);
|
|
8498
8595
|
mkdirSync9(dirname9(destPath), { recursive: true });
|
|
8499
|
-
copyFileSync2(
|
|
8596
|
+
copyFileSync2(join29(sourceDir, relPath), destPath);
|
|
8500
8597
|
}
|
|
8501
8598
|
return { usedGitIgnoreRules: true };
|
|
8502
8599
|
}
|
|
@@ -8510,16 +8607,16 @@ async function copyPluginSource(sourceDir, targetDir) {
|
|
|
8510
8607
|
});
|
|
8511
8608
|
return { usedGitIgnoreRules: false };
|
|
8512
8609
|
}
|
|
8513
|
-
async function
|
|
8610
|
+
async function isGitWorkingTree2(dir) {
|
|
8514
8611
|
try {
|
|
8515
|
-
await
|
|
8612
|
+
await execa6("git", ["rev-parse", "--is-inside-work-tree"], { cwd: dir });
|
|
8516
8613
|
return true;
|
|
8517
8614
|
} catch {
|
|
8518
8615
|
return false;
|
|
8519
8616
|
}
|
|
8520
8617
|
}
|
|
8521
8618
|
async function listGitFiles(dir) {
|
|
8522
|
-
const { stdout } = await
|
|
8619
|
+
const { stdout } = await execa6(
|
|
8523
8620
|
"git",
|
|
8524
8621
|
["ls-files", "--cached", "--others", "--exclude-standard", "-z"],
|
|
8525
8622
|
{ cwd: dir }
|
|
@@ -8528,8 +8625,8 @@ async function listGitFiles(dir) {
|
|
|
8528
8625
|
}
|
|
8529
8626
|
|
|
8530
8627
|
// src/lib/plugin-workspace-sources.ts
|
|
8531
|
-
import { existsSync as
|
|
8532
|
-
import { join as
|
|
8628
|
+
import { existsSync as existsSync28, readdirSync as readdirSync12, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "fs";
|
|
8629
|
+
import { join as join30 } from "path";
|
|
8533
8630
|
function readTomlStringArray(text, key) {
|
|
8534
8631
|
const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
|
|
8535
8632
|
if (!open) return [];
|
|
@@ -8573,9 +8670,9 @@ function readDependencyNames(text) {
|
|
|
8573
8670
|
return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
|
|
8574
8671
|
}
|
|
8575
8672
|
function workspaceMemberNames(instanceRoot) {
|
|
8576
|
-
const rootPyproject =
|
|
8577
|
-
if (!
|
|
8578
|
-
const text =
|
|
8673
|
+
const rootPyproject = join30(instanceRoot, "pyproject.toml");
|
|
8674
|
+
if (!existsSync28(rootPyproject)) return /* @__PURE__ */ new Set();
|
|
8675
|
+
const text = readFileSync21(rootPyproject, "utf8");
|
|
8579
8676
|
const members = readTomlStringArray(text, "members");
|
|
8580
8677
|
const excluded = new Set(readTomlStringArray(text, "exclude"));
|
|
8581
8678
|
const dirs = [];
|
|
@@ -8584,7 +8681,7 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8584
8681
|
const base = member.slice(0, -2);
|
|
8585
8682
|
let entries;
|
|
8586
8683
|
try {
|
|
8587
|
-
entries = readdirSync12(
|
|
8684
|
+
entries = readdirSync12(join30(instanceRoot, base), { withFileTypes: true });
|
|
8588
8685
|
} catch {
|
|
8589
8686
|
continue;
|
|
8590
8687
|
}
|
|
@@ -8598,9 +8695,9 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8598
8695
|
}
|
|
8599
8696
|
const names = /* @__PURE__ */ new Set();
|
|
8600
8697
|
for (const dir of dirs) {
|
|
8601
|
-
const pp =
|
|
8602
|
-
if (!
|
|
8603
|
-
const name = readProjectName(
|
|
8698
|
+
const pp = join30(instanceRoot, dir, "pyproject.toml");
|
|
8699
|
+
if (!existsSync28(pp)) continue;
|
|
8700
|
+
const name = readProjectName(readFileSync21(pp, "utf8"));
|
|
8604
8701
|
if (name) names.add(name);
|
|
8605
8702
|
}
|
|
8606
8703
|
return names;
|
|
@@ -8611,8 +8708,8 @@ function existingWorkspaceSources(text) {
|
|
|
8611
8708
|
);
|
|
8612
8709
|
}
|
|
8613
8710
|
function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
|
|
8614
|
-
if (!
|
|
8615
|
-
const text =
|
|
8711
|
+
if (!existsSync28(pluginPyprojectPath) || memberNames.size === 0) return [];
|
|
8712
|
+
const text = readFileSync21(pluginPyprojectPath, "utf8");
|
|
8616
8713
|
const already = existingWorkspaceSources(text);
|
|
8617
8714
|
const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
|
|
8618
8715
|
if (toAdd.length === 0) return [];
|
|
@@ -8632,12 +8729,12 @@ ${lines.join("\n")}${text.slice(insertAt)}`;
|
|
|
8632
8729
|
${lines.join("\n")}
|
|
8633
8730
|
`;
|
|
8634
8731
|
}
|
|
8635
|
-
|
|
8732
|
+
writeFileSync11(pluginPyprojectPath, updated);
|
|
8636
8733
|
return toAdd;
|
|
8637
8734
|
}
|
|
8638
8735
|
function applyWorkspaceSources(targetDir, cwd, relTargetDir) {
|
|
8639
|
-
const pluginPyproject =
|
|
8640
|
-
if (!
|
|
8736
|
+
const pluginPyproject = join30(targetDir, "pyproject.toml");
|
|
8737
|
+
if (!existsSync28(pluginPyproject)) return;
|
|
8641
8738
|
const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(cwd));
|
|
8642
8739
|
if (sourced.length > 0) {
|
|
8643
8740
|
log.info(
|
|
@@ -8677,14 +8774,14 @@ var pluginInstallCommand = new Command15("install").description(
|
|
|
8677
8774
|
}
|
|
8678
8775
|
);
|
|
8679
8776
|
function resolveLocalPlugin(localPath) {
|
|
8680
|
-
if (!
|
|
8777
|
+
if (!existsSync29(localPath)) {
|
|
8681
8778
|
throw new Error(`--local path does not exist: ${localPath}`);
|
|
8682
8779
|
}
|
|
8683
8780
|
if (!statSync6(localPath).isDirectory()) {
|
|
8684
8781
|
throw new Error(`--local path is not a directory: ${localPath}`);
|
|
8685
8782
|
}
|
|
8686
|
-
const manifestPath =
|
|
8687
|
-
if (!
|
|
8783
|
+
const manifestPath = join31(localPath, "biffo.plugin.json");
|
|
8784
|
+
if (!existsSync29(manifestPath)) {
|
|
8688
8785
|
throw new Error(
|
|
8689
8786
|
`${localPath} does not contain a biffo.plugin.json manifest at its root \u2014 is it a plugin directory? (Scaffold one with \`biffo plugin create <name>\`.)`
|
|
8690
8787
|
);
|
|
@@ -8710,8 +8807,8 @@ function parsePluginTarget(target) {
|
|
|
8710
8807
|
async function cloneAndValidatePlugin(entry, git) {
|
|
8711
8808
|
const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
|
|
8712
8809
|
try {
|
|
8713
|
-
const manifestPath =
|
|
8714
|
-
if (!
|
|
8810
|
+
const manifestPath = join31(tmpDir, "biffo.plugin.json");
|
|
8811
|
+
if (!existsSync29(manifestPath)) {
|
|
8715
8812
|
throw new Error(
|
|
8716
8813
|
`Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
|
|
8717
8814
|
);
|
|
@@ -8739,8 +8836,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8739
8836
|
`Nothing to install. Pass a registry target (e.g. \`biffo plugin install acme-crm@1.0\`) or a local plugin directory (\`biffo plugin install --local services/acme-crm\`).`
|
|
8740
8837
|
);
|
|
8741
8838
|
}
|
|
8742
|
-
const servicesDir =
|
|
8743
|
-
if (!
|
|
8839
|
+
const servicesDir = join31(options.cwd, "services");
|
|
8840
|
+
if (!existsSync29(servicesDir)) {
|
|
8744
8841
|
throw new Error(
|
|
8745
8842
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8746
8843
|
);
|
|
@@ -8758,10 +8855,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8758
8855
|
}
|
|
8759
8856
|
const pluginName = entry ? entry.name : source.name;
|
|
8760
8857
|
const relTargetDir = pluginDir(pluginName, "third-party");
|
|
8761
|
-
const targetDir =
|
|
8762
|
-
const modulesDir =
|
|
8858
|
+
const targetDir = join31(options.cwd, relTargetDir);
|
|
8859
|
+
const modulesDir = join31(options.cwd, "modules", "plugins", pluginName);
|
|
8763
8860
|
const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
|
|
8764
|
-
if (
|
|
8861
|
+
if (existsSync29(targetDir) && !inTreeSource) {
|
|
8765
8862
|
throw new Error(
|
|
8766
8863
|
`Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
|
|
8767
8864
|
);
|
|
@@ -8800,10 +8897,13 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8800
8897
|
await copyPluginSource(source.sourceDir, targetDir);
|
|
8801
8898
|
log.success(`Installed plugin source at ${relTargetDir}/`);
|
|
8802
8899
|
}
|
|
8900
|
+
const previousProvenance = readProvenance(targetDir);
|
|
8901
|
+
const nextProvenance = inTreeSource ? inTreePluginProvenance(relTargetDir) : entry ? resolveRegistryProvenance(entry.repo, await deps.git.resolveDefaultBranchSha(entry.repo)) : await resolveLocalProvenance(source.sourceDir, source.origin);
|
|
8902
|
+
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
8803
8903
|
applyWorkspaceSources(targetDir, options.cwd, relTargetDir);
|
|
8804
8904
|
const stagePaths = [relTargetDir];
|
|
8805
|
-
const tfSourceDir =
|
|
8806
|
-
if (
|
|
8905
|
+
const tfSourceDir = join31(targetDir, "terraform");
|
|
8906
|
+
if (existsSync29(tfSourceDir)) {
|
|
8807
8907
|
mkdirSync10(modulesDir, { recursive: true });
|
|
8808
8908
|
cpSync4(tfSourceDir, modulesDir, { recursive: true });
|
|
8809
8909
|
stagePaths.push(`modules/plugins/${pluginName}`);
|
|
@@ -8860,7 +8960,7 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8860
8960
|
}
|
|
8861
8961
|
function parseManifestFile(path) {
|
|
8862
8962
|
try {
|
|
8863
|
-
return JSON.parse(
|
|
8963
|
+
return JSON.parse(readFileSync22(path, "utf8"));
|
|
8864
8964
|
} catch (err) {
|
|
8865
8965
|
throw new Error(`Could not parse ${path} as JSON: ${err.message}`);
|
|
8866
8966
|
}
|
|
@@ -8897,8 +8997,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
|
|
|
8897
8997
|
}
|
|
8898
8998
|
|
|
8899
8999
|
// src/commands/plugin-list.ts
|
|
8900
|
-
import { existsSync as
|
|
8901
|
-
import { join as
|
|
9000
|
+
import { existsSync as existsSync30, readFileSync as readFileSync23 } from "fs";
|
|
9001
|
+
import { join as join32, resolve as resolve13 } from "path";
|
|
8902
9002
|
import chalk16 from "chalk";
|
|
8903
9003
|
import { Command as Command16 } from "commander";
|
|
8904
9004
|
var pluginListCommand = new Command16("list").description("List plugins installed in this project checkout").option("--cwd <path>", "Project root to scan (defaults to the current directory)").action(async (options) => {
|
|
@@ -8911,8 +9011,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
|
|
|
8911
9011
|
}
|
|
8912
9012
|
});
|
|
8913
9013
|
async function runPluginList(options) {
|
|
8914
|
-
const servicesDir =
|
|
8915
|
-
if (!
|
|
9014
|
+
const servicesDir = join32(options.cwd, "services");
|
|
9015
|
+
if (!existsSync30(servicesDir)) {
|
|
8916
9016
|
throw new Error(
|
|
8917
9017
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8918
9018
|
);
|
|
@@ -8920,7 +9020,7 @@ async function runPluginList(options) {
|
|
|
8920
9020
|
const plugins = [];
|
|
8921
9021
|
for (const location of findInstalledPlugins(options.cwd)) {
|
|
8922
9022
|
try {
|
|
8923
|
-
const manifest = validateManifest(JSON.parse(
|
|
9023
|
+
const manifest = validateManifest(JSON.parse(readFileSync23(location.manifestPath, "utf8")));
|
|
8924
9024
|
plugins.push({
|
|
8925
9025
|
name: manifest.name,
|
|
8926
9026
|
version: manifest.version,
|
|
@@ -8956,16 +9056,275 @@ async function runPluginList(options) {
|
|
|
8956
9056
|
);
|
|
8957
9057
|
}
|
|
8958
9058
|
|
|
9059
|
+
// src/commands/plugin-staleness.ts
|
|
9060
|
+
import { resolve as resolve14 } from "path";
|
|
9061
|
+
import { Command as Command17 } from "commander";
|
|
9062
|
+
|
|
9063
|
+
// src/lib/plugin-staleness.ts
|
|
9064
|
+
import { existsSync as existsSync31, readFileSync as readFileSync24, readdirSync as readdirSync13, statSync as statSync7 } from "fs";
|
|
9065
|
+
import { join as join33, relative as relative4 } from "path";
|
|
9066
|
+
function discoverVendoredPlugins(servicesDir) {
|
|
9067
|
+
if (!existsSync31(servicesDir)) return [];
|
|
9068
|
+
return readdirSync13(servicesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_") && e.name !== "api").map((e) => e.name).filter((name) => existsSync31(join33(servicesDir, name, "biffo.plugin.json"))).sort();
|
|
9069
|
+
}
|
|
9070
|
+
async function checkPluginStaleness(cwd, deps) {
|
|
9071
|
+
const servicesDir = join33(cwd, "services");
|
|
9072
|
+
const names = discoverVendoredPlugins(servicesDir);
|
|
9073
|
+
let registryRepoByName = null;
|
|
9074
|
+
const resolveRegistryRepo = async (name) => {
|
|
9075
|
+
if (registryRepoByName === null) {
|
|
9076
|
+
registryRepoByName = /* @__PURE__ */ new Map();
|
|
9077
|
+
try {
|
|
9078
|
+
const reg = await deps.registry.fetchRegistry();
|
|
9079
|
+
for (const entry of reg.plugins) registryRepoByName.set(entry.name, entry.repo);
|
|
9080
|
+
} catch {
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
return registryRepoByName.get(name) ?? null;
|
|
9084
|
+
};
|
|
9085
|
+
const results = [];
|
|
9086
|
+
for (const name of names) {
|
|
9087
|
+
results.push(await checkOnePlugin(join33(servicesDir, name), name, resolveRegistryRepo, deps.git));
|
|
9088
|
+
}
|
|
9089
|
+
return results;
|
|
9090
|
+
}
|
|
9091
|
+
async function checkOnePlugin(pluginDir2, name, resolveRegistryRepo, git) {
|
|
9092
|
+
const provenance = readProvenance(pluginDir2);
|
|
9093
|
+
if (provenance.status === "invalid") {
|
|
9094
|
+
return {
|
|
9095
|
+
name,
|
|
9096
|
+
status: "cannot-tell",
|
|
9097
|
+
method: "unresolvable",
|
|
9098
|
+
detail: `provenance file is unreadable \u2014 ${provenance.reason}`
|
|
9099
|
+
};
|
|
9100
|
+
}
|
|
9101
|
+
const record = provenance.status === "present" ? provenance.record : null;
|
|
9102
|
+
if (record?.inTree) {
|
|
9103
|
+
return {
|
|
9104
|
+
name,
|
|
9105
|
+
status: "cannot-tell",
|
|
9106
|
+
method: "unresolvable",
|
|
9107
|
+
detail: `installed --local straight into services/${name}/ (in-tree) \u2014 there is no external plugin repo to compare against`
|
|
9108
|
+
};
|
|
9109
|
+
}
|
|
9110
|
+
if (record?.sha && isFetchableUrl(record.origin)) {
|
|
9111
|
+
return checkViaProvenance(name, record, record.origin, git);
|
|
9112
|
+
}
|
|
9113
|
+
const localOrigin = record && !isFetchableUrl(record.origin) && existsSync31(record.origin) ? record.origin : null;
|
|
9114
|
+
if (localOrigin) {
|
|
9115
|
+
return checkViaContentDiff(name, pluginDir2, localOrigin, { isLocalDir: true }, git);
|
|
9116
|
+
}
|
|
9117
|
+
const registryRepo = await resolveRegistryRepo(name);
|
|
9118
|
+
if (!registryRepo) {
|
|
9119
|
+
return {
|
|
9120
|
+
name,
|
|
9121
|
+
status: "cannot-tell",
|
|
9122
|
+
method: "unresolvable",
|
|
9123
|
+
detail: record ? `provenance records origin '${record.origin}', which is neither a reachable git URL nor a local directory that still exists, and '${name}' was not found in the plugin registry either` : `no provenance recorded (vendored before #1547, or an unreachable registry) and '${name}' was not found in the plugin registry \u2014 nothing to compare against`
|
|
9124
|
+
};
|
|
9125
|
+
}
|
|
9126
|
+
if (record?.sha) {
|
|
9127
|
+
return checkViaProvenance(name, record, registryRepo, git);
|
|
9128
|
+
}
|
|
9129
|
+
return checkViaContentDiff(name, pluginDir2, registryRepo, { isLocalDir: false }, git);
|
|
9130
|
+
}
|
|
9131
|
+
function isFetchableUrl(origin) {
|
|
9132
|
+
return /^(https?|git|ssh):\/\//.test(origin) || /^[^/\\]+@[^:]+:/.test(origin);
|
|
9133
|
+
}
|
|
9134
|
+
async function checkViaProvenance(name, record, repoUrl, git) {
|
|
9135
|
+
const remoteHeadSha = await git.resolveDefaultBranchSha(repoUrl);
|
|
9136
|
+
if (!remoteHeadSha) {
|
|
9137
|
+
return {
|
|
9138
|
+
name,
|
|
9139
|
+
status: "cannot-tell",
|
|
9140
|
+
method: "unresolvable",
|
|
9141
|
+
detail: `could not reach ${repoUrl} (network or authentication failure)`
|
|
9142
|
+
};
|
|
9143
|
+
}
|
|
9144
|
+
if (remoteHeadSha === record.sha) {
|
|
9145
|
+
return {
|
|
9146
|
+
name,
|
|
9147
|
+
status: "up-to-date",
|
|
9148
|
+
method: "provenance",
|
|
9149
|
+
detail: `matches ${repoUrl}'s default branch (${shortSha(remoteHeadSha)})`
|
|
9150
|
+
};
|
|
9151
|
+
}
|
|
9152
|
+
let clone;
|
|
9153
|
+
try {
|
|
9154
|
+
clone = await git.cloneForEditing(repoUrl, "biffo-plugin-staleness-full");
|
|
9155
|
+
} catch {
|
|
9156
|
+
return {
|
|
9157
|
+
name,
|
|
9158
|
+
status: "cannot-tell",
|
|
9159
|
+
method: "unresolvable",
|
|
9160
|
+
detail: `could not clone ${repoUrl} to count commits behind (network or authentication failure)`
|
|
9161
|
+
};
|
|
9162
|
+
}
|
|
9163
|
+
try {
|
|
9164
|
+
const commitsBehind = await git.countBehind(clone, record.sha, "HEAD");
|
|
9165
|
+
if (commitsBehind === null) {
|
|
9166
|
+
return {
|
|
9167
|
+
name,
|
|
9168
|
+
status: "cannot-tell",
|
|
9169
|
+
method: "unresolvable",
|
|
9170
|
+
detail: `recorded commit ${shortSha(record.sha)} was not found in ${repoUrl}'s history (rebased or force-pushed?) \u2014 cannot count commits behind`
|
|
9171
|
+
};
|
|
9172
|
+
}
|
|
9173
|
+
if (commitsBehind === 0) {
|
|
9174
|
+
return {
|
|
9175
|
+
name,
|
|
9176
|
+
status: "up-to-date",
|
|
9177
|
+
method: "provenance",
|
|
9178
|
+
detail: `matches ${repoUrl}'s default branch (${shortSha(remoteHeadSha)})`
|
|
9179
|
+
};
|
|
9180
|
+
}
|
|
9181
|
+
return {
|
|
9182
|
+
name,
|
|
9183
|
+
status: "behind",
|
|
9184
|
+
commitsBehind,
|
|
9185
|
+
method: "provenance",
|
|
9186
|
+
detail: `${commitsBehind} commit(s) behind ${repoUrl}'s default branch`
|
|
9187
|
+
};
|
|
9188
|
+
} finally {
|
|
9189
|
+
git.cleanup(clone);
|
|
9190
|
+
}
|
|
9191
|
+
}
|
|
9192
|
+
async function checkViaContentDiff(name, pluginDir2, sourceDir, opts, git) {
|
|
9193
|
+
let cloneDir = null;
|
|
9194
|
+
let effectiveSourceDir = sourceDir;
|
|
9195
|
+
if (!opts.isLocalDir) {
|
|
9196
|
+
try {
|
|
9197
|
+
cloneDir = await git.cloneToTemp(sourceDir, "biffo-plugin-staleness-content");
|
|
9198
|
+
} catch {
|
|
9199
|
+
return {
|
|
9200
|
+
name,
|
|
9201
|
+
status: "cannot-tell",
|
|
9202
|
+
method: "unresolvable",
|
|
9203
|
+
detail: `could not clone ${sourceDir} for a content comparison (network or authentication failure)`
|
|
9204
|
+
};
|
|
9205
|
+
}
|
|
9206
|
+
effectiveSourceDir = cloneDir;
|
|
9207
|
+
}
|
|
9208
|
+
try {
|
|
9209
|
+
const filesDiffering = await countDifferingFiles(effectiveSourceDir, pluginDir2);
|
|
9210
|
+
const originDescription = opts.isLocalDir ? effectiveSourceDir : sourceDir;
|
|
9211
|
+
if (filesDiffering === 0) {
|
|
9212
|
+
return {
|
|
9213
|
+
name,
|
|
9214
|
+
status: "up-to-date",
|
|
9215
|
+
method: "content-diff",
|
|
9216
|
+
filesDiffering: 0,
|
|
9217
|
+
detail: `byte-identical to ${originDescription} (no provenance recorded, so an exact commit could not be named)`
|
|
9218
|
+
};
|
|
9219
|
+
}
|
|
9220
|
+
return {
|
|
9221
|
+
name,
|
|
9222
|
+
status: "behind",
|
|
9223
|
+
filesDiffering,
|
|
9224
|
+
method: "content-diff",
|
|
9225
|
+
detail: `${filesDiffering} file(s) differ from ${originDescription} (no provenance recorded, so an exact commit count could not be determined)`
|
|
9226
|
+
};
|
|
9227
|
+
} finally {
|
|
9228
|
+
if (cloneDir) git.cleanup(cloneDir);
|
|
9229
|
+
}
|
|
9230
|
+
}
|
|
9231
|
+
function shortSha(sha) {
|
|
9232
|
+
return sha.slice(0, 7);
|
|
9233
|
+
}
|
|
9234
|
+
async function countDifferingFiles(sourceDir, pluginDir2) {
|
|
9235
|
+
const sourceFiles = await sourceFileList(sourceDir);
|
|
9236
|
+
const vendorFiles = vendorFileList(pluginDir2);
|
|
9237
|
+
const allPaths = /* @__PURE__ */ new Set([...sourceFiles, ...vendorFiles]);
|
|
9238
|
+
let differing = 0;
|
|
9239
|
+
for (const relPath of allPaths) {
|
|
9240
|
+
if (relPath === PLUGIN_PROVENANCE_FILENAME) continue;
|
|
9241
|
+
const inSource = sourceFiles.has(relPath);
|
|
9242
|
+
const inVendor = vendorFiles.has(relPath);
|
|
9243
|
+
if (!inSource || !inVendor) {
|
|
9244
|
+
differing++;
|
|
9245
|
+
continue;
|
|
9246
|
+
}
|
|
9247
|
+
const a = readFileSync24(join33(sourceDir, relPath));
|
|
9248
|
+
const b = readFileSync24(join33(pluginDir2, relPath));
|
|
9249
|
+
if (!a.equals(b)) differing++;
|
|
9250
|
+
}
|
|
9251
|
+
return differing;
|
|
9252
|
+
}
|
|
9253
|
+
async function sourceFileList(dir) {
|
|
9254
|
+
if (await isGitWorkingTree2(dir)) {
|
|
9255
|
+
return new Set(await listGitFiles(dir));
|
|
9256
|
+
}
|
|
9257
|
+
return new Set(walkExcluding(dir, dir, LOCAL_COPY_EXCLUDES));
|
|
9258
|
+
}
|
|
9259
|
+
function vendorFileList(dir) {
|
|
9260
|
+
return new Set(walkExcluding(dir, dir, LOCAL_COPY_EXCLUDES));
|
|
9261
|
+
}
|
|
9262
|
+
function walkExcluding(root, dir, excludes) {
|
|
9263
|
+
if (!existsSync31(dir)) return [];
|
|
9264
|
+
const out = [];
|
|
9265
|
+
for (const entry of readdirSync13(dir)) {
|
|
9266
|
+
if (excludes.has(entry) || entry === ".git") continue;
|
|
9267
|
+
const full = join33(dir, entry);
|
|
9268
|
+
const stat = statSync7(full);
|
|
9269
|
+
if (stat.isDirectory()) {
|
|
9270
|
+
out.push(...walkExcluding(root, full, excludes));
|
|
9271
|
+
} else {
|
|
9272
|
+
out.push(relative4(root, full));
|
|
9273
|
+
}
|
|
9274
|
+
}
|
|
9275
|
+
return out;
|
|
9276
|
+
}
|
|
9277
|
+
function exitCodeForStaleness(results) {
|
|
9278
|
+
if (results.some((r) => r.status === "cannot-tell")) return 2;
|
|
9279
|
+
if (results.some((r) => r.status === "behind")) return 1;
|
|
9280
|
+
return 0;
|
|
9281
|
+
}
|
|
9282
|
+
function formatStalenessReport(results) {
|
|
9283
|
+
if (results.length === 0) {
|
|
9284
|
+
return " No vendored plugins under services/ \u2014 nothing to check.";
|
|
9285
|
+
}
|
|
9286
|
+
const lines = [""];
|
|
9287
|
+
for (const r of results) {
|
|
9288
|
+
const icon = r.status === "up-to-date" ? "\u2713" : r.status === "behind" ? "\u26A0" : "?";
|
|
9289
|
+
lines.push(` ${icon} ${r.name}: ${labelFor(r.status)} \u2014 ${r.detail}`);
|
|
9290
|
+
}
|
|
9291
|
+
lines.push("");
|
|
9292
|
+
return lines.join("\n");
|
|
9293
|
+
}
|
|
9294
|
+
function labelFor(status) {
|
|
9295
|
+
switch (status) {
|
|
9296
|
+
case "up-to-date":
|
|
9297
|
+
return "up to date";
|
|
9298
|
+
case "behind":
|
|
9299
|
+
return "BEHIND";
|
|
9300
|
+
case "cannot-tell":
|
|
9301
|
+
return "CANNOT TELL";
|
|
9302
|
+
}
|
|
9303
|
+
}
|
|
9304
|
+
|
|
9305
|
+
// src/commands/plugin-staleness.ts
|
|
9306
|
+
var pluginStalenessCommand = new Command17("staleness").description(
|
|
9307
|
+
"Report how far each services/<name>/ vendored plugin has drifted from its source (#1547). Exits 0 up to date, 1 behind, 2 cannot tell \u2014 2 is never a pass."
|
|
9308
|
+
).option("--cwd <path>", "Project root to check (defaults to the current directory)").action(async (options) => {
|
|
9309
|
+
const cwd = options.cwd ? resolve14(options.cwd) : process.cwd();
|
|
9310
|
+
const results = await checkPluginStaleness(cwd, {
|
|
9311
|
+
registry: new RegistryAdapter(),
|
|
9312
|
+
git: new GitAdapter()
|
|
9313
|
+
});
|
|
9314
|
+
console.log(formatStalenessReport(results));
|
|
9315
|
+
process.exit(exitCodeForStaleness(results));
|
|
9316
|
+
});
|
|
9317
|
+
|
|
8959
9318
|
// src/commands/plugin-sync-migrations.ts
|
|
8960
|
-
import { existsSync as
|
|
8961
|
-
import { join as
|
|
9319
|
+
import { existsSync as existsSync32 } from "fs";
|
|
9320
|
+
import { join as join34, relative as relative5, resolve as resolve15 } from "path";
|
|
8962
9321
|
import chalk17 from "chalk";
|
|
8963
|
-
import { Command as
|
|
8964
|
-
var pluginSyncMigrationsCommand = new
|
|
9322
|
+
import { Command as Command18 } from "commander";
|
|
9323
|
+
var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
8965
9324
|
"Generate real, committed migration file(s) for installed-but-not-yet-migrated plugin(s): biffo plugin sync-migrations [name]"
|
|
8966
9325
|
).argument("[name]", "Restrict to this installed plugin (default: every plugin under services/)").option("--dry-run", "Generate nothing; just report what would be generated").option("--no-commit", "Generate and stage the file(s) but do not commit").option("--cwd <path>", "Project root (defaults to the current directory)").action(
|
|
8967
9326
|
async (name, options) => {
|
|
8968
|
-
const cwd = options.cwd ?
|
|
9327
|
+
const cwd = options.cwd ? resolve15(options.cwd) : process.cwd();
|
|
8969
9328
|
try {
|
|
8970
9329
|
await runPluginSyncMigrations(
|
|
8971
9330
|
name,
|
|
@@ -8979,11 +9338,11 @@ var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
|
|
|
8979
9338
|
}
|
|
8980
9339
|
);
|
|
8981
9340
|
async function runPluginSyncMigrations(name, options, deps) {
|
|
8982
|
-
const servicesDir =
|
|
8983
|
-
if (!
|
|
9341
|
+
const servicesDir = join34(options.cwd, "services");
|
|
9342
|
+
if (!existsSync32(servicesDir)) {
|
|
8984
9343
|
throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
|
|
8985
9344
|
}
|
|
8986
|
-
if (name && !
|
|
9345
|
+
if (name && !existsSync32(join34(servicesDir, name, "biffo.plugin.json"))) {
|
|
8987
9346
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
8988
9347
|
}
|
|
8989
9348
|
if (options.dryRun) {
|
|
@@ -8999,7 +9358,7 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
8999
9358
|
);
|
|
9000
9359
|
return;
|
|
9001
9360
|
}
|
|
9002
|
-
const relativePaths = generated.map((p) =>
|
|
9361
|
+
const relativePaths = generated.map((p) => relative5(options.cwd, p));
|
|
9003
9362
|
for (const p of relativePaths) {
|
|
9004
9363
|
log.success(`Generated ${p}`);
|
|
9005
9364
|
}
|
|
@@ -9019,18 +9378,18 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
9019
9378
|
}
|
|
9020
9379
|
|
|
9021
9380
|
// src/commands/plugin-uninstall.ts
|
|
9022
|
-
import { existsSync as
|
|
9023
|
-
import { join as
|
|
9381
|
+
import { existsSync as existsSync33, readFileSync as readFileSync25, rmSync as rmSync8 } from "fs";
|
|
9382
|
+
import { join as join35, resolve as resolve16 } from "path";
|
|
9024
9383
|
import chalk18 from "chalk";
|
|
9025
|
-
import { Command as
|
|
9384
|
+
import { Command as Command19 } from "commander";
|
|
9026
9385
|
import inquirer6 from "inquirer";
|
|
9027
9386
|
var NAME_PATTERN2 = /^[a-z][a-z0-9-]*$/;
|
|
9028
|
-
var pluginUninstallCommand = new
|
|
9387
|
+
var pluginUninstallCommand = new Command19("uninstall").description("Remove an installed plugin: biffo plugin uninstall <name>").argument("<name>", "Plugin name").option("--dry-run", "Print planned changes without modifying the repo").option("--force", "Skip the confirmation prompt").option(
|
|
9029
9388
|
"--keep-data",
|
|
9030
9389
|
"No-op today (see notes) \u2014 the CLI never drops plugin data regardless of this flag"
|
|
9031
9390
|
).option("--cwd <path>", "Project root to uninstall from (defaults to the current directory)").action(
|
|
9032
9391
|
async (name, options) => {
|
|
9033
|
-
const cwd = options.cwd ?
|
|
9392
|
+
const cwd = options.cwd ? resolve16(options.cwd) : process.cwd();
|
|
9034
9393
|
try {
|
|
9035
9394
|
await runPluginUninstall(
|
|
9036
9395
|
name,
|
|
@@ -9052,16 +9411,16 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9052
9411
|
if (!NAME_PATTERN2.test(name)) {
|
|
9053
9412
|
throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
|
|
9054
9413
|
}
|
|
9055
|
-
const servicesDir =
|
|
9056
|
-
if (!
|
|
9414
|
+
const servicesDir = join35(options.cwd, "services");
|
|
9415
|
+
if (!existsSync33(servicesDir)) {
|
|
9057
9416
|
throw new Error(
|
|
9058
9417
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9059
9418
|
);
|
|
9060
9419
|
}
|
|
9061
|
-
const targetDir =
|
|
9062
|
-
if (!
|
|
9063
|
-
const firstParty =
|
|
9064
|
-
if (
|
|
9420
|
+
const targetDir = join35(servicesDir, name);
|
|
9421
|
+
if (!existsSync33(targetDir)) {
|
|
9422
|
+
const firstParty = join35(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
|
|
9423
|
+
if (existsSync33(firstParty)) {
|
|
9065
9424
|
throw new Error(
|
|
9066
9425
|
`Plugin '${name}' is a first-party plugin at ${pluginDir(name, "first-party")}/, which is template-owned \u2014 \`biffo core upgrade\` would restore it on the next upgrade. Disable it instead by removing '${name}' from \`enabled_plugins\` in infra/environments/<env>/main.tf and re-applying.`
|
|
9067
9426
|
);
|
|
@@ -9069,9 +9428,9 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9069
9428
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
9070
9429
|
}
|
|
9071
9430
|
const version = readInstalledVersion(targetDir);
|
|
9072
|
-
const modulesDir =
|
|
9431
|
+
const modulesDir = join35(options.cwd, "modules", "plugins", name);
|
|
9073
9432
|
const stagePaths = [`services/${name}`];
|
|
9074
|
-
if (
|
|
9433
|
+
if (existsSync33(modulesDir)) {
|
|
9075
9434
|
stagePaths.push(`modules/plugins/${name}`);
|
|
9076
9435
|
}
|
|
9077
9436
|
if (options.dryRun) {
|
|
@@ -9093,7 +9452,7 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9093
9452
|
}
|
|
9094
9453
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
9095
9454
|
log.success(`Removed services/${name}/`);
|
|
9096
|
-
if (
|
|
9455
|
+
if (existsSync33(modulesDir)) {
|
|
9097
9456
|
rmSync8(modulesDir, { recursive: true, force: true });
|
|
9098
9457
|
log.success(`Removed modules/plugins/${name}/`);
|
|
9099
9458
|
const wiring = syncPluginTerraform(options.cwd);
|
|
@@ -9130,10 +9489,10 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9130
9489
|
}
|
|
9131
9490
|
}
|
|
9132
9491
|
function readInstalledVersion(targetDir) {
|
|
9133
|
-
const manifestPath =
|
|
9134
|
-
if (!
|
|
9492
|
+
const manifestPath = join35(targetDir, "biffo.plugin.json");
|
|
9493
|
+
if (!existsSync33(manifestPath)) return void 0;
|
|
9135
9494
|
try {
|
|
9136
|
-
return validateManifest(JSON.parse(
|
|
9495
|
+
return validateManifest(JSON.parse(readFileSync25(manifestPath, "utf8"))).version;
|
|
9137
9496
|
} catch {
|
|
9138
9497
|
return void 0;
|
|
9139
9498
|
}
|
|
@@ -9166,12 +9525,12 @@ function printDryRun5(name, version, stagePaths, keepData) {
|
|
|
9166
9525
|
}
|
|
9167
9526
|
|
|
9168
9527
|
// src/commands/plugin-upgrade.ts
|
|
9169
|
-
import { cpSync as cpSync5, existsSync as
|
|
9170
|
-
import { join as
|
|
9528
|
+
import { cpSync as cpSync5, existsSync as existsSync34, mkdirSync as mkdirSync11, readFileSync as readFileSync26, rmSync as rmSync9 } from "fs";
|
|
9529
|
+
import { join as join36, relative as relative6, resolve as resolve17 } from "path";
|
|
9171
9530
|
import chalk19 from "chalk";
|
|
9172
|
-
import { Command as
|
|
9531
|
+
import { Command as Command20 } from "commander";
|
|
9173
9532
|
import inquirer7 from "inquirer";
|
|
9174
|
-
var pluginUpgradeCommand = new
|
|
9533
|
+
var pluginUpgradeCommand = new Command20("upgrade").description(
|
|
9175
9534
|
"Upgrade an installed plugin to a new minor version (biffo plugin upgrade <name>@<new-minor>) or refresh it in place from a local, unpublished checkout (biffo plugin upgrade --local <path>)"
|
|
9176
9535
|
).argument(
|
|
9177
9536
|
"[target]",
|
|
@@ -9181,12 +9540,12 @@ var pluginUpgradeCommand = new Command19("upgrade").description(
|
|
|
9181
9540
|
"Refresh the installed plugin from a local, unpublished checkout instead of the registry"
|
|
9182
9541
|
).option("--dry-run", "Resolve the new version and print planned changes without applying them").option("--force", "Skip the confirmation prompt").option("--cwd <path>", "Project root to upgrade in (defaults to the current directory)").action(
|
|
9183
9542
|
async (target, options) => {
|
|
9184
|
-
const cwd = options.cwd ?
|
|
9543
|
+
const cwd = options.cwd ? resolve17(options.cwd) : process.cwd();
|
|
9185
9544
|
try {
|
|
9186
9545
|
await runPluginUpgrade(
|
|
9187
9546
|
target,
|
|
9188
9547
|
{
|
|
9189
|
-
...options.local ? { local:
|
|
9548
|
+
...options.local ? { local: resolve17(options.local) } : {},
|
|
9190
9549
|
dryRun: options.dryRun ?? false,
|
|
9191
9550
|
force: options.force ?? false,
|
|
9192
9551
|
cwd
|
|
@@ -9214,8 +9573,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9214
9573
|
`Nothing to upgrade. Pass a registry target (e.g. \`biffo plugin upgrade acme-crm@1.1\`) or a local checkout to refresh from (\`biffo plugin upgrade --local ../acme-crm\`).`
|
|
9215
9574
|
);
|
|
9216
9575
|
}
|
|
9217
|
-
const servicesDir =
|
|
9218
|
-
if (!
|
|
9576
|
+
const servicesDir = join36(options.cwd, "services");
|
|
9577
|
+
if (!existsSync34(servicesDir)) {
|
|
9219
9578
|
throw new Error(
|
|
9220
9579
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9221
9580
|
);
|
|
@@ -9224,8 +9583,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9224
9583
|
return runLocalPluginRefresh(options.local, options, deps);
|
|
9225
9584
|
}
|
|
9226
9585
|
const { name, minor } = parsePluginTarget(target);
|
|
9227
|
-
const targetDir =
|
|
9228
|
-
if (!
|
|
9586
|
+
const targetDir = join36(servicesDir, name);
|
|
9587
|
+
if (!existsSync34(targetDir)) {
|
|
9229
9588
|
throw new Error(
|
|
9230
9589
|
`Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
|
|
9231
9590
|
);
|
|
@@ -9239,7 +9598,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9239
9598
|
`Plugin declares required_core_version '${entry.required_core_version}'. The CLI cannot verify this against your deployment \u2014 the Core API exposes no version endpoint and services/api/pyproject.toml's version is a static placeholder, not a real release marker. Confirm compatibility yourself before deploying.`
|
|
9240
9599
|
);
|
|
9241
9600
|
}
|
|
9242
|
-
const modulesDir =
|
|
9601
|
+
const modulesDir = join36(options.cwd, "modules", "plugins", entry.name);
|
|
9243
9602
|
if (options.dryRun) {
|
|
9244
9603
|
printDryRun6(entry, currentVersion);
|
|
9245
9604
|
return;
|
|
@@ -9267,17 +9626,23 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9267
9626
|
log.success(
|
|
9268
9627
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
9269
9628
|
);
|
|
9629
|
+
const previousProvenance = readProvenance(targetDir);
|
|
9270
9630
|
rmSync9(targetDir, { recursive: true, force: true });
|
|
9271
9631
|
mkdirSync11(targetDir, { recursive: true });
|
|
9272
9632
|
cpSync5(tmpDir, targetDir, { recursive: true });
|
|
9273
9633
|
log.success(`Upgraded plugin source at services/${entry.name}/`);
|
|
9634
|
+
const nextProvenance = resolveRegistryProvenance(
|
|
9635
|
+
entry.repo,
|
|
9636
|
+
await deps.git.resolveDefaultBranchSha(entry.repo)
|
|
9637
|
+
);
|
|
9638
|
+
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
9274
9639
|
applyWorkspaceSources(targetDir, options.cwd, `services/${entry.name}`);
|
|
9275
9640
|
const stagePaths = [`services/${entry.name}`];
|
|
9276
|
-
if (
|
|
9641
|
+
if (existsSync34(modulesDir)) {
|
|
9277
9642
|
rmSync9(modulesDir, { recursive: true, force: true });
|
|
9278
9643
|
}
|
|
9279
|
-
const tfSourceDir =
|
|
9280
|
-
if (
|
|
9644
|
+
const tfSourceDir = join36(targetDir, "terraform");
|
|
9645
|
+
if (existsSync34(tfSourceDir)) {
|
|
9281
9646
|
mkdirSync11(modulesDir, { recursive: true });
|
|
9282
9647
|
cpSync5(tfSourceDir, modulesDir, { recursive: true });
|
|
9283
9648
|
stagePaths.push(`modules/plugins/${entry.name}`);
|
|
@@ -9289,10 +9654,10 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9289
9654
|
);
|
|
9290
9655
|
const generatedPaths = await deps.migrations.generate(options.cwd, [entry.name]);
|
|
9291
9656
|
for (const absPath of generatedPaths) {
|
|
9292
|
-
stagePaths.push(
|
|
9657
|
+
stagePaths.push(relative6(options.cwd, absPath));
|
|
9293
9658
|
}
|
|
9294
9659
|
if (generatedPaths.length > 0) {
|
|
9295
|
-
log.success(`Generated migration: ${
|
|
9660
|
+
log.success(`Generated migration: ${relative6(options.cwd, generatedPaths[0])}`);
|
|
9296
9661
|
} else {
|
|
9297
9662
|
log.info(
|
|
9298
9663
|
`${entry.name}'s tables and columns already match the manifest \u2014 no migration needed.`
|
|
@@ -9317,16 +9682,16 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9317
9682
|
async function runLocalPluginRefresh(localPath, options, deps) {
|
|
9318
9683
|
const source = resolveLocalPlugin(localPath);
|
|
9319
9684
|
log.success(`Resolved ${source.name}@${source.version} from ${source.origin}`);
|
|
9320
|
-
const servicesDir =
|
|
9321
|
-
const targetDir =
|
|
9322
|
-
if (!
|
|
9685
|
+
const servicesDir = join36(options.cwd, "services");
|
|
9686
|
+
const targetDir = join36(servicesDir, source.name);
|
|
9687
|
+
if (!existsSync34(targetDir)) {
|
|
9323
9688
|
throw new Error(
|
|
9324
9689
|
`Plugin '${source.name}' is not installed at services/${source.name}/. Use 'biffo plugin install --local ${localPath}' instead.`
|
|
9325
9690
|
);
|
|
9326
9691
|
}
|
|
9327
|
-
const inTreeSource =
|
|
9692
|
+
const inTreeSource = resolve17(source.sourceDir) === resolve17(targetDir);
|
|
9328
9693
|
const currentVersion = readInstalledVersion2(targetDir);
|
|
9329
|
-
const modulesDir =
|
|
9694
|
+
const modulesDir = join36(options.cwd, "modules", "plugins", source.name);
|
|
9330
9695
|
if (options.dryRun) {
|
|
9331
9696
|
printLocalDryRun(source, currentVersion, inTreeSource);
|
|
9332
9697
|
return;
|
|
@@ -9349,6 +9714,7 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9349
9714
|
log.success(
|
|
9350
9715
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
9351
9716
|
);
|
|
9717
|
+
const previousProvenance = readProvenance(targetDir);
|
|
9352
9718
|
if (inTreeSource) {
|
|
9353
9719
|
log.info(
|
|
9354
9720
|
`services/${source.name}/ is already the local checkout \u2014 nothing to copy; re-syncing its Terraform module and checking for a migration.`
|
|
@@ -9359,13 +9725,15 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9359
9725
|
await copyPluginSource(source.sourceDir, targetDir);
|
|
9360
9726
|
log.success(`Refreshed plugin source at services/${source.name}/ from ${source.origin}`);
|
|
9361
9727
|
}
|
|
9728
|
+
const nextProvenance = inTreeSource ? inTreePluginProvenance(`services/${source.name}`) : await resolveLocalProvenance(source.sourceDir, source.origin);
|
|
9729
|
+
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
9362
9730
|
applyWorkspaceSources(targetDir, options.cwd, `services/${source.name}`);
|
|
9363
9731
|
const stagePaths = [`services/${source.name}`];
|
|
9364
|
-
if (
|
|
9732
|
+
if (existsSync34(modulesDir)) {
|
|
9365
9733
|
rmSync9(modulesDir, { recursive: true, force: true });
|
|
9366
9734
|
}
|
|
9367
|
-
const tfSourceDir =
|
|
9368
|
-
if (
|
|
9735
|
+
const tfSourceDir = join36(targetDir, "terraform");
|
|
9736
|
+
if (existsSync34(tfSourceDir)) {
|
|
9369
9737
|
mkdirSync11(modulesDir, { recursive: true });
|
|
9370
9738
|
cpSync5(tfSourceDir, modulesDir, { recursive: true });
|
|
9371
9739
|
stagePaths.push(`modules/plugins/${source.name}`);
|
|
@@ -9377,10 +9745,10 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9377
9745
|
);
|
|
9378
9746
|
const generatedPaths = await deps.migrations.generate(options.cwd, [source.name]);
|
|
9379
9747
|
for (const absPath of generatedPaths) {
|
|
9380
|
-
stagePaths.push(
|
|
9748
|
+
stagePaths.push(relative6(options.cwd, absPath));
|
|
9381
9749
|
}
|
|
9382
9750
|
if (generatedPaths.length > 0) {
|
|
9383
|
-
log.success(`Generated migration: ${
|
|
9751
|
+
log.success(`Generated migration: ${relative6(options.cwd, generatedPaths[0])}`);
|
|
9384
9752
|
} else {
|
|
9385
9753
|
log.info(
|
|
9386
9754
|
`${source.name}'s tables and columns already match the manifest \u2014 no migration needed.`
|
|
@@ -9408,10 +9776,10 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9408
9776
|
}
|
|
9409
9777
|
}
|
|
9410
9778
|
function readInstalledVersion2(targetDir) {
|
|
9411
|
-
const manifestPath =
|
|
9412
|
-
if (!
|
|
9779
|
+
const manifestPath = join36(targetDir, "biffo.plugin.json");
|
|
9780
|
+
if (!existsSync34(manifestPath)) return void 0;
|
|
9413
9781
|
try {
|
|
9414
|
-
return validateManifest(JSON.parse(
|
|
9782
|
+
return validateManifest(JSON.parse(readFileSync26(manifestPath, "utf8"))).version;
|
|
9415
9783
|
} catch {
|
|
9416
9784
|
return void 0;
|
|
9417
9785
|
}
|
|
@@ -9470,7 +9838,7 @@ function printLocalDryRun(source, currentVersion, inTreeSource) {
|
|
|
9470
9838
|
}
|
|
9471
9839
|
|
|
9472
9840
|
// src/commands/plugin.ts
|
|
9473
|
-
var pluginCommand = new
|
|
9841
|
+
var pluginCommand = new Command21("plugin").description("Manage Biffo plugins");
|
|
9474
9842
|
pluginCommand.addCommand(pluginCreateCommand);
|
|
9475
9843
|
pluginCommand.addCommand(pluginListCommand);
|
|
9476
9844
|
pluginCommand.addCommand(pluginInstallCommand);
|
|
@@ -9478,15 +9846,16 @@ pluginCommand.addCommand(pluginUninstallCommand);
|
|
|
9478
9846
|
pluginCommand.addCommand(pluginUpgradeCommand);
|
|
9479
9847
|
pluginCommand.addCommand(pluginSyncMigrationsCommand);
|
|
9480
9848
|
pluginCommand.addCommand(pluginInfoCommand);
|
|
9849
|
+
pluginCommand.addCommand(pluginStalenessCommand);
|
|
9481
9850
|
|
|
9482
9851
|
// src/commands/sibling.ts
|
|
9483
|
-
import { Command as
|
|
9852
|
+
import { Command as Command23 } from "commander";
|
|
9484
9853
|
|
|
9485
9854
|
// src/commands/sibling-check-identity.ts
|
|
9486
|
-
import { existsSync as
|
|
9487
|
-
import { resolve as
|
|
9855
|
+
import { existsSync as existsSync35, readFileSync as readFileSync27 } from "fs";
|
|
9856
|
+
import { resolve as resolve18 } from "path";
|
|
9488
9857
|
import chalk20 from "chalk";
|
|
9489
|
-
import { Command as
|
|
9858
|
+
import { Command as Command22 } from "commander";
|
|
9490
9859
|
|
|
9491
9860
|
// src/lib/sibling-identity-check.ts
|
|
9492
9861
|
function checkSiblingIdentity(envs) {
|
|
@@ -9538,7 +9907,7 @@ function checkSiblingIdentity(envs) {
|
|
|
9538
9907
|
// src/commands/sibling-check-identity.ts
|
|
9539
9908
|
var VALID_ENVIRONMENTS2 = ["dev", "staging", "prod"];
|
|
9540
9909
|
var SIBLING_CORE_POOL_VAR = "CORE_COGNITO_USER_POOL_ID";
|
|
9541
|
-
var siblingCheckIdentityCommand = new
|
|
9910
|
+
var siblingCheckIdentityCommand = new Command22("check-identity").description(
|
|
9542
9911
|
"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."
|
|
9543
9912
|
).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) => {
|
|
9544
9913
|
if (options.env && !VALID_ENVIRONMENTS2.includes(options.env)) {
|
|
@@ -9677,7 +10046,7 @@ async function fetchPublishedIdentity(portalUrl) {
|
|
|
9677
10046
|
}
|
|
9678
10047
|
async function resolveConfig4(options) {
|
|
9679
10048
|
if (options.config) {
|
|
9680
|
-
const raw = JSON.parse(
|
|
10049
|
+
const raw = JSON.parse(readFileSync27(resolve18(options.config), "utf8"));
|
|
9681
10050
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
9682
10051
|
if (!result.success) {
|
|
9683
10052
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -9696,9 +10065,9 @@ async function resolveConfig4(options) {
|
|
|
9696
10065
|
}
|
|
9697
10066
|
return cfg;
|
|
9698
10067
|
}
|
|
9699
|
-
const localConfigPath =
|
|
9700
|
-
if (
|
|
9701
|
-
const raw = JSON.parse(
|
|
10068
|
+
const localConfigPath = resolve18(process.cwd(), "biffo.config.json");
|
|
10069
|
+
if (existsSync35(localConfigPath)) {
|
|
10070
|
+
const raw = JSON.parse(readFileSync27(localConfigPath, "utf8"));
|
|
9702
10071
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
9703
10072
|
if (result.success) return result.data;
|
|
9704
10073
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -9734,31 +10103,31 @@ async function resolveConfig4(options) {
|
|
|
9734
10103
|
}
|
|
9735
10104
|
|
|
9736
10105
|
// src/commands/sibling.ts
|
|
9737
|
-
var siblingCommand = new
|
|
10106
|
+
var siblingCommand = new Command23("sibling").description(
|
|
9738
10107
|
"Create and manage sibling apps that share a Biffo core project (ADR-0007)"
|
|
9739
10108
|
);
|
|
9740
10109
|
siblingCommand.addCommand(siblingCreateCommand);
|
|
9741
10110
|
siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
9742
10111
|
|
|
9743
10112
|
// src/commands/check.ts
|
|
9744
|
-
import { Command as
|
|
10113
|
+
import { Command as Command24 } from "commander";
|
|
9745
10114
|
|
|
9746
10115
|
// src/scripts/check-adr-numbering.ts
|
|
9747
|
-
import { existsSync as
|
|
9748
|
-
import { join as
|
|
9749
|
-
import { execa as
|
|
10116
|
+
import { existsSync as existsSync37 } from "fs";
|
|
10117
|
+
import { join as join38 } from "path";
|
|
10118
|
+
import { execa as execa7 } from "execa";
|
|
9750
10119
|
|
|
9751
10120
|
// src/lib/adr-numbering-guard.ts
|
|
9752
|
-
import { existsSync as
|
|
9753
|
-
import { join as
|
|
10121
|
+
import { existsSync as existsSync36, readdirSync as readdirSync14, readFileSync as readFileSync28 } from "fs";
|
|
10122
|
+
import { join as join37 } from "path";
|
|
9754
10123
|
var ADR_FILENAME = /^(\d{4})-.+\.md$/;
|
|
9755
10124
|
var ALLOWLIST_FILENAME = ".numbering-allowlist";
|
|
9756
10125
|
var TEMPLATE_ADR_RESERVED_UPTO = "0099";
|
|
9757
10126
|
function readAdrNumberingAllowlist(adrDir) {
|
|
9758
|
-
const path =
|
|
9759
|
-
if (!
|
|
10127
|
+
const path = join37(adrDir, ALLOWLIST_FILENAME);
|
|
10128
|
+
if (!existsSync36(path)) return /* @__PURE__ */ new Set();
|
|
9760
10129
|
const numbers = /* @__PURE__ */ new Set();
|
|
9761
|
-
for (const rawLine of
|
|
10130
|
+
for (const rawLine of readFileSync28(path, "utf8").split("\n")) {
|
|
9762
10131
|
const line = rawLine.split("#")[0].trim();
|
|
9763
10132
|
if (line) numbers.add(line);
|
|
9764
10133
|
}
|
|
@@ -9766,8 +10135,8 @@ function readAdrNumberingAllowlist(adrDir) {
|
|
|
9766
10135
|
}
|
|
9767
10136
|
function adrNumbersIn(adrDir) {
|
|
9768
10137
|
const claims = /* @__PURE__ */ new Map();
|
|
9769
|
-
if (!
|
|
9770
|
-
for (const entry of
|
|
10138
|
+
if (!existsSync36(adrDir)) return claims;
|
|
10139
|
+
for (const entry of readdirSync14(adrDir).sort()) {
|
|
9771
10140
|
const match = ADR_FILENAME.exec(entry);
|
|
9772
10141
|
if (!match) continue;
|
|
9773
10142
|
const number = match[1];
|
|
@@ -9820,9 +10189,9 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
|
|
|
9820
10189
|
|
|
9821
10190
|
// src/scripts/check-adr-numbering.ts
|
|
9822
10191
|
async function runAdrNumberingCheck() {
|
|
9823
|
-
const root = (await
|
|
9824
|
-
const adrDir =
|
|
9825
|
-
if (!
|
|
10192
|
+
const root = (await execa7("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10193
|
+
const adrDir = join38(root, "docs", "ADR");
|
|
10194
|
+
if (!existsSync37(adrDir)) {
|
|
9826
10195
|
console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
|
|
9827
10196
|
return;
|
|
9828
10197
|
}
|
|
@@ -9861,7 +10230,7 @@ Already accepted? List it in docs/ADR/${ALLOWLIST_FILENAME} instead of leaving t
|
|
|
9861
10230
|
|
|
9862
10231
|
// src/scripts/check-branch-protection.ts
|
|
9863
10232
|
import { Octokit as Octokit2 } from "@octokit/rest";
|
|
9864
|
-
import { execa as
|
|
10233
|
+
import { execa as execa8 } from "execa";
|
|
9865
10234
|
|
|
9866
10235
|
// src/lib/branch-protection-apply.ts
|
|
9867
10236
|
var CONTEXT_CONSISTENCY_THRESHOLD = 2 / 3;
|
|
@@ -9988,7 +10357,7 @@ async function resolveRepo(explicit) {
|
|
|
9988
10357
|
}
|
|
9989
10358
|
return { owner, repo };
|
|
9990
10359
|
}
|
|
9991
|
-
const { stdout } = await
|
|
10360
|
+
const { stdout } = await execa8("git", ["remote", "get-url", "origin"]);
|
|
9992
10361
|
const m = /github\.com[:/]([^/]+)\/(.+?)(?:\.git)?$/.exec(stdout.trim());
|
|
9993
10362
|
if (!m?.[1] || !m[2]) {
|
|
9994
10363
|
console.error(
|
|
@@ -10120,13 +10489,13 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
|
|
|
10120
10489
|
}
|
|
10121
10490
|
|
|
10122
10491
|
// src/scripts/check-codeql-suppression.ts
|
|
10123
|
-
import { existsSync as
|
|
10124
|
-
import { join as
|
|
10125
|
-
import { execa as
|
|
10492
|
+
import { existsSync as existsSync38 } from "fs";
|
|
10493
|
+
import { join as join40, relative as relative7 } from "path";
|
|
10494
|
+
import { execa as execa9 } from "execa";
|
|
10126
10495
|
|
|
10127
10496
|
// src/lib/codeql-suppression-guard.ts
|
|
10128
|
-
import { readdirSync as
|
|
10129
|
-
import { join as
|
|
10497
|
+
import { readdirSync as readdirSync15, readFileSync as readFileSync29, statSync as statSync8 } from "fs";
|
|
10498
|
+
import { join as join39 } from "path";
|
|
10130
10499
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10131
10500
|
".git",
|
|
10132
10501
|
".worktrees",
|
|
@@ -10152,15 +10521,15 @@ function walkSourceFiles(root) {
|
|
|
10152
10521
|
const walk2 = (dir) => {
|
|
10153
10522
|
let entries;
|
|
10154
10523
|
try {
|
|
10155
|
-
entries =
|
|
10524
|
+
entries = readdirSync15(dir);
|
|
10156
10525
|
} catch {
|
|
10157
10526
|
return;
|
|
10158
10527
|
}
|
|
10159
10528
|
for (const entry of entries) {
|
|
10160
|
-
const p =
|
|
10529
|
+
const p = join39(dir, entry);
|
|
10161
10530
|
let st;
|
|
10162
10531
|
try {
|
|
10163
|
-
st =
|
|
10532
|
+
st = statSync8(p);
|
|
10164
10533
|
} catch {
|
|
10165
10534
|
continue;
|
|
10166
10535
|
}
|
|
@@ -10183,7 +10552,7 @@ function countSourceFiles(root) {
|
|
|
10183
10552
|
function sweepCodeqlSuppressionComments(root) {
|
|
10184
10553
|
const hits = [];
|
|
10185
10554
|
for (const path of walkSourceFiles(root)) {
|
|
10186
|
-
const text =
|
|
10555
|
+
const text = readFileSync29(path, "utf8");
|
|
10187
10556
|
for (const line of findCodeqlSuppressionComments(text)) {
|
|
10188
10557
|
hits.push({ path, line, text: text.split("\n")[line - 1] ?? "" });
|
|
10189
10558
|
}
|
|
@@ -10193,9 +10562,9 @@ function sweepCodeqlSuppressionComments(root) {
|
|
|
10193
10562
|
|
|
10194
10563
|
// src/scripts/check-codeql-suppression.ts
|
|
10195
10564
|
async function runCodeqlSuppressionCheck() {
|
|
10196
|
-
const root = (await
|
|
10197
|
-
const scanRoot =
|
|
10198
|
-
if (!
|
|
10565
|
+
const root = (await execa9("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10566
|
+
const scanRoot = join40(root, "cli", "src");
|
|
10567
|
+
if (!existsSync38(scanRoot)) {
|
|
10199
10568
|
console.log(
|
|
10200
10569
|
"\u2014 codeql-suppression guard: skipped \u2014 no cli/src in this repo, so there is no CLI source to scan."
|
|
10201
10570
|
);
|
|
@@ -10207,7 +10576,7 @@ async function runCodeqlSuppressionCheck() {
|
|
|
10207
10576
|
"\u2717 codeql-suppression guard: found a `codeql[...]`-shaped comment, which does not suppress anything in this repo (#1491) \u2014 dismiss the real alert instead (UI, or `PATCH .../code-scanning/alerts/<n>` with a recorded reason)\n"
|
|
10208
10577
|
);
|
|
10209
10578
|
for (const hit of hits) {
|
|
10210
|
-
console.error(` ${
|
|
10579
|
+
console.error(` ${relative7(root, hit.path)}:${hit.line} ${hit.text.trim()}`);
|
|
10211
10580
|
}
|
|
10212
10581
|
process.exit(1);
|
|
10213
10582
|
}
|
|
@@ -10219,11 +10588,11 @@ async function runCodeqlSuppressionCheck() {
|
|
|
10219
10588
|
}
|
|
10220
10589
|
|
|
10221
10590
|
// src/scripts/check-cognito-invite-template.ts
|
|
10222
|
-
import { execa as
|
|
10591
|
+
import { execa as execa10 } from "execa";
|
|
10223
10592
|
|
|
10224
10593
|
// src/lib/cognito-invite-template-guard.ts
|
|
10225
|
-
import { readdirSync as
|
|
10226
|
-
import { join as
|
|
10594
|
+
import { readdirSync as readdirSync16, readFileSync as readFileSync30, statSync as statSync9 } from "fs";
|
|
10595
|
+
import { join as join41 } from "path";
|
|
10227
10596
|
var REQUIRED_INVITE_MEMBERS = ["email_subject", "email_message", "sms_message"];
|
|
10228
10597
|
var REQUIRED_INVITE_PLACEHOLDERS = ["{username}", "{####}"];
|
|
10229
10598
|
var PLACEHOLDER_MEMBERS = ["email_message", "sms_message"];
|
|
@@ -10297,36 +10666,36 @@ function memberBody(blockBody, member) {
|
|
|
10297
10666
|
}
|
|
10298
10667
|
function findModuleTerraformFiles(repoRoot) {
|
|
10299
10668
|
const found = [];
|
|
10300
|
-
const walk2 = (dir,
|
|
10669
|
+
const walk2 = (dir, relative10) => {
|
|
10301
10670
|
let entries;
|
|
10302
10671
|
try {
|
|
10303
|
-
entries =
|
|
10672
|
+
entries = readdirSync16(dir);
|
|
10304
10673
|
} catch {
|
|
10305
10674
|
return;
|
|
10306
10675
|
}
|
|
10307
10676
|
for (const entry of entries) {
|
|
10308
10677
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees") continue;
|
|
10309
|
-
const full =
|
|
10310
|
-
const rel = `${
|
|
10311
|
-
if (
|
|
10678
|
+
const full = join41(dir, entry);
|
|
10679
|
+
const rel = `${relative10}/${entry}`;
|
|
10680
|
+
if (statSync9(full).isDirectory()) {
|
|
10312
10681
|
walk2(full, rel);
|
|
10313
10682
|
} else if (entry.endsWith(".tf")) {
|
|
10314
10683
|
found.push(rel);
|
|
10315
10684
|
}
|
|
10316
10685
|
}
|
|
10317
10686
|
};
|
|
10318
|
-
walk2(
|
|
10687
|
+
walk2(join41(repoRoot, "modules"), "modules");
|
|
10319
10688
|
return found.sort();
|
|
10320
10689
|
}
|
|
10321
10690
|
function checkCognitoInviteTemplates(repoRoot) {
|
|
10322
10691
|
return findModuleTerraformFiles(repoRoot).flatMap(
|
|
10323
|
-
(file) => checkInviteTemplateSource(file,
|
|
10692
|
+
(file) => checkInviteTemplateSource(file, readFileSync30(join41(repoRoot, file), "utf8"))
|
|
10324
10693
|
);
|
|
10325
10694
|
}
|
|
10326
10695
|
|
|
10327
10696
|
// src/scripts/check-cognito-invite-template.ts
|
|
10328
10697
|
async function runCognitoInviteTemplateCheck() {
|
|
10329
|
-
const root = (await
|
|
10698
|
+
const root = (await execa10("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10330
10699
|
const files = findModuleTerraformFiles(root);
|
|
10331
10700
|
console.log(`audited ${files.length} .tf file(s) under modules/ under ${root}`);
|
|
10332
10701
|
if (files.length === 0) {
|
|
@@ -10348,12 +10717,12 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
10348
10717
|
}
|
|
10349
10718
|
|
|
10350
10719
|
// src/scripts/check-core-direct-paths.ts
|
|
10351
|
-
import { join as
|
|
10352
|
-
import { execa as
|
|
10720
|
+
import { join as join43 } from "path";
|
|
10721
|
+
import { execa as execa11 } from "execa";
|
|
10353
10722
|
|
|
10354
10723
|
// src/lib/core-direct-paths-audit.ts
|
|
10355
|
-
import { existsSync as
|
|
10356
|
-
import { join as
|
|
10724
|
+
import { existsSync as existsSync39, readFileSync as readFileSync31, readdirSync as readdirSync17, statSync as statSync10 } from "fs";
|
|
10725
|
+
import { join as join42 } from "path";
|
|
10357
10726
|
var EXTERNAL_BASE_IDENTIFIERS = ["CORE_API_URL"];
|
|
10358
10727
|
var API_ROUTE_PREFIX = "/api/v1";
|
|
10359
10728
|
var TEST_FILE_SUFFIXES = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
|
|
@@ -10512,15 +10881,15 @@ function walkFiles(root, accept, skipDir) {
|
|
|
10512
10881
|
const walk2 = (dir) => {
|
|
10513
10882
|
let entries;
|
|
10514
10883
|
try {
|
|
10515
|
-
entries =
|
|
10884
|
+
entries = readdirSync17(dir);
|
|
10516
10885
|
} catch {
|
|
10517
10886
|
return;
|
|
10518
10887
|
}
|
|
10519
10888
|
for (const entry of entries) {
|
|
10520
|
-
const p =
|
|
10889
|
+
const p = join42(dir, entry);
|
|
10521
10890
|
let st;
|
|
10522
10891
|
try {
|
|
10523
|
-
st =
|
|
10892
|
+
st = statSync10(p);
|
|
10524
10893
|
} catch {
|
|
10525
10894
|
continue;
|
|
10526
10895
|
}
|
|
@@ -10547,7 +10916,7 @@ function auditFrontendExtraction(frontendSrcDir, externalBases = EXTERNAL_BASE_I
|
|
|
10547
10916
|
const extracted = [];
|
|
10548
10917
|
let rawTotal = 0;
|
|
10549
10918
|
for (const file of files) {
|
|
10550
|
-
const text =
|
|
10919
|
+
const text = readFileSync31(file, "utf8");
|
|
10551
10920
|
rawTotal += countRawExternalOccurrences(text, externalBases);
|
|
10552
10921
|
extracted.push(...extractCoreDirectPaths(text, file, externalBases));
|
|
10553
10922
|
}
|
|
@@ -10596,7 +10965,7 @@ function auditCoreRouteExtraction(apiSrcDir) {
|
|
|
10596
10965
|
const prefixSet = /* @__PURE__ */ new Set();
|
|
10597
10966
|
let rawApiRouterCount = 0;
|
|
10598
10967
|
for (const file of files) {
|
|
10599
|
-
const text =
|
|
10968
|
+
const text = readFileSync31(file, "utf8");
|
|
10600
10969
|
const extraction = extractCoreRoutePrefixes(text);
|
|
10601
10970
|
rawApiRouterCount += extraction.rawApiRouterCount;
|
|
10602
10971
|
for (const p of extraction.prefixes) prefixSet.add(normalizePrefix(p));
|
|
@@ -10612,10 +10981,10 @@ function pathMatchesAnyCorePrefix(normalized, corePrefixes, apiRoutePrefix = API
|
|
|
10612
10981
|
}
|
|
10613
10982
|
function resolveSiblingCoreSrc(params) {
|
|
10614
10983
|
const { estateDir, sibling } = params;
|
|
10615
|
-
const configPath =
|
|
10984
|
+
const configPath = join42(estateDir, sibling, "biffo.sibling.json");
|
|
10616
10985
|
let raw;
|
|
10617
10986
|
try {
|
|
10618
|
-
raw =
|
|
10987
|
+
raw = readFileSync31(configPath, "utf8");
|
|
10619
10988
|
} catch (err) {
|
|
10620
10989
|
throw new Error(
|
|
10621
10990
|
`cannot resolve ${sibling}'s core: ${configPath} does not exist or is unreadable (${err.message}) -- refusing to guess which core serves this sibling.`
|
|
@@ -10635,8 +11004,8 @@ function resolveSiblingCoreSrc(params) {
|
|
|
10635
11004
|
`cannot resolve ${sibling}'s core: ${configPath} has no non-empty "core_project" field.`
|
|
10636
11005
|
);
|
|
10637
11006
|
}
|
|
10638
|
-
const coreApiSrcDir =
|
|
10639
|
-
if (!
|
|
11007
|
+
const coreApiSrcDir = join42(estateDir, coreProject, "services", "api", "src");
|
|
11008
|
+
if (!existsSync39(coreApiSrcDir)) {
|
|
10640
11009
|
throw new Error(
|
|
10641
11010
|
`cannot resolve ${sibling}'s core: biffo.sibling.json names core_project "${coreProject}", but ${coreApiSrcDir} does not exist -- the instance is missing from this estate checkout, not merely unmatched. Refusing to silently skip ${sibling} and shrink the audit's denominator.`
|
|
10642
11011
|
);
|
|
@@ -10677,9 +11046,9 @@ function auditSiblingCoreDirectPaths(params) {
|
|
|
10677
11046
|
|
|
10678
11047
|
// src/scripts/check-core-direct-paths.ts
|
|
10679
11048
|
async function runCoreDirectPathsCheck(opts = {}) {
|
|
10680
|
-
const root = (await
|
|
11049
|
+
const root = (await execa11("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10681
11050
|
const sibling = opts.sibling ?? "sibling-template (self-check)";
|
|
10682
|
-
const frontendSrcDir = opts.frontendSrc ??
|
|
11051
|
+
const frontendSrcDir = opts.frontendSrc ?? join43(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
|
|
10683
11052
|
let coreApiSrcDir;
|
|
10684
11053
|
let coreProject = null;
|
|
10685
11054
|
if (opts.coreSrc) {
|
|
@@ -10695,7 +11064,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
10695
11064
|
coreApiSrcDir = resolution.coreApiSrcDir;
|
|
10696
11065
|
coreProject = resolution.coreProject;
|
|
10697
11066
|
} else {
|
|
10698
|
-
coreApiSrcDir =
|
|
11067
|
+
coreApiSrcDir = join43(root, "services", "api", "src");
|
|
10699
11068
|
}
|
|
10700
11069
|
const report = auditSiblingCoreDirectPaths({ sibling, frontendSrcDir, coreApiSrcDir });
|
|
10701
11070
|
console.log(
|
|
@@ -10731,7 +11100,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
10731
11100
|
}
|
|
10732
11101
|
|
|
10733
11102
|
// src/scripts/check-core-ownership.ts
|
|
10734
|
-
import { execa as
|
|
11103
|
+
import { execa as execa12 } from "execa";
|
|
10735
11104
|
var BOLD = "\x1B[1m";
|
|
10736
11105
|
var DIM = "\x1B[2m";
|
|
10737
11106
|
var RED = "\x1B[31m";
|
|
@@ -10742,7 +11111,7 @@ async function runOwnershipCheck(argv) {
|
|
|
10742
11111
|
const stagedFlag = args.indexOf("--staged");
|
|
10743
11112
|
const staged = stagedFlag !== -1;
|
|
10744
11113
|
const messageFile = staged ? args[stagedFlag + 1] : void 0;
|
|
10745
|
-
const root = (await
|
|
11114
|
+
const root = (await execa12("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10746
11115
|
const ownership = classifyRepoOwnership(root);
|
|
10747
11116
|
if (ownership === "template") {
|
|
10748
11117
|
console.log("\u2713 core ownership guard: skipped \u2014 this is the template, which owns these paths.");
|
|
@@ -10758,11 +11127,11 @@ async function runOwnershipCheck(argv) {
|
|
|
10758
11127
|
let deletedFiles = [];
|
|
10759
11128
|
let commitMessage = "";
|
|
10760
11129
|
if (staged) {
|
|
10761
|
-
const { stdout } = await
|
|
11130
|
+
const { stdout } = await execa12("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
10762
11131
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
10763
11132
|
if (messageFile) {
|
|
10764
|
-
const { readFileSync:
|
|
10765
|
-
if (
|
|
11133
|
+
const { readFileSync: readFileSync41, existsSync: existsSync48 } = await import("fs");
|
|
11134
|
+
if (existsSync48(messageFile)) commitMessage = readFileSync41(messageFile, "utf8");
|
|
10766
11135
|
}
|
|
10767
11136
|
} else {
|
|
10768
11137
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -10770,18 +11139,18 @@ async function runOwnershipCheck(argv) {
|
|
|
10770
11139
|
console.error("No base ref: set GITHUB_BASE_REF or pass a base branch as the first argument.");
|
|
10771
11140
|
process.exit(2);
|
|
10772
11141
|
}
|
|
10773
|
-
await
|
|
10774
|
-
const { stdout } = await
|
|
11142
|
+
await execa12("git", ["fetch", "--quiet", "origin", base], { cwd: root, reject: false });
|
|
11143
|
+
const { stdout } = await execa12("git", ["diff", "--name-status", `origin/${base}...HEAD`], {
|
|
10775
11144
|
cwd: root
|
|
10776
11145
|
});
|
|
10777
11146
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
10778
|
-
const { stdout: log2 } = await
|
|
11147
|
+
const { stdout: log2 } = await execa12("git", ["log", "--format=%B", `origin/${base}..HEAD`], {
|
|
10779
11148
|
cwd: root,
|
|
10780
11149
|
reject: false
|
|
10781
11150
|
});
|
|
10782
11151
|
commitMessage = log2;
|
|
10783
11152
|
}
|
|
10784
|
-
const { stdout: gitBranch } = await
|
|
11153
|
+
const { stdout: gitBranch } = await execa12("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
10785
11154
|
cwd: root,
|
|
10786
11155
|
reject: false
|
|
10787
11156
|
});
|
|
@@ -10863,11 +11232,11 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
10863
11232
|
}
|
|
10864
11233
|
|
|
10865
11234
|
// src/scripts/check-eventbridge-log-permissions.ts
|
|
10866
|
-
import { execa as
|
|
11235
|
+
import { execa as execa13 } from "execa";
|
|
10867
11236
|
|
|
10868
11237
|
// src/lib/eventbridge-log-permission-guard.ts
|
|
10869
|
-
import { readFileSync as
|
|
10870
|
-
import { join as
|
|
11238
|
+
import { readFileSync as readFileSync32, readdirSync as readdirSync18, statSync as statSync11 } from "fs";
|
|
11239
|
+
import { join as join44 } from "path";
|
|
10871
11240
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
10872
11241
|
var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
|
|
10873
11242
|
var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
|
|
@@ -10939,15 +11308,15 @@ function walkTerraformFiles(root) {
|
|
|
10939
11308
|
const walk2 = (dir) => {
|
|
10940
11309
|
let entries;
|
|
10941
11310
|
try {
|
|
10942
|
-
entries =
|
|
11311
|
+
entries = readdirSync18(dir);
|
|
10943
11312
|
} catch {
|
|
10944
11313
|
return;
|
|
10945
11314
|
}
|
|
10946
11315
|
for (const entry of entries) {
|
|
10947
|
-
const p =
|
|
11316
|
+
const p = join44(dir, entry);
|
|
10948
11317
|
let st;
|
|
10949
11318
|
try {
|
|
10950
|
-
st =
|
|
11319
|
+
st = statSync11(p);
|
|
10951
11320
|
} catch {
|
|
10952
11321
|
continue;
|
|
10953
11322
|
}
|
|
@@ -10987,7 +11356,7 @@ function auditEventBridgeLogPermissions(root) {
|
|
|
10987
11356
|
let rawEventTargetCount = 0;
|
|
10988
11357
|
let rawLogPolicyCount = 0;
|
|
10989
11358
|
for (const file of files) {
|
|
10990
|
-
const text =
|
|
11359
|
+
const text = readFileSync32(file, "utf8");
|
|
10991
11360
|
rawEventTargetCount += countRawResourceDeclarations(text, EVENT_TARGET_TYPE);
|
|
10992
11361
|
rawLogPolicyCount += countRawResourceDeclarations(text, LOG_RESOURCE_POLICY_TYPE);
|
|
10993
11362
|
eventTargetBlocks.push(...findResourceBlocks(text, file, EVENT_TARGET_TYPE));
|
|
@@ -11040,7 +11409,7 @@ function auditEventBridgeLogPermissions(root) {
|
|
|
11040
11409
|
|
|
11041
11410
|
// src/scripts/check-eventbridge-log-permissions.ts
|
|
11042
11411
|
async function runEventBridgeLogPermissionCheck() {
|
|
11043
|
-
const root = (await
|
|
11412
|
+
const root = (await execa13("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11044
11413
|
let report;
|
|
11045
11414
|
try {
|
|
11046
11415
|
report = auditEventBridgeLogPermissions(root);
|
|
@@ -11077,15 +11446,15 @@ async function runEventBridgeLogPermissionCheck() {
|
|
|
11077
11446
|
}
|
|
11078
11447
|
|
|
11079
11448
|
// src/scripts/check-lambda-output.ts
|
|
11080
|
-
import { execa as
|
|
11449
|
+
import { execa as execa14 } from "execa";
|
|
11081
11450
|
|
|
11082
11451
|
// src/lib/lambda-output-guard.ts
|
|
11083
|
-
import { readFileSync as
|
|
11084
|
-
import { join as
|
|
11452
|
+
import { readFileSync as readFileSync34 } from "fs";
|
|
11453
|
+
import { join as join46 } from "path";
|
|
11085
11454
|
|
|
11086
11455
|
// src/lib/terraform-input-guard.ts
|
|
11087
|
-
import { readdirSync as
|
|
11088
|
-
import { join as
|
|
11456
|
+
import { readdirSync as readdirSync19, readFileSync as readFileSync33, statSync as statSync12 } from "fs";
|
|
11457
|
+
import { join as join45 } from "path";
|
|
11089
11458
|
var GUARDED_SUBCOMMANDS = [
|
|
11090
11459
|
"init",
|
|
11091
11460
|
"plan",
|
|
@@ -11100,20 +11469,20 @@ function stripComments2(source) {
|
|
|
11100
11469
|
}
|
|
11101
11470
|
function findWorkflowFiles(repoRoot) {
|
|
11102
11471
|
const found = [];
|
|
11103
|
-
const walk2 = (dir,
|
|
11472
|
+
const walk2 = (dir, relative10) => {
|
|
11104
11473
|
let entries;
|
|
11105
11474
|
try {
|
|
11106
|
-
entries =
|
|
11475
|
+
entries = readdirSync19(dir);
|
|
11107
11476
|
} catch {
|
|
11108
11477
|
return;
|
|
11109
11478
|
}
|
|
11110
11479
|
for (const entry of entries) {
|
|
11111
11480
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees") continue;
|
|
11112
|
-
const full =
|
|
11113
|
-
const rel =
|
|
11114
|
-
if (
|
|
11481
|
+
const full = join45(dir, entry);
|
|
11482
|
+
const rel = relative10 ? `${relative10}/${entry}` : entry;
|
|
11483
|
+
if (statSync12(full).isDirectory()) {
|
|
11115
11484
|
walk2(full, rel);
|
|
11116
|
-
} else if (/\.ya?ml$/.test(entry) &&
|
|
11485
|
+
} else if (/\.ya?ml$/.test(entry) && relative10.endsWith(".github/workflows")) {
|
|
11117
11486
|
found.push(rel);
|
|
11118
11487
|
}
|
|
11119
11488
|
}
|
|
@@ -11153,7 +11522,7 @@ function checkWorkflowSource(file, rawSource) {
|
|
|
11153
11522
|
}
|
|
11154
11523
|
function checkTerraformInput(repoRoot) {
|
|
11155
11524
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
11156
|
-
(file) => checkWorkflowSource(file,
|
|
11525
|
+
(file) => checkWorkflowSource(file, readFileSync33(join45(repoRoot, file), "utf8"))
|
|
11157
11526
|
);
|
|
11158
11527
|
}
|
|
11159
11528
|
|
|
@@ -11211,13 +11580,13 @@ function checkWorkflowSource2(file, rawSource) {
|
|
|
11211
11580
|
}
|
|
11212
11581
|
function checkLambdaOutput(repoRoot) {
|
|
11213
11582
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
11214
|
-
(file) => checkWorkflowSource2(file,
|
|
11583
|
+
(file) => checkWorkflowSource2(file, readFileSync34(join46(repoRoot, file), "utf8"))
|
|
11215
11584
|
);
|
|
11216
11585
|
}
|
|
11217
11586
|
|
|
11218
11587
|
// src/scripts/check-lambda-output.ts
|
|
11219
11588
|
async function runLambdaOutputCheck() {
|
|
11220
|
-
const root = (await
|
|
11589
|
+
const root = (await execa14("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11221
11590
|
const files = findWorkflowFiles(root);
|
|
11222
11591
|
console.log(`audited ${files.length} workflow file(s) under ${root}`);
|
|
11223
11592
|
if (files.length === 0) {
|
|
@@ -11239,9 +11608,9 @@ async function runLambdaOutputCheck() {
|
|
|
11239
11608
|
}
|
|
11240
11609
|
|
|
11241
11610
|
// src/scripts/check-pipe-trap.ts
|
|
11242
|
-
import { readFileSync as
|
|
11243
|
-
import { join as
|
|
11244
|
-
import { execa as
|
|
11611
|
+
import { readFileSync as readFileSync35, readdirSync as readdirSync20 } from "fs";
|
|
11612
|
+
import { join as join47, relative as relative8 } from "path";
|
|
11613
|
+
import { execa as execa15 } from "execa";
|
|
11245
11614
|
|
|
11246
11615
|
// src/lib/pipe-trap-guard.ts
|
|
11247
11616
|
var STATUS_BEARING = [
|
|
@@ -11337,23 +11706,23 @@ function findPipeTraps(source) {
|
|
|
11337
11706
|
function shellFiles(root) {
|
|
11338
11707
|
const out = [];
|
|
11339
11708
|
for (const dir of ["scripts", ".githooks"]) {
|
|
11340
|
-
const full =
|
|
11709
|
+
const full = join47(root, dir);
|
|
11341
11710
|
let entries;
|
|
11342
11711
|
try {
|
|
11343
|
-
entries =
|
|
11712
|
+
entries = readdirSync20(full, { withFileTypes: true });
|
|
11344
11713
|
} catch {
|
|
11345
11714
|
continue;
|
|
11346
11715
|
}
|
|
11347
11716
|
for (const entry of entries) {
|
|
11348
11717
|
if (!entry.isFile()) continue;
|
|
11349
11718
|
if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
|
|
11350
|
-
out.push(
|
|
11719
|
+
out.push(join47(full, entry.name));
|
|
11351
11720
|
}
|
|
11352
11721
|
}
|
|
11353
11722
|
return out;
|
|
11354
11723
|
}
|
|
11355
11724
|
async function runPipeTrapCheck() {
|
|
11356
|
-
const root = (await
|
|
11725
|
+
const root = (await execa15("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11357
11726
|
const files = shellFiles(root);
|
|
11358
11727
|
console.log(`audited ${files.length} shell file(s) under scripts/ and .githooks/ under ${root}`);
|
|
11359
11728
|
if (files.length === 0) {
|
|
@@ -11363,8 +11732,8 @@ async function runPipeTrapCheck() {
|
|
|
11363
11732
|
process.exit(1);
|
|
11364
11733
|
}
|
|
11365
11734
|
const findings = files.flatMap(
|
|
11366
|
-
(file) => findPipeTraps(
|
|
11367
|
-
(t) => `${
|
|
11735
|
+
(file) => findPipeTraps(readFileSync35(file, "utf8")).map(
|
|
11736
|
+
(t) => `${relative8(root, file)}:${t.line} ${t.text}
|
|
11368
11737
|
${t.reason}`
|
|
11369
11738
|
)
|
|
11370
11739
|
);
|
|
@@ -11380,11 +11749,11 @@ async function runPipeTrapCheck() {
|
|
|
11380
11749
|
}
|
|
11381
11750
|
|
|
11382
11751
|
// src/scripts/check-plugin-allowlist-convention.ts
|
|
11383
|
-
import { execa as
|
|
11752
|
+
import { execa as execa16 } from "execa";
|
|
11384
11753
|
|
|
11385
11754
|
// src/lib/plugin-allowlist-convention.ts
|
|
11386
|
-
import { readFileSync as
|
|
11387
|
-
import { join as
|
|
11755
|
+
import { readFileSync as readFileSync36 } from "fs";
|
|
11756
|
+
import { join as join48 } from "path";
|
|
11388
11757
|
var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
|
|
11389
11758
|
var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
|
|
11390
11759
|
var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
|
|
@@ -11393,18 +11762,18 @@ var PROJECT = "<project>";
|
|
|
11393
11762
|
var ENV = "<env>";
|
|
11394
11763
|
var PLUGIN = "<plugin>";
|
|
11395
11764
|
var ACCOUNT = "<account>";
|
|
11396
|
-
function read(repoRoot,
|
|
11765
|
+
function read(repoRoot, relative10) {
|
|
11397
11766
|
try {
|
|
11398
|
-
return
|
|
11767
|
+
return readFileSync36(join48(repoRoot, relative10), "utf8");
|
|
11399
11768
|
} catch {
|
|
11400
|
-
throw new Error(`plugin-allowlist drift guard: cannot read ${
|
|
11769
|
+
throw new Error(`plugin-allowlist drift guard: cannot read ${relative10}`);
|
|
11401
11770
|
}
|
|
11402
11771
|
}
|
|
11403
11772
|
function assignedString(source, name) {
|
|
11404
11773
|
const match = new RegExp(`^\\s*${name}\\s*=\\s*"((?:[^"\\\\]|\\\\.)*)"\\s*$`, "m").exec(source);
|
|
11405
11774
|
return match?.[1];
|
|
11406
11775
|
}
|
|
11407
|
-
function
|
|
11776
|
+
function resolve19(expression, bindings) {
|
|
11408
11777
|
let current = expression;
|
|
11409
11778
|
for (let pass = 0; pass < 10; pass += 1) {
|
|
11410
11779
|
const next = current.replace(/\$\{([^}]+)\}/g, (whole, ref) => {
|
|
@@ -11441,13 +11810,13 @@ function composeExpectedRoleName(repoRoot) {
|
|
|
11441
11810
|
"var.project_name": PROJECT,
|
|
11442
11811
|
"var.environment": ENV,
|
|
11443
11812
|
"var.plugin_name": PLUGIN,
|
|
11444
|
-
"var.function_name":
|
|
11813
|
+
"var.function_name": resolve19(pluginFunctionName, {
|
|
11445
11814
|
"var.plugin_name": PLUGIN
|
|
11446
11815
|
})
|
|
11447
11816
|
};
|
|
11448
|
-
bindings["local.name_prefix"] =
|
|
11449
|
-
bindings["local.function_name"] =
|
|
11450
|
-
return
|
|
11817
|
+
bindings["local.name_prefix"] = resolve19(namePrefix, bindings);
|
|
11818
|
+
bindings["local.function_name"] = resolve19(functionName, bindings);
|
|
11819
|
+
return resolve19(roleName, bindings);
|
|
11451
11820
|
}
|
|
11452
11821
|
function readAllowlistGlob(repoRoot) {
|
|
11453
11822
|
const allowlist = read(repoRoot, ALLOWLIST_MAIN_TF);
|
|
@@ -11457,7 +11826,7 @@ function readAllowlistGlob(repoRoot) {
|
|
|
11457
11826
|
`plugin-allowlist drift guard: could not find the "for name in var.enabled_plugins" glob in ${ALLOWLIST_MAIN_TF}.`
|
|
11458
11827
|
);
|
|
11459
11828
|
}
|
|
11460
|
-
return
|
|
11829
|
+
return resolve19(glob, {
|
|
11461
11830
|
"data.aws_caller_identity.current.account_id": ACCOUNT,
|
|
11462
11831
|
"var.project_name": PROJECT,
|
|
11463
11832
|
"var.environment": ENV,
|
|
@@ -11491,7 +11860,7 @@ Plugins would be rejected by require_service_principal (ADR-0009). Fix the glob,
|
|
|
11491
11860
|
|
|
11492
11861
|
// src/scripts/check-plugin-allowlist-convention.ts
|
|
11493
11862
|
async function runPluginAllowlistConventionCheck() {
|
|
11494
|
-
const root = (await
|
|
11863
|
+
const root = (await execa16("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11495
11864
|
let violations;
|
|
11496
11865
|
try {
|
|
11497
11866
|
violations = checkAllowlistConvention(root);
|
|
@@ -11516,34 +11885,34 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
11516
11885
|
}
|
|
11517
11886
|
|
|
11518
11887
|
// src/scripts/check-plugin-collisions.ts
|
|
11519
|
-
import { existsSync as
|
|
11520
|
-
import { join as
|
|
11521
|
-
import { execa as
|
|
11888
|
+
import { existsSync as existsSync41 } from "fs";
|
|
11889
|
+
import { join as join50 } from "path";
|
|
11890
|
+
import { execa as execa17 } from "execa";
|
|
11522
11891
|
|
|
11523
11892
|
// src/lib/plugin-collision-guard.ts
|
|
11524
|
-
import { existsSync as
|
|
11525
|
-
import { join as
|
|
11893
|
+
import { existsSync as existsSync40, readdirSync as readdirSync21, statSync as statSync13 } from "fs";
|
|
11894
|
+
import { join as join49 } from "path";
|
|
11526
11895
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
11527
11896
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
11528
11897
|
function subdirectories(dir) {
|
|
11529
|
-
if (!
|
|
11530
|
-
return
|
|
11898
|
+
if (!existsSync40(dir)) return [];
|
|
11899
|
+
return readdirSync21(dir).filter((entry) => {
|
|
11531
11900
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
11532
11901
|
try {
|
|
11533
|
-
return
|
|
11902
|
+
return statSync13(join49(dir, entry)).isDirectory();
|
|
11534
11903
|
} catch {
|
|
11535
11904
|
return false;
|
|
11536
11905
|
}
|
|
11537
11906
|
});
|
|
11538
11907
|
}
|
|
11539
11908
|
function regularPackagesOf(pluginDir2) {
|
|
11540
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
11909
|
+
return subdirectories(pluginDir2).filter((name) => existsSync40(join49(pluginDir2, name, "__init__.py"))).sort();
|
|
11541
11910
|
}
|
|
11542
11911
|
function bareTestModulesOf(pluginDir2) {
|
|
11543
|
-
const testsDir =
|
|
11544
|
-
if (!
|
|
11545
|
-
if (
|
|
11546
|
-
return
|
|
11912
|
+
const testsDir = join49(pluginDir2, "tests");
|
|
11913
|
+
if (!existsSync40(testsDir)) return [];
|
|
11914
|
+
if (existsSync40(join49(testsDir, "__init__.py"))) return [];
|
|
11915
|
+
return readdirSync21(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
11547
11916
|
}
|
|
11548
11917
|
function findCollisions(servicesDir, pluginDirs) {
|
|
11549
11918
|
const plugins = (pluginDirs ?? subdirectories(servicesDir)).filter((name) => !name.startsWith("_")).filter((name) => name !== "api").sort();
|
|
@@ -11551,7 +11920,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
11551
11920
|
const gather = (kind, namesOf) => {
|
|
11552
11921
|
const claims = /* @__PURE__ */ new Map();
|
|
11553
11922
|
for (const plugin of plugins) {
|
|
11554
|
-
for (const name of namesOf(
|
|
11923
|
+
for (const name of namesOf(join49(servicesDir, plugin))) {
|
|
11555
11924
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
11556
11925
|
}
|
|
11557
11926
|
}
|
|
@@ -11588,9 +11957,9 @@ function formatCollisions(collisions) {
|
|
|
11588
11957
|
|
|
11589
11958
|
// src/scripts/check-plugin-collisions.ts
|
|
11590
11959
|
async function runPluginCollisionCheck() {
|
|
11591
|
-
const root = (await
|
|
11592
|
-
const servicesDir =
|
|
11593
|
-
if (!
|
|
11960
|
+
const root = (await execa17("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11961
|
+
const servicesDir = join50(root, "services");
|
|
11962
|
+
if (!existsSync41(servicesDir)) {
|
|
11594
11963
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
11595
11964
|
return;
|
|
11596
11965
|
}
|
|
@@ -11607,11 +11976,11 @@ async function runPluginCollisionCheck() {
|
|
|
11607
11976
|
}
|
|
11608
11977
|
|
|
11609
11978
|
// src/scripts/check-plugin-terraform.ts
|
|
11610
|
-
import { execa as
|
|
11979
|
+
import { execa as execa18 } from "execa";
|
|
11611
11980
|
|
|
11612
11981
|
// src/lib/plugin-terraform-guard.ts
|
|
11613
|
-
import { existsSync as
|
|
11614
|
-
import { dirname as dirname10, join as
|
|
11982
|
+
import { existsSync as existsSync42, readFileSync as readFileSync37, readdirSync as readdirSync22 } from "fs";
|
|
11983
|
+
import { dirname as dirname10, join as join51, relative as relative9, sep as sep3 } from "path";
|
|
11615
11984
|
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
11616
11985
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
11617
11986
|
function findPluginManifests(root) {
|
|
@@ -11619,16 +11988,16 @@ function findPluginManifests(root) {
|
|
|
11619
11988
|
const walk2 = (dir) => {
|
|
11620
11989
|
let entries;
|
|
11621
11990
|
try {
|
|
11622
|
-
entries =
|
|
11991
|
+
entries = readdirSync22(dir, { withFileTypes: true });
|
|
11623
11992
|
} catch {
|
|
11624
11993
|
return;
|
|
11625
11994
|
}
|
|
11626
11995
|
for (const entry of entries) {
|
|
11627
11996
|
if (entry.isDirectory()) {
|
|
11628
11997
|
if (SKIP_DIRS3.has(entry.name)) continue;
|
|
11629
|
-
walk2(
|
|
11998
|
+
walk2(join51(dir, entry.name));
|
|
11630
11999
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
11631
|
-
found.push(
|
|
12000
|
+
found.push(relative9(root, join51(dir, entry.name)).split(sep3).join("/"));
|
|
11632
12001
|
}
|
|
11633
12002
|
}
|
|
11634
12003
|
};
|
|
@@ -11638,7 +12007,7 @@ function findPluginManifests(root) {
|
|
|
11638
12007
|
function readSubscriptions(absManifestPath) {
|
|
11639
12008
|
let parsed;
|
|
11640
12009
|
try {
|
|
11641
|
-
parsed = JSON.parse(
|
|
12010
|
+
parsed = JSON.parse(readFileSync37(absManifestPath, "utf8"));
|
|
11642
12011
|
} catch {
|
|
11643
12012
|
return null;
|
|
11644
12013
|
}
|
|
@@ -11653,15 +12022,15 @@ function readSubscriptions(absManifestPath) {
|
|
|
11653
12022
|
}
|
|
11654
12023
|
function checkPluginTerraform(root) {
|
|
11655
12024
|
const violations = [];
|
|
11656
|
-
const coreManifest =
|
|
12025
|
+
const coreManifest = existsSync42(join51(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
11657
12026
|
for (const manifest of findPluginManifests(root)) {
|
|
11658
12027
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
11659
|
-
const absManifest =
|
|
12028
|
+
const absManifest = join51(root, manifest);
|
|
11660
12029
|
const subscriptions = readSubscriptions(absManifest);
|
|
11661
12030
|
if (subscriptions === null) continue;
|
|
11662
12031
|
const pluginDir2 = dirname10(absManifest);
|
|
11663
|
-
if (
|
|
11664
|
-
const relPluginDir =
|
|
12032
|
+
if (existsSync42(join51(pluginDir2, "terraform"))) continue;
|
|
12033
|
+
const relPluginDir = relative9(root, pluginDir2).split(sep3).join("/");
|
|
11665
12034
|
violations.push({
|
|
11666
12035
|
manifest,
|
|
11667
12036
|
expectedTerraformDir: relPluginDir ? `${relPluginDir}/terraform` : "terraform",
|
|
@@ -11680,7 +12049,7 @@ function formatViolations(violations) {
|
|
|
11680
12049
|
|
|
11681
12050
|
// src/scripts/check-plugin-terraform.ts
|
|
11682
12051
|
async function runPluginTerraformCheck() {
|
|
11683
|
-
const root = (await
|
|
12052
|
+
const root = (await execa18("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11684
12053
|
const violations = checkPluginTerraform(root);
|
|
11685
12054
|
if (violations.length > 0) {
|
|
11686
12055
|
console.error("\u2717 plugin Terraform guard: event subscriptions with no infrastructure\n");
|
|
@@ -11691,13 +12060,13 @@ async function runPluginTerraformCheck() {
|
|
|
11691
12060
|
}
|
|
11692
12061
|
|
|
11693
12062
|
// src/scripts/check-plugin-tool-supply.ts
|
|
11694
|
-
import { existsSync as
|
|
11695
|
-
import { join as
|
|
11696
|
-
import { execa as
|
|
12063
|
+
import { existsSync as existsSync44 } from "fs";
|
|
12064
|
+
import { join as join53 } from "path";
|
|
12065
|
+
import { execa as execa19 } from "execa";
|
|
11697
12066
|
|
|
11698
12067
|
// src/lib/plugin-tool-supply-audit.ts
|
|
11699
|
-
import { existsSync as
|
|
11700
|
-
import { join as
|
|
12068
|
+
import { existsSync as existsSync43, readFileSync as readFileSync38, readdirSync as readdirSync23, statSync as statSync14 } from "fs";
|
|
12069
|
+
import { join as join52 } from "path";
|
|
11701
12070
|
|
|
11702
12071
|
// src/lib/openrouter-model-snapshot.ts
|
|
11703
12072
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -12108,13 +12477,13 @@ var OPENROUTER_MODEL_IDS = [
|
|
|
12108
12477
|
function listDirs(root) {
|
|
12109
12478
|
let entries;
|
|
12110
12479
|
try {
|
|
12111
|
-
entries =
|
|
12480
|
+
entries = readdirSync23(root);
|
|
12112
12481
|
} catch {
|
|
12113
12482
|
return [];
|
|
12114
12483
|
}
|
|
12115
12484
|
return entries.filter((e) => {
|
|
12116
12485
|
try {
|
|
12117
|
-
return
|
|
12486
|
+
return statSync14(join52(root, e)).isDirectory();
|
|
12118
12487
|
} catch {
|
|
12119
12488
|
return false;
|
|
12120
12489
|
}
|
|
@@ -12125,15 +12494,15 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
12125
12494
|
const walk2 = (dir) => {
|
|
12126
12495
|
let entries;
|
|
12127
12496
|
try {
|
|
12128
|
-
entries =
|
|
12497
|
+
entries = readdirSync23(dir);
|
|
12129
12498
|
} catch {
|
|
12130
12499
|
return;
|
|
12131
12500
|
}
|
|
12132
12501
|
for (const entry of entries) {
|
|
12133
|
-
const p =
|
|
12502
|
+
const p = join52(dir, entry);
|
|
12134
12503
|
let st;
|
|
12135
12504
|
try {
|
|
12136
|
-
st =
|
|
12505
|
+
st = statSync14(p);
|
|
12137
12506
|
} catch {
|
|
12138
12507
|
continue;
|
|
12139
12508
|
}
|
|
@@ -12156,14 +12525,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
12156
12525
|
);
|
|
12157
12526
|
}
|
|
12158
12527
|
function pluginTerraformFiles(pluginDir2) {
|
|
12159
|
-
const tfDir =
|
|
12528
|
+
const tfDir = join52(pluginDir2, "terraform");
|
|
12160
12529
|
let entries;
|
|
12161
12530
|
try {
|
|
12162
|
-
entries =
|
|
12531
|
+
entries = readdirSync23(tfDir);
|
|
12163
12532
|
} catch {
|
|
12164
12533
|
return [];
|
|
12165
12534
|
}
|
|
12166
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
12535
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join52(tfDir, e)).sort();
|
|
12167
12536
|
}
|
|
12168
12537
|
function extractManifestTools(manifestText) {
|
|
12169
12538
|
let parsed;
|
|
@@ -12415,8 +12784,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
12415
12784
|
function normalizeModelId(id) {
|
|
12416
12785
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
12417
12786
|
}
|
|
12418
|
-
var CONFIG_PY_PATH =
|
|
12419
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
12787
|
+
var CONFIG_PY_PATH = join52("services", "api", "src", "api", "config.py");
|
|
12788
|
+
var ORCHESTRATION_SCHEMA_PATH = join52(
|
|
12420
12789
|
"services",
|
|
12421
12790
|
"api",
|
|
12422
12791
|
"src",
|
|
@@ -12428,10 +12797,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
12428
12797
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
12429
12798
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
12430
12799
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
12431
|
-
const configPath =
|
|
12432
|
-
const orchestrationPath =
|
|
12433
|
-
const configMissing = !
|
|
12434
|
-
const orchestrationSchemaMissing = !
|
|
12800
|
+
const configPath = join52(repoRoot, CONFIG_PY_PATH);
|
|
12801
|
+
const orchestrationPath = join52(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
12802
|
+
const configMissing = !existsSync43(configPath);
|
|
12803
|
+
const orchestrationSchemaMissing = !existsSync43(orchestrationPath);
|
|
12435
12804
|
const knownSet = new Set(knownModelIds);
|
|
12436
12805
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
12437
12806
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -12449,13 +12818,13 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
12449
12818
|
};
|
|
12450
12819
|
let settingsBlind = false;
|
|
12451
12820
|
if (!configMissing) {
|
|
12452
|
-
const settingsFields = extractSettingsModelFields(
|
|
12821
|
+
const settingsFields = extractSettingsModelFields(readFileSync38(configPath, "utf8"));
|
|
12453
12822
|
if (settingsFields.length === 0) settingsBlind = true;
|
|
12454
12823
|
for (const { field, value } of settingsFields) record(`${CONFIG_PY_PATH}#${field}`, value);
|
|
12455
12824
|
}
|
|
12456
12825
|
let curatedFieldsBlind = false;
|
|
12457
12826
|
if (!orchestrationSchemaMissing) {
|
|
12458
|
-
const curated = extractCuratedModelFields(
|
|
12827
|
+
const curated = extractCuratedModelFields(readFileSync38(orchestrationPath, "utf8"));
|
|
12459
12828
|
if (curated.rawFieldCount > 0 && curated.fields.every((f) => f.defaultValue === null && f.optionValues.length === 0)) {
|
|
12460
12829
|
curatedFieldsBlind = true;
|
|
12461
12830
|
}
|
|
@@ -12502,7 +12871,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
12502
12871
|
function discoverPluginDirs(pluginsRoot) {
|
|
12503
12872
|
return listDirs(pluginsRoot).filter((name) => {
|
|
12504
12873
|
try {
|
|
12505
|
-
return
|
|
12874
|
+
return statSync14(join52(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
12506
12875
|
} catch {
|
|
12507
12876
|
return false;
|
|
12508
12877
|
}
|
|
@@ -12515,8 +12884,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
12515
12884
|
let terraformBlind = false;
|
|
12516
12885
|
let totalDeclaredTools = 0;
|
|
12517
12886
|
for (const name of pluginNames) {
|
|
12518
|
-
const pluginDir2 =
|
|
12519
|
-
const manifestText =
|
|
12887
|
+
const pluginDir2 = join52(pluginsRoot, name);
|
|
12888
|
+
const manifestText = readFileSync38(join52(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
12520
12889
|
const manifest = extractManifestTools(manifestText);
|
|
12521
12890
|
if (manifest.parseError) {
|
|
12522
12891
|
findings.push({
|
|
@@ -12534,13 +12903,13 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
12534
12903
|
totalDeclaredTools += manifest.tools.length;
|
|
12535
12904
|
const pySources = pluginPythonFiles(pluginDir2).map((f) => ({
|
|
12536
12905
|
file: f,
|
|
12537
|
-
text:
|
|
12906
|
+
text: readFileSync38(f, "utf8")
|
|
12538
12907
|
}));
|
|
12539
12908
|
const resolver = buildSymbolResolver(pySources);
|
|
12540
12909
|
const registry = extractToolRegistryEntries(pySources, resolver);
|
|
12541
12910
|
if (registry.rawToolDefinitionCount > 0 && registry.entries.length === 0) registryBlind = true;
|
|
12542
12911
|
const tfFiles = pluginTerraformFiles(pluginDir2);
|
|
12543
|
-
const tfText = tfFiles.map((f) =>
|
|
12912
|
+
const tfText = tfFiles.map((f) => readFileSync38(f, "utf8")).join("\n");
|
|
12544
12913
|
const terraform = extractTerraformEnvKeys(tfText);
|
|
12545
12914
|
if (terraform.rawMarkerCount > 0 && terraform.resolvedBlockCount === 0) terraformBlind = true;
|
|
12546
12915
|
for (const toolName of manifest.tools) {
|
|
@@ -12614,7 +12983,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
12614
12983
|
requiredEnvVars: envResult.envVars,
|
|
12615
12984
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
12616
12985
|
status: anyWired ? "ok" : "missing-env",
|
|
12617
|
-
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 ${
|
|
12986
|
+
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 ${join52(pluginDir2, "terraform")}, so this deployment can never supply it`
|
|
12618
12987
|
});
|
|
12619
12988
|
}
|
|
12620
12989
|
}
|
|
@@ -12645,10 +13014,10 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
12645
13014
|
|
|
12646
13015
|
// src/scripts/check-plugin-tool-supply.ts
|
|
12647
13016
|
async function runPluginToolSupplyCheck() {
|
|
12648
|
-
const root = (await
|
|
13017
|
+
const root = (await execa19("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12649
13018
|
let allOk = true;
|
|
12650
|
-
const pluginsRoot =
|
|
12651
|
-
if (!
|
|
13019
|
+
const pluginsRoot = join53(root, "services", "_plugins");
|
|
13020
|
+
if (!existsSync44(pluginsRoot)) {
|
|
12652
13021
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
12653
13022
|
} else {
|
|
12654
13023
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -12678,8 +13047,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
12678
13047
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
12679
13048
|
}
|
|
12680
13049
|
}
|
|
12681
|
-
const servicesApiRoot =
|
|
12682
|
-
if (!
|
|
13050
|
+
const servicesApiRoot = join53(root, "services", "api");
|
|
13051
|
+
if (!existsSync44(servicesApiRoot)) {
|
|
12683
13052
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
12684
13053
|
} else {
|
|
12685
13054
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -12725,7 +13094,7 @@ async function runPluginToolSupplyCheck() {
|
|
|
12725
13094
|
}
|
|
12726
13095
|
|
|
12727
13096
|
// src/scripts/check-release-subject.ts
|
|
12728
|
-
import { execa as
|
|
13097
|
+
import { execa as execa20 } from "execa";
|
|
12729
13098
|
|
|
12730
13099
|
// src/lib/release-version.ts
|
|
12731
13100
|
var MINOR_TYPES = /* @__PURE__ */ new Set(["feat"]);
|
|
@@ -12762,7 +13131,7 @@ async function fetchPrTitleViaGh({
|
|
|
12762
13131
|
PR_NUMBER,
|
|
12763
13132
|
GH_REPO
|
|
12764
13133
|
}) {
|
|
12765
|
-
const { stdout } = await
|
|
13134
|
+
const { stdout } = await execa20(
|
|
12766
13135
|
"gh",
|
|
12767
13136
|
["pr", "view", PR_NUMBER, "--repo", GH_REPO, "--json", "title", "--jq", ".title"],
|
|
12768
13137
|
{ env: { ...process.env, GH_TOKEN } }
|
|
@@ -12798,7 +13167,7 @@ async function resolveReleaseSubject({
|
|
|
12798
13167
|
);
|
|
12799
13168
|
}
|
|
12800
13169
|
}
|
|
12801
|
-
return (await
|
|
13170
|
+
return (await execa20("git", ["log", "-1", "--format=%s"], { cwd })).stdout.trim();
|
|
12802
13171
|
}
|
|
12803
13172
|
async function runReleaseSubjectCheck(argv) {
|
|
12804
13173
|
const base = process.env["GITHUB_BASE_REF"] ?? argv[0];
|
|
@@ -12806,9 +13175,9 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
12806
13175
|
console.error("No base ref: set GITHUB_BASE_REF or pass a base branch as the first argument.");
|
|
12807
13176
|
process.exit(2);
|
|
12808
13177
|
}
|
|
12809
|
-
const root = (await
|
|
12810
|
-
await
|
|
12811
|
-
const { stdout } = await
|
|
13178
|
+
const root = (await execa20("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13179
|
+
await execa20("git", ["fetch", "--quiet", "origin", base], { cwd: root, reject: false });
|
|
13180
|
+
const { stdout } = await execa20("git", ["diff", "--name-only", `origin/${base}...HEAD`], {
|
|
12812
13181
|
cwd: root
|
|
12813
13182
|
});
|
|
12814
13183
|
const changedFiles = stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
@@ -12856,13 +13225,13 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
12856
13225
|
}
|
|
12857
13226
|
|
|
12858
13227
|
// src/scripts/check-skeleton-drift.ts
|
|
12859
|
-
import { existsSync as
|
|
12860
|
-
import { join as
|
|
12861
|
-
import { execa as
|
|
13228
|
+
import { existsSync as existsSync45, readdirSync as readdirSync25 } from "fs";
|
|
13229
|
+
import { join as join55 } from "path";
|
|
13230
|
+
import { execa as execa21 } from "execa";
|
|
12862
13231
|
|
|
12863
13232
|
// src/lib/skeleton-drift-guard.ts
|
|
12864
|
-
import { readFileSync as
|
|
12865
|
-
import { join as
|
|
13233
|
+
import { readFileSync as readFileSync39, readdirSync as readdirSync24, statSync as statSync15 } from "fs";
|
|
13234
|
+
import { join as join54 } from "path";
|
|
12866
13235
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
12867
13236
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
12868
13237
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -12920,16 +13289,16 @@ function walk(dir, base = dir) {
|
|
|
12920
13289
|
const out = [];
|
|
12921
13290
|
let entries;
|
|
12922
13291
|
try {
|
|
12923
|
-
entries =
|
|
13292
|
+
entries = readdirSync24(dir);
|
|
12924
13293
|
} catch {
|
|
12925
13294
|
return out;
|
|
12926
13295
|
}
|
|
12927
13296
|
for (const entry of entries) {
|
|
12928
13297
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
12929
|
-
const abs =
|
|
13298
|
+
const abs = join54(dir, entry);
|
|
12930
13299
|
let isDir;
|
|
12931
13300
|
try {
|
|
12932
|
-
isDir =
|
|
13301
|
+
isDir = statSync15(abs).isDirectory();
|
|
12933
13302
|
} catch {
|
|
12934
13303
|
continue;
|
|
12935
13304
|
}
|
|
@@ -12948,7 +13317,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
12948
13317
|
if (!rule.appliesTo(rel)) continue;
|
|
12949
13318
|
let contents;
|
|
12950
13319
|
try {
|
|
12951
|
-
contents =
|
|
13320
|
+
contents = readFileSync39(join54(skeletonRoot, rel), "utf8");
|
|
12952
13321
|
} catch {
|
|
12953
13322
|
continue;
|
|
12954
13323
|
}
|
|
@@ -12977,23 +13346,23 @@ function formatViolations2(violations) {
|
|
|
12977
13346
|
|
|
12978
13347
|
// src/scripts/check-skeleton-drift.ts
|
|
12979
13348
|
function discoverSkeletons(root) {
|
|
12980
|
-
const skeletonsDir =
|
|
13349
|
+
const skeletonsDir = join55(root, "_skeletons");
|
|
12981
13350
|
let entries;
|
|
12982
13351
|
try {
|
|
12983
|
-
entries =
|
|
13352
|
+
entries = readdirSync25(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
12984
13353
|
} catch {
|
|
12985
13354
|
return [];
|
|
12986
13355
|
}
|
|
12987
|
-
return entries.filter((name) =>
|
|
13356
|
+
return entries.filter((name) => existsSync45(join55(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
12988
13357
|
}
|
|
12989
13358
|
async function runSkeletonDriftCheck() {
|
|
12990
|
-
const root = (await
|
|
13359
|
+
const root = (await execa21("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12991
13360
|
const skeletons = discoverSkeletons(root);
|
|
12992
13361
|
let filesConsidered = 0;
|
|
12993
13362
|
for (const name of skeletons) {
|
|
12994
|
-
const skeletonRoot =
|
|
13363
|
+
const skeletonRoot = join55(root, "_skeletons", name);
|
|
12995
13364
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
12996
|
-
if (
|
|
13365
|
+
if (existsSync45(join55(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
12997
13366
|
filesConsidered += 1;
|
|
12998
13367
|
}
|
|
12999
13368
|
}
|
|
@@ -13007,7 +13376,7 @@ async function runSkeletonDriftCheck() {
|
|
|
13007
13376
|
process.exit(1);
|
|
13008
13377
|
}
|
|
13009
13378
|
const violations = skeletons.flatMap(
|
|
13010
|
-
(name) => auditSkeleton(
|
|
13379
|
+
(name) => auditSkeleton(join55(root, "_skeletons", name), name)
|
|
13011
13380
|
);
|
|
13012
13381
|
if (violations.length > 0) {
|
|
13013
13382
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -13019,9 +13388,9 @@ async function runSkeletonDriftCheck() {
|
|
|
13019
13388
|
}
|
|
13020
13389
|
|
|
13021
13390
|
// src/scripts/check-terraform-input.ts
|
|
13022
|
-
import { execa as
|
|
13391
|
+
import { execa as execa22 } from "execa";
|
|
13023
13392
|
async function runTerraformInputCheck() {
|
|
13024
|
-
const root = (await
|
|
13393
|
+
const root = (await execa22("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13025
13394
|
const files = findWorkflowFiles(root);
|
|
13026
13395
|
console.log(`audited ${files.length} workflow file(s) under ${root}`);
|
|
13027
13396
|
if (files.length === 0) {
|
|
@@ -13043,8 +13412,8 @@ async function runTerraformInputCheck() {
|
|
|
13043
13412
|
}
|
|
13044
13413
|
|
|
13045
13414
|
// src/commands/check.ts
|
|
13046
|
-
var checkCommand = new
|
|
13047
|
-
"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) run in CI and git hooks, plus out-of-band audits (branch protection)"
|
|
13415
|
+
var checkCommand = new Command24("check").description(
|
|
13416
|
+
"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) run in CI and git hooks, plus out-of-band audits (branch protection, plugin-staleness)"
|
|
13048
13417
|
);
|
|
13049
13418
|
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 () => {
|
|
13050
13419
|
await runOwnershipCheck(rawArgsAfter("ownership"));
|
|
@@ -13129,16 +13498,26 @@ checkCommand.command("branch-protection").description(
|
|
|
13129
13498
|
).action(async (opts) => {
|
|
13130
13499
|
await runBranchProtectionCheck(opts.repo, { fix: opts.fix });
|
|
13131
13500
|
});
|
|
13501
|
+
checkCommand.command("plugin-staleness").description(
|
|
13502
|
+
"Advisory: report how far each services/<name>/ vendored plugin has drifted from its source (#1547) \u2014 an instance can drift arbitrarily far behind a plugin repo while both sides' CI stays green, because neither has an opinion about the gap between them. Deliberately NEVER fails this check, unlike every other one in this group: an instance may legitimately pin a plugin version, and staleness moving is not a defect the way an ownership violation or a namespace collision is. Run `biffo plugin staleness` directly for the same measurement with a real 0/1/2 exit code, if you want to gate on it."
|
|
13503
|
+
).action(async () => {
|
|
13504
|
+
const cwd = process.env["BIFFO_ORIGINAL_CWD"] || process.cwd();
|
|
13505
|
+
const results = await checkPluginStaleness(cwd, {
|
|
13506
|
+
registry: new RegistryAdapter(),
|
|
13507
|
+
git: new GitAdapter()
|
|
13508
|
+
});
|
|
13509
|
+
console.log(formatStalenessReport(results));
|
|
13510
|
+
});
|
|
13132
13511
|
function rawArgsAfter(subcommand) {
|
|
13133
13512
|
const at = process.argv.indexOf(subcommand);
|
|
13134
13513
|
return at === -1 ? [] : process.argv.slice(at + 1);
|
|
13135
13514
|
}
|
|
13136
13515
|
|
|
13137
13516
|
// src/commands/doctor.ts
|
|
13138
|
-
import { existsSync as
|
|
13139
|
-
import { join as
|
|
13517
|
+
import { existsSync as existsSync46, readFileSync as readFileSync40 } from "fs";
|
|
13518
|
+
import { join as join56, resolve as resolve20 } from "path";
|
|
13140
13519
|
import chalk21 from "chalk";
|
|
13141
|
-
import { Command as
|
|
13520
|
+
import { Command as Command25 } from "commander";
|
|
13142
13521
|
|
|
13143
13522
|
// src/lib/doctor.ts
|
|
13144
13523
|
function checkCheckoutCurrency(facts) {
|
|
@@ -13261,10 +13640,10 @@ function runDoctorChecks(facts) {
|
|
|
13261
13640
|
|
|
13262
13641
|
// src/commands/doctor.ts
|
|
13263
13642
|
var INTEGRATION_BRANCH = "dev";
|
|
13264
|
-
var doctorCommand = new
|
|
13643
|
+
var doctorCommand = new Command25("doctor").description(
|
|
13265
13644
|
"Report repo-state conditions that make everything read from this checkout unreliable"
|
|
13266
13645
|
).option("--cwd <path>", "Repo root to inspect (defaults to the current directory)").option("--no-fetch", "Skip the fetch; report against refs as they already are locally").action(async (options) => {
|
|
13267
|
-
const cwd = options.cwd ?
|
|
13646
|
+
const cwd = options.cwd ? resolve20(options.cwd) : process.cwd();
|
|
13268
13647
|
try {
|
|
13269
13648
|
const findings = await runDoctor({ cwd, fetch: options.fetch !== false });
|
|
13270
13649
|
printFindings(findings);
|
|
@@ -13315,10 +13694,10 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
|
|
|
13315
13694
|
return runDoctorChecks(facts);
|
|
13316
13695
|
}
|
|
13317
13696
|
function readLocalCoreVersion(cwd) {
|
|
13318
|
-
const path =
|
|
13319
|
-
if (!
|
|
13697
|
+
const path = join56(cwd, INSTANCE_CORE_FILE);
|
|
13698
|
+
if (!existsSync46(path)) return null;
|
|
13320
13699
|
try {
|
|
13321
|
-
return extractVersionField(
|
|
13700
|
+
return extractVersionField(readFileSync40(path, "utf8"));
|
|
13322
13701
|
} catch {
|
|
13323
13702
|
return null;
|
|
13324
13703
|
}
|
|
@@ -13338,10 +13717,10 @@ function extractVersionField(contents) {
|
|
|
13338
13717
|
return match?.[1] ?? null;
|
|
13339
13718
|
}
|
|
13340
13719
|
function readFossil(cwd) {
|
|
13341
|
-
const path =
|
|
13342
|
-
if (!
|
|
13720
|
+
const path = join56(cwd, CORE_VERSION_FILE);
|
|
13721
|
+
if (!existsSync46(path)) return null;
|
|
13343
13722
|
try {
|
|
13344
|
-
const value =
|
|
13723
|
+
const value = readFileSync40(path, "utf8").trim();
|
|
13345
13724
|
return value === "" ? null : value;
|
|
13346
13725
|
} catch {
|
|
13347
13726
|
return null;
|
|
@@ -13374,9 +13753,9 @@ function printFindings(findings) {
|
|
|
13374
13753
|
import { execSync as execSync7 } from "child_process";
|
|
13375
13754
|
import { GetCallerIdentityCommand as GetCallerIdentityCommand3, STSClient as STSClient3 } from "@aws-sdk/client-sts";
|
|
13376
13755
|
import chalk22 from "chalk";
|
|
13377
|
-
import { Command as
|
|
13756
|
+
import { Command as Command26 } from "commander";
|
|
13378
13757
|
import inquirer8 from "inquirer";
|
|
13379
|
-
var teardownCommand = new
|
|
13758
|
+
var teardownCommand = new Command26("teardown").description(
|
|
13380
13759
|
"Destroy all infrastructure then remove the repo, IAM role, and state bucket \u2014 single command"
|
|
13381
13760
|
).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(
|
|
13382
13761
|
"--confirm <name>",
|
|
@@ -13787,16 +14166,16 @@ function resolveGithubToken4() {
|
|
|
13787
14166
|
import { spawnSync } from "child_process";
|
|
13788
14167
|
import { dirname as dirname12 } from "path";
|
|
13789
14168
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
13790
|
-
import { Command as
|
|
14169
|
+
import { Command as Command27 } from "commander";
|
|
13791
14170
|
|
|
13792
14171
|
// src/lib/packaged-scripts.ts
|
|
13793
|
-
import { existsSync as
|
|
13794
|
-
import { dirname as dirname11, join as
|
|
14172
|
+
import { existsSync as existsSync47 } from "fs";
|
|
14173
|
+
import { dirname as dirname11, join as join57 } from "path";
|
|
13795
14174
|
function findPackagedScript(startDir, relativePath) {
|
|
13796
14175
|
let dir = startDir;
|
|
13797
14176
|
for (; ; ) {
|
|
13798
|
-
const candidate =
|
|
13799
|
-
if (
|
|
14177
|
+
const candidate = join57(dir, relativePath);
|
|
14178
|
+
if (existsSync47(candidate)) return candidate;
|
|
13800
14179
|
const parent = dirname11(dir);
|
|
13801
14180
|
if (parent === dir) return null;
|
|
13802
14181
|
dir = parent;
|
|
@@ -13816,7 +14195,7 @@ function runPackagedScript(script, args, cwd) {
|
|
|
13816
14195
|
return result.status === null ? 2 : result.status;
|
|
13817
14196
|
}
|
|
13818
14197
|
function packagedScriptCommand(spec) {
|
|
13819
|
-
const command = new
|
|
14198
|
+
const command = new Command27(spec.name).description(spec.description).allowExcessArguments(true).allowUnknownOption(true);
|
|
13820
14199
|
if (spec.argument) command.argument(`<${spec.argument.name}>`, spec.argument.description);
|
|
13821
14200
|
return command.action(() => {
|
|
13822
14201
|
const here = dirname12(fileURLToPath6(import.meta.url));
|
|
@@ -13905,7 +14284,7 @@ var runnerDropForensicsCommand = packagedScriptCommand({
|
|
|
13905
14284
|
});
|
|
13906
14285
|
|
|
13907
14286
|
// src/index.ts
|
|
13908
|
-
var program = new
|
|
14287
|
+
var program = new Command28();
|
|
13909
14288
|
function cliVersion() {
|
|
13910
14289
|
try {
|
|
13911
14290
|
return getLatestCoreVersion();
|