@biffo/cli 0.315.8 → 0.316.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
CHANGED
|
@@ -9311,8 +9311,8 @@ function printScopeSeamEntitlement(name) {
|
|
|
9311
9311
|
}
|
|
9312
9312
|
|
|
9313
9313
|
// src/commands/plugin-install.ts
|
|
9314
|
-
import { cpSync as cpSync5, existsSync as
|
|
9315
|
-
import { join as
|
|
9314
|
+
import { cpSync as cpSync5, existsSync as existsSync32, mkdirSync as mkdirSync11, readFileSync as readFileSync24, statSync as statSync7, writeFileSync as writeFileSync13 } from "fs";
|
|
9315
|
+
import { join as join34, relative as relative4, resolve as resolve12 } from "path";
|
|
9316
9316
|
import chalk15 from "chalk";
|
|
9317
9317
|
import { Command as Command15 } from "commander";
|
|
9318
9318
|
|
|
@@ -9409,9 +9409,122 @@ ${lines.join("\n")}
|
|
|
9409
9409
|
Supply each with --config <name>=<value> (repeatable) and re-run install. For a 'secret', pass the SSM parameter PATH holding the credential (create the parameter first, e.g. \`aws ssm put-parameter --type SecureString ...\`), never the credential itself.`;
|
|
9410
9410
|
}
|
|
9411
9411
|
|
|
9412
|
-
// src/lib/plugin-
|
|
9412
|
+
// src/lib/plugin-frontend-registry.ts
|
|
9413
9413
|
import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "fs";
|
|
9414
9414
|
import { join as join29 } from "path";
|
|
9415
|
+
var PLUGIN_REGISTRY_RELATIVE_PATH = "apps/frontend/src/lib/plugins.ts";
|
|
9416
|
+
var REGISTRY_START_MARKER = "// BIFFO-PLUGIN-REGISTRY:START \u2014 managed by `biffo plugin install`/`uninstall`. Do not hand-edit.";
|
|
9417
|
+
var REGISTRY_END_MARKER = "// BIFFO-PLUGIN-REGISTRY:END";
|
|
9418
|
+
function titleFromSlug(slug) {
|
|
9419
|
+
return slug.split("-").filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
9420
|
+
}
|
|
9421
|
+
function frontendUrlForSlug(slug) {
|
|
9422
|
+
return `/api/v1/plugins/${slug}/ui`;
|
|
9423
|
+
}
|
|
9424
|
+
function registryPath2(cwd) {
|
|
9425
|
+
return join29(cwd, PLUGIN_REGISTRY_RELATIVE_PATH);
|
|
9426
|
+
}
|
|
9427
|
+
function missingRegistryError(cwd, pluginSlug) {
|
|
9428
|
+
return new Error(
|
|
9429
|
+
`${PLUGIN_REGISTRY_RELATIVE_PATH} does not exist in ${cwd} \u2014 this sibling checkout has not adopted the dashboard dynamic-route pattern (ADR-0021 \xA72), so plugin "${pluginSlug}"'s \`user_frontend\` block has nowhere to register. Add the file with a managed \`INSTALLED_PLUGINS\` array (see plugin-frontend-registry.ts for the exact marker contract) before installing a user-facing plugin here, or install a plugin with no \`user_frontend\` block instead.`
|
|
9430
|
+
);
|
|
9431
|
+
}
|
|
9432
|
+
function malformedRegistryError(cwd, pluginSlug) {
|
|
9433
|
+
return new Error(
|
|
9434
|
+
`${PLUGIN_REGISTRY_RELATIVE_PATH} in ${cwd} does not carry the managed "${REGISTRY_START_MARKER}" / "${REGISTRY_END_MARKER}" region that plugin "${pluginSlug}"'s install needs to write into. Add that managed region around the INSTALLED_PLUGINS array contents (see plugin-frontend-registry.ts's module docstring) rather than hand-editing the array.`
|
|
9435
|
+
);
|
|
9436
|
+
}
|
|
9437
|
+
function findManagedRegion(source) {
|
|
9438
|
+
const startIdx = source.indexOf(REGISTRY_START_MARKER);
|
|
9439
|
+
if (startIdx === -1) return null;
|
|
9440
|
+
const bodyStart = startIdx + REGISTRY_START_MARKER.length;
|
|
9441
|
+
const endIdx = source.indexOf(REGISTRY_END_MARKER, bodyStart);
|
|
9442
|
+
if (endIdx === -1) return null;
|
|
9443
|
+
return {
|
|
9444
|
+
region: { before: source.slice(0, bodyStart), after: source.slice(endIdx) },
|
|
9445
|
+
body: source.slice(bodyStart, endIdx)
|
|
9446
|
+
};
|
|
9447
|
+
}
|
|
9448
|
+
var SLUG_FIELD_PATTERN = /\bslug\s*:\s*(['"])([a-zA-Z0-9_-]+)\1/;
|
|
9449
|
+
function extractSlug(entryRaw) {
|
|
9450
|
+
const match = SLUG_FIELD_PATTERN.exec(entryRaw);
|
|
9451
|
+
return match ? match[2] : null;
|
|
9452
|
+
}
|
|
9453
|
+
function splitEntries(body) {
|
|
9454
|
+
const entries = [];
|
|
9455
|
+
const n = body.length;
|
|
9456
|
+
let i = 0;
|
|
9457
|
+
while (i < n) {
|
|
9458
|
+
const ch = body[i];
|
|
9459
|
+
if (ch !== "{") {
|
|
9460
|
+
i++;
|
|
9461
|
+
continue;
|
|
9462
|
+
}
|
|
9463
|
+
const start = i + 1;
|
|
9464
|
+
let depth = 1;
|
|
9465
|
+
let j = start;
|
|
9466
|
+
let quote = null;
|
|
9467
|
+
while (j < n && depth > 0) {
|
|
9468
|
+
const c = body[j];
|
|
9469
|
+
if (quote) {
|
|
9470
|
+
if (c === "\\") {
|
|
9471
|
+
j += 2;
|
|
9472
|
+
continue;
|
|
9473
|
+
}
|
|
9474
|
+
if (c === quote) quote = null;
|
|
9475
|
+
} else if (c === '"' || c === "'" || c === "`") {
|
|
9476
|
+
quote = c;
|
|
9477
|
+
} else if (c === "{") {
|
|
9478
|
+
depth++;
|
|
9479
|
+
} else if (c === "}") {
|
|
9480
|
+
depth--;
|
|
9481
|
+
}
|
|
9482
|
+
j++;
|
|
9483
|
+
}
|
|
9484
|
+
const raw = body.slice(start, j - 1);
|
|
9485
|
+
entries.push({ raw, slug: extractSlug(raw) });
|
|
9486
|
+
i = j;
|
|
9487
|
+
}
|
|
9488
|
+
return entries;
|
|
9489
|
+
}
|
|
9490
|
+
function serializeEntryRaw(entry) {
|
|
9491
|
+
return `
|
|
9492
|
+
slug: ${JSON.stringify(entry.slug)},
|
|
9493
|
+
title: ${JSON.stringify(entry.title)},
|
|
9494
|
+
frontendUrl: ${JSON.stringify(entry.frontendUrl)},
|
|
9495
|
+
`;
|
|
9496
|
+
}
|
|
9497
|
+
function assertPluginRegistryReady(cwd, pluginSlug) {
|
|
9498
|
+
readManagedEntries(cwd, pluginSlug);
|
|
9499
|
+
}
|
|
9500
|
+
function readManagedEntries(cwd, pluginSlug) {
|
|
9501
|
+
const path = registryPath2(cwd);
|
|
9502
|
+
if (!existsSync28(path)) throw missingRegistryError(cwd, pluginSlug);
|
|
9503
|
+
const source = readFileSync21(path, "utf8");
|
|
9504
|
+
const found = findManagedRegion(source);
|
|
9505
|
+
if (!found) throw malformedRegistryError(cwd, pluginSlug);
|
|
9506
|
+
return { source, region: found.region, entries: splitEntries(found.body) };
|
|
9507
|
+
}
|
|
9508
|
+
function writeManagedEntries(cwd, region, entries) {
|
|
9509
|
+
const body = entries.length > 0 ? "\n" + entries.map((e) => ` {${e.raw}},
|
|
9510
|
+
`).join("") : "\n";
|
|
9511
|
+
writeFileSync10(registryPath2(cwd), region.before + body + region.after, "utf8");
|
|
9512
|
+
}
|
|
9513
|
+
function upsertPluginRegistryEntry(cwd, entry) {
|
|
9514
|
+
const { region, entries } = readManagedEntries(cwd, entry.slug);
|
|
9515
|
+
const preserved = entries.filter((e) => e.slug !== entry.slug);
|
|
9516
|
+
const next = { raw: serializeEntryRaw(entry), slug: entry.slug };
|
|
9517
|
+
writeManagedEntries(cwd, region, [...preserved, next]);
|
|
9518
|
+
}
|
|
9519
|
+
function removePluginRegistryEntry(cwd, pluginSlug) {
|
|
9520
|
+
const { region, entries } = readManagedEntries(cwd, pluginSlug);
|
|
9521
|
+
const next = entries.filter((e) => e.slug !== pluginSlug);
|
|
9522
|
+
writeManagedEntries(cwd, region, next);
|
|
9523
|
+
}
|
|
9524
|
+
|
|
9525
|
+
// src/lib/plugin-provenance.ts
|
|
9526
|
+
import { existsSync as existsSync29, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "fs";
|
|
9527
|
+
import { join as join30 } from "path";
|
|
9415
9528
|
var PLUGIN_PROVENANCE_FILENAME = ".biffo-plugin-provenance.json";
|
|
9416
9529
|
function isPluginProvenance(value) {
|
|
9417
9530
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -9419,11 +9532,11 @@ function isPluginProvenance(value) {
|
|
|
9419
9532
|
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";
|
|
9420
9533
|
}
|
|
9421
9534
|
function readProvenance(pluginDir2) {
|
|
9422
|
-
const path =
|
|
9423
|
-
if (!
|
|
9535
|
+
const path = join30(pluginDir2, PLUGIN_PROVENANCE_FILENAME);
|
|
9536
|
+
if (!existsSync29(path)) return { status: "absent" };
|
|
9424
9537
|
let parsed;
|
|
9425
9538
|
try {
|
|
9426
|
-
parsed = JSON.parse(
|
|
9539
|
+
parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
9427
9540
|
} catch (err) {
|
|
9428
9541
|
return {
|
|
9429
9542
|
status: "invalid",
|
|
@@ -9439,7 +9552,7 @@ function readProvenance(pluginDir2) {
|
|
|
9439
9552
|
return { status: "present", record: parsed };
|
|
9440
9553
|
}
|
|
9441
9554
|
function writePluginProvenance(pluginDir2, record) {
|
|
9442
|
-
|
|
9555
|
+
writeFileSync11(join30(pluginDir2, PLUGIN_PROVENANCE_FILENAME), `${JSON.stringify(record, null, 2)}
|
|
9443
9556
|
`);
|
|
9444
9557
|
}
|
|
9445
9558
|
function reconcileProvenance(previous, next) {
|
|
@@ -9489,8 +9602,8 @@ async function tryGit(cwd, args) {
|
|
|
9489
9602
|
}
|
|
9490
9603
|
|
|
9491
9604
|
// src/lib/plugin-seed-vendor.ts
|
|
9492
|
-
import { cpSync as cpSync3, existsSync as
|
|
9493
|
-
import { join as
|
|
9605
|
+
import { cpSync as cpSync3, existsSync as existsSync30, mkdirSync as mkdirSync9, readdirSync as readdirSync12, rmSync as rmSync8 } from "fs";
|
|
9606
|
+
import { join as join31 } from "path";
|
|
9494
9607
|
var VENDOR_PREFIX = "_plugin-";
|
|
9495
9608
|
function pluginSeedImportDir(pluginName) {
|
|
9496
9609
|
return `db/imports/${VENDOR_PREFIX}${pluginName}`;
|
|
@@ -9499,8 +9612,8 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
|
9499
9612
|
if (!manifest.seed) {
|
|
9500
9613
|
return { vendored: false };
|
|
9501
9614
|
}
|
|
9502
|
-
const sourceSeedDir =
|
|
9503
|
-
if (!
|
|
9615
|
+
const sourceSeedDir = join31(pluginSourceDir, manifest.seed.dir);
|
|
9616
|
+
if (!existsSync30(sourceSeedDir)) {
|
|
9504
9617
|
throw new Error(
|
|
9505
9618
|
`${manifest.name}'s manifest declares seed.dir '${manifest.seed.dir}', but ${sourceSeedDir} does not exist in the plugin's source.`
|
|
9506
9619
|
);
|
|
@@ -9512,11 +9625,11 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
|
9512
9625
|
);
|
|
9513
9626
|
}
|
|
9514
9627
|
const relTargetDir = pluginSeedImportDir(manifest.name);
|
|
9515
|
-
const targetDir =
|
|
9628
|
+
const targetDir = join31(cwd, relTargetDir);
|
|
9516
9629
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
9517
9630
|
mkdirSync9(targetDir, { recursive: true });
|
|
9518
9631
|
for (const file of sqlFiles) {
|
|
9519
|
-
cpSync3(
|
|
9632
|
+
cpSync3(join31(sourceSeedDir, file), join31(targetDir, file));
|
|
9520
9633
|
}
|
|
9521
9634
|
log.success(
|
|
9522
9635
|
`Vendored ${sqlFiles.length} seed file(s) to ${relTargetDir}/ (baseline_tables: ${manifest.seed.baseline_tables.join(", ") || "none declared"})`
|
|
@@ -9529,7 +9642,7 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
|
9529
9642
|
|
|
9530
9643
|
// src/lib/plugin-source-copy.ts
|
|
9531
9644
|
import { copyFileSync as copyFileSync2, cpSync as cpSync4, mkdirSync as mkdirSync10 } from "fs";
|
|
9532
|
-
import { basename as basename2, dirname as dirname9, join as
|
|
9645
|
+
import { basename as basename2, dirname as dirname9, join as join32 } from "path";
|
|
9533
9646
|
var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
9534
9647
|
".git",
|
|
9535
9648
|
".venv",
|
|
@@ -9545,9 +9658,9 @@ async function copyPluginSource(sourceDir, targetDir) {
|
|
|
9545
9658
|
if (await isGitWorkingTree2(sourceDir)) {
|
|
9546
9659
|
const files = await listGitFiles(sourceDir);
|
|
9547
9660
|
for (const relPath of files) {
|
|
9548
|
-
const destPath =
|
|
9661
|
+
const destPath = join32(targetDir, relPath);
|
|
9549
9662
|
mkdirSync10(dirname9(destPath), { recursive: true });
|
|
9550
|
-
copyFileSync2(
|
|
9663
|
+
copyFileSync2(join32(sourceDir, relPath), destPath);
|
|
9551
9664
|
}
|
|
9552
9665
|
return { usedGitIgnoreRules: true };
|
|
9553
9666
|
}
|
|
@@ -9579,8 +9692,8 @@ async function listGitFiles(dir) {
|
|
|
9579
9692
|
}
|
|
9580
9693
|
|
|
9581
9694
|
// src/lib/plugin-workspace-sources.ts
|
|
9582
|
-
import { existsSync as
|
|
9583
|
-
import { join as
|
|
9695
|
+
import { existsSync as existsSync31, readdirSync as readdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
|
|
9696
|
+
import { join as join33 } from "path";
|
|
9584
9697
|
function readTomlStringArray(text, key) {
|
|
9585
9698
|
const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
|
|
9586
9699
|
if (!open) return [];
|
|
@@ -9624,9 +9737,9 @@ function readDependencyNames(text) {
|
|
|
9624
9737
|
return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
|
|
9625
9738
|
}
|
|
9626
9739
|
function workspaceMemberNames(instanceRoot) {
|
|
9627
|
-
const rootPyproject =
|
|
9628
|
-
if (!
|
|
9629
|
-
const text =
|
|
9740
|
+
const rootPyproject = join33(instanceRoot, "pyproject.toml");
|
|
9741
|
+
if (!existsSync31(rootPyproject)) return /* @__PURE__ */ new Set();
|
|
9742
|
+
const text = readFileSync23(rootPyproject, "utf8");
|
|
9630
9743
|
const members = readTomlStringArray(text, "members");
|
|
9631
9744
|
const excluded = new Set(readTomlStringArray(text, "exclude"));
|
|
9632
9745
|
const dirs = [];
|
|
@@ -9635,7 +9748,7 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
9635
9748
|
const base = member.slice(0, -2);
|
|
9636
9749
|
let entries;
|
|
9637
9750
|
try {
|
|
9638
|
-
entries = readdirSync13(
|
|
9751
|
+
entries = readdirSync13(join33(instanceRoot, base), { withFileTypes: true });
|
|
9639
9752
|
} catch {
|
|
9640
9753
|
continue;
|
|
9641
9754
|
}
|
|
@@ -9649,9 +9762,9 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
9649
9762
|
}
|
|
9650
9763
|
const names = /* @__PURE__ */ new Set();
|
|
9651
9764
|
for (const dir of dirs) {
|
|
9652
|
-
const pp =
|
|
9653
|
-
if (!
|
|
9654
|
-
const name = readProjectName(
|
|
9765
|
+
const pp = join33(instanceRoot, dir, "pyproject.toml");
|
|
9766
|
+
if (!existsSync31(pp)) continue;
|
|
9767
|
+
const name = readProjectName(readFileSync23(pp, "utf8"));
|
|
9655
9768
|
if (name) names.add(name);
|
|
9656
9769
|
}
|
|
9657
9770
|
return names;
|
|
@@ -9662,8 +9775,8 @@ function existingWorkspaceSources(text) {
|
|
|
9662
9775
|
);
|
|
9663
9776
|
}
|
|
9664
9777
|
function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
|
|
9665
|
-
if (!
|
|
9666
|
-
const text =
|
|
9778
|
+
if (!existsSync31(pluginPyprojectPath) || memberNames.size === 0) return [];
|
|
9779
|
+
const text = readFileSync23(pluginPyprojectPath, "utf8");
|
|
9667
9780
|
const already = existingWorkspaceSources(text);
|
|
9668
9781
|
const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
|
|
9669
9782
|
if (toAdd.length === 0) return [];
|
|
@@ -9683,12 +9796,12 @@ ${lines.join("\n")}${text.slice(insertAt)}`;
|
|
|
9683
9796
|
${lines.join("\n")}
|
|
9684
9797
|
`;
|
|
9685
9798
|
}
|
|
9686
|
-
|
|
9799
|
+
writeFileSync12(pluginPyprojectPath, updated);
|
|
9687
9800
|
return toAdd;
|
|
9688
9801
|
}
|
|
9689
9802
|
function applyWorkspaceSources(targetDir, cwd, relTargetDir) {
|
|
9690
|
-
const pluginPyproject =
|
|
9691
|
-
if (!
|
|
9803
|
+
const pluginPyproject = join33(targetDir, "pyproject.toml");
|
|
9804
|
+
if (!existsSync31(pluginPyproject)) return;
|
|
9692
9805
|
const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(cwd));
|
|
9693
9806
|
if (sourced.length > 0) {
|
|
9694
9807
|
log.info(
|
|
@@ -9734,14 +9847,14 @@ var pluginInstallCommand = new Command15("install").description(
|
|
|
9734
9847
|
}
|
|
9735
9848
|
);
|
|
9736
9849
|
function resolveLocalPlugin(localPath) {
|
|
9737
|
-
if (!
|
|
9850
|
+
if (!existsSync32(localPath)) {
|
|
9738
9851
|
throw new Error(`--local path does not exist: ${localPath}`);
|
|
9739
9852
|
}
|
|
9740
9853
|
if (!statSync7(localPath).isDirectory()) {
|
|
9741
9854
|
throw new Error(`--local path is not a directory: ${localPath}`);
|
|
9742
9855
|
}
|
|
9743
|
-
const manifestPath =
|
|
9744
|
-
if (!
|
|
9856
|
+
const manifestPath = join34(localPath, "biffo.plugin.json");
|
|
9857
|
+
if (!existsSync32(manifestPath)) {
|
|
9745
9858
|
throw new Error(
|
|
9746
9859
|
`${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>\`.)`
|
|
9747
9860
|
);
|
|
@@ -9767,8 +9880,8 @@ function parsePluginTarget(target) {
|
|
|
9767
9880
|
async function cloneAndValidatePlugin(entry, git) {
|
|
9768
9881
|
const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
|
|
9769
9882
|
try {
|
|
9770
|
-
const manifestPath =
|
|
9771
|
-
if (!
|
|
9883
|
+
const manifestPath = join34(tmpDir, "biffo.plugin.json");
|
|
9884
|
+
if (!existsSync32(manifestPath)) {
|
|
9772
9885
|
throw new Error(
|
|
9773
9886
|
`Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
|
|
9774
9887
|
);
|
|
@@ -9796,8 +9909,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9796
9909
|
`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\`).`
|
|
9797
9910
|
);
|
|
9798
9911
|
}
|
|
9799
|
-
const servicesDir =
|
|
9800
|
-
if (!
|
|
9912
|
+
const servicesDir = join34(options.cwd, "services");
|
|
9913
|
+
if (!existsSync32(servicesDir)) {
|
|
9801
9914
|
throw new Error(
|
|
9802
9915
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9803
9916
|
);
|
|
@@ -9815,10 +9928,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9815
9928
|
}
|
|
9816
9929
|
const pluginName = entry ? entry.name : source.name;
|
|
9817
9930
|
const relTargetDir = pluginDir(pluginName, "third-party");
|
|
9818
|
-
const targetDir =
|
|
9819
|
-
const modulesDir =
|
|
9931
|
+
const targetDir = join34(options.cwd, relTargetDir);
|
|
9932
|
+
const modulesDir = join34(options.cwd, "modules", "plugins", pluginName);
|
|
9820
9933
|
const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
|
|
9821
|
-
if (
|
|
9934
|
+
if (existsSync32(targetDir) && !inTreeSource) {
|
|
9822
9935
|
throw new Error(
|
|
9823
9936
|
`Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
|
|
9824
9937
|
);
|
|
@@ -9850,7 +9963,7 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9850
9963
|
log.success(
|
|
9851
9964
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
9852
9965
|
);
|
|
9853
|
-
const retiredShapeReasons = findRetiredFrontendShape(
|
|
9966
|
+
const retiredShapeReasons = findRetiredFrontendShape(join34(source.sourceDir, "terraform"));
|
|
9854
9967
|
if (retiredShapeReasons.length > 0) {
|
|
9855
9968
|
throw new Error(retiredFrontendShapeError(pluginName, retiredShapeReasons));
|
|
9856
9969
|
}
|
|
@@ -9862,6 +9975,9 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9862
9975
|
if (configSupply.missingRequired.length > 0) {
|
|
9863
9976
|
throw new Error(missingRequiredConfigMessage(pluginName, configSupply.missingRequired));
|
|
9864
9977
|
}
|
|
9978
|
+
if (manifest.user_frontend) {
|
|
9979
|
+
assertPluginRegistryReady(options.cwd, pluginName);
|
|
9980
|
+
}
|
|
9865
9981
|
if (inTreeSource) {
|
|
9866
9982
|
log.info(`${relTargetDir}/ is already in this checkout \u2014 installing in place.`);
|
|
9867
9983
|
} else {
|
|
@@ -9874,8 +9990,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9874
9990
|
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
9875
9991
|
applyWorkspaceSources(targetDir, options.cwd, relTargetDir);
|
|
9876
9992
|
const stagePaths = [relTargetDir];
|
|
9877
|
-
const tfSourceDir =
|
|
9878
|
-
if (
|
|
9993
|
+
const tfSourceDir = join34(targetDir, "terraform");
|
|
9994
|
+
if (existsSync32(tfSourceDir)) {
|
|
9879
9995
|
mkdirSync11(modulesDir, { recursive: true });
|
|
9880
9996
|
cpSync5(tfSourceDir, modulesDir, { recursive: true });
|
|
9881
9997
|
stagePaths.push(`modules/plugins/${pluginName}`);
|
|
@@ -9921,8 +10037,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9921
10037
|
stagePaths.push(seedResult.stagedPath);
|
|
9922
10038
|
}
|
|
9923
10039
|
if (manifest.config.length > 0) {
|
|
9924
|
-
const configFilePath =
|
|
9925
|
-
|
|
10040
|
+
const configFilePath = join34(targetDir, "biffo.plugin-config.json");
|
|
10041
|
+
writeFileSync13(
|
|
9926
10042
|
configFilePath,
|
|
9927
10043
|
JSON.stringify(
|
|
9928
10044
|
{
|
|
@@ -9945,6 +10061,15 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9945
10061
|
`Recorded ${configSupply.resolved.length}/${manifest.config.length} declared config value(s) at ${relative4(options.cwd, configFilePath)}`
|
|
9946
10062
|
);
|
|
9947
10063
|
}
|
|
10064
|
+
if (manifest.user_frontend) {
|
|
10065
|
+
upsertPluginRegistryEntry(options.cwd, {
|
|
10066
|
+
slug: pluginName,
|
|
10067
|
+
title: titleFromSlug(pluginName),
|
|
10068
|
+
frontendUrl: frontendUrlForSlug(pluginName)
|
|
10069
|
+
});
|
|
10070
|
+
stagePaths.push(PLUGIN_REGISTRY_RELATIVE_PATH);
|
|
10071
|
+
log.success(`Registered ${pluginName} in ${PLUGIN_REGISTRY_RELATIVE_PATH}`);
|
|
10072
|
+
}
|
|
9948
10073
|
const commitMessage = `feat(plugins): install ${pluginName}@${source.version}`;
|
|
9949
10074
|
await deps.git.add(options.cwd, stagePaths);
|
|
9950
10075
|
await deps.git.commit(options.cwd, commitMessage);
|
|
@@ -9979,7 +10104,7 @@ function printConfigWiringInstructions(pluginName, resolved) {
|
|
|
9979
10104
|
}
|
|
9980
10105
|
function parseManifestFile(path) {
|
|
9981
10106
|
try {
|
|
9982
|
-
return JSON.parse(
|
|
10107
|
+
return JSON.parse(readFileSync24(path, "utf8"));
|
|
9983
10108
|
} catch (err) {
|
|
9984
10109
|
throw new Error(`Could not parse ${path} as JSON: ${err.message}`);
|
|
9985
10110
|
}
|
|
@@ -10033,8 +10158,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource, suppliedConfig
|
|
|
10033
10158
|
}
|
|
10034
10159
|
|
|
10035
10160
|
// src/commands/plugin-list.ts
|
|
10036
|
-
import { existsSync as
|
|
10037
|
-
import { join as
|
|
10161
|
+
import { existsSync as existsSync33, readFileSync as readFileSync25 } from "fs";
|
|
10162
|
+
import { join as join35, resolve as resolve13 } from "path";
|
|
10038
10163
|
import chalk16 from "chalk";
|
|
10039
10164
|
import { Command as Command16 } from "commander";
|
|
10040
10165
|
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) => {
|
|
@@ -10047,8 +10172,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
|
|
|
10047
10172
|
}
|
|
10048
10173
|
});
|
|
10049
10174
|
async function runPluginList(options) {
|
|
10050
|
-
const servicesDir =
|
|
10051
|
-
if (!
|
|
10175
|
+
const servicesDir = join35(options.cwd, "services");
|
|
10176
|
+
if (!existsSync33(servicesDir)) {
|
|
10052
10177
|
throw new Error(
|
|
10053
10178
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
10054
10179
|
);
|
|
@@ -10056,7 +10181,7 @@ async function runPluginList(options) {
|
|
|
10056
10181
|
const plugins = [];
|
|
10057
10182
|
for (const location of findInstalledPlugins(options.cwd)) {
|
|
10058
10183
|
try {
|
|
10059
|
-
const manifest = validateManifest(JSON.parse(
|
|
10184
|
+
const manifest = validateManifest(JSON.parse(readFileSync25(location.manifestPath, "utf8")));
|
|
10060
10185
|
plugins.push({
|
|
10061
10186
|
name: manifest.name,
|
|
10062
10187
|
version: manifest.version,
|
|
@@ -10097,14 +10222,14 @@ import { resolve as resolve14 } from "path";
|
|
|
10097
10222
|
import { Command as Command17 } from "commander";
|
|
10098
10223
|
|
|
10099
10224
|
// src/lib/plugin-staleness.ts
|
|
10100
|
-
import { existsSync as
|
|
10101
|
-
import { join as
|
|
10225
|
+
import { existsSync as existsSync34, readFileSync as readFileSync26, readdirSync as readdirSync14, statSync as statSync8 } from "fs";
|
|
10226
|
+
import { join as join36, relative as relative5 } from "path";
|
|
10102
10227
|
function discoverVendoredPlugins(servicesDir) {
|
|
10103
|
-
if (!
|
|
10104
|
-
return readdirSync14(servicesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_") && e.name !== "api").map((e) => e.name).filter((name) =>
|
|
10228
|
+
if (!existsSync34(servicesDir)) return [];
|
|
10229
|
+
return readdirSync14(servicesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_") && e.name !== "api").map((e) => e.name).filter((name) => existsSync34(join36(servicesDir, name, "biffo.plugin.json"))).sort();
|
|
10105
10230
|
}
|
|
10106
10231
|
async function checkPluginStaleness(cwd, deps) {
|
|
10107
|
-
const servicesDir =
|
|
10232
|
+
const servicesDir = join36(cwd, "services");
|
|
10108
10233
|
const names = discoverVendoredPlugins(servicesDir);
|
|
10109
10234
|
let registryRepoByName = null;
|
|
10110
10235
|
const resolveRegistryRepo = async (name) => {
|
|
@@ -10120,7 +10245,7 @@ async function checkPluginStaleness(cwd, deps) {
|
|
|
10120
10245
|
};
|
|
10121
10246
|
const results = [];
|
|
10122
10247
|
for (const name of names) {
|
|
10123
|
-
results.push(await checkOnePlugin(
|
|
10248
|
+
results.push(await checkOnePlugin(join36(servicesDir, name), name, resolveRegistryRepo, deps.git));
|
|
10124
10249
|
}
|
|
10125
10250
|
return results;
|
|
10126
10251
|
}
|
|
@@ -10146,7 +10271,7 @@ async function checkOnePlugin(pluginDir2, name, resolveRegistryRepo, git) {
|
|
|
10146
10271
|
if (record?.sha && isFetchableUrl(record.origin)) {
|
|
10147
10272
|
return checkViaProvenance(name, record, record.origin, git);
|
|
10148
10273
|
}
|
|
10149
|
-
const localOrigin = record && !isFetchableUrl(record.origin) &&
|
|
10274
|
+
const localOrigin = record && !isFetchableUrl(record.origin) && existsSync34(record.origin) ? record.origin : null;
|
|
10150
10275
|
if (localOrigin) {
|
|
10151
10276
|
return checkViaContentDiff(name, pluginDir2, localOrigin, { isLocalDir: true }, git);
|
|
10152
10277
|
}
|
|
@@ -10280,8 +10405,8 @@ async function countDifferingFiles(sourceDir, pluginDir2) {
|
|
|
10280
10405
|
differing++;
|
|
10281
10406
|
continue;
|
|
10282
10407
|
}
|
|
10283
|
-
const a =
|
|
10284
|
-
const b =
|
|
10408
|
+
const a = readFileSync26(join36(sourceDir, relPath));
|
|
10409
|
+
const b = readFileSync26(join36(pluginDir2, relPath));
|
|
10285
10410
|
if (!a.equals(b)) differing++;
|
|
10286
10411
|
}
|
|
10287
10412
|
return differing;
|
|
@@ -10296,11 +10421,11 @@ function vendorFileList(dir) {
|
|
|
10296
10421
|
return new Set(walkExcluding(dir, dir, LOCAL_COPY_EXCLUDES));
|
|
10297
10422
|
}
|
|
10298
10423
|
function walkExcluding(root, dir, excludes) {
|
|
10299
|
-
if (!
|
|
10424
|
+
if (!existsSync34(dir)) return [];
|
|
10300
10425
|
const out = [];
|
|
10301
10426
|
for (const entry of readdirSync14(dir)) {
|
|
10302
10427
|
if (excludes.has(entry) || entry === ".git") continue;
|
|
10303
|
-
const full =
|
|
10428
|
+
const full = join36(dir, entry);
|
|
10304
10429
|
let stat;
|
|
10305
10430
|
try {
|
|
10306
10431
|
stat = statSync8(full);
|
|
@@ -10357,8 +10482,8 @@ var pluginStalenessCommand = new Command17("staleness").description(
|
|
|
10357
10482
|
});
|
|
10358
10483
|
|
|
10359
10484
|
// src/commands/plugin-sync-migrations.ts
|
|
10360
|
-
import { existsSync as
|
|
10361
|
-
import { join as
|
|
10485
|
+
import { existsSync as existsSync35 } from "fs";
|
|
10486
|
+
import { join as join37, relative as relative6, resolve as resolve15 } from "path";
|
|
10362
10487
|
import chalk17 from "chalk";
|
|
10363
10488
|
import { Command as Command18 } from "commander";
|
|
10364
10489
|
var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
@@ -10379,11 +10504,11 @@ var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
|
10379
10504
|
}
|
|
10380
10505
|
);
|
|
10381
10506
|
async function runPluginSyncMigrations(name, options, deps) {
|
|
10382
|
-
const servicesDir =
|
|
10383
|
-
if (!
|
|
10507
|
+
const servicesDir = join37(options.cwd, "services");
|
|
10508
|
+
if (!existsSync35(servicesDir)) {
|
|
10384
10509
|
throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
|
|
10385
10510
|
}
|
|
10386
|
-
if (name && !
|
|
10511
|
+
if (name && !existsSync35(join37(servicesDir, name, "biffo.plugin.json"))) {
|
|
10387
10512
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
10388
10513
|
}
|
|
10389
10514
|
if (options.dryRun) {
|
|
@@ -10419,8 +10544,8 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
10419
10544
|
}
|
|
10420
10545
|
|
|
10421
10546
|
// src/commands/plugin-uninstall.ts
|
|
10422
|
-
import { existsSync as
|
|
10423
|
-
import { join as
|
|
10547
|
+
import { existsSync as existsSync36, readFileSync as readFileSync27, rmSync as rmSync9 } from "fs";
|
|
10548
|
+
import { join as join38, resolve as resolve16 } from "path";
|
|
10424
10549
|
import chalk18 from "chalk";
|
|
10425
10550
|
import { Command as Command19 } from "commander";
|
|
10426
10551
|
import inquirer6 from "inquirer";
|
|
@@ -10452,28 +10577,33 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
10452
10577
|
if (!NAME_PATTERN2.test(name)) {
|
|
10453
10578
|
throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
|
|
10454
10579
|
}
|
|
10455
|
-
const servicesDir =
|
|
10456
|
-
if (!
|
|
10580
|
+
const servicesDir = join38(options.cwd, "services");
|
|
10581
|
+
if (!existsSync36(servicesDir)) {
|
|
10457
10582
|
throw new Error(
|
|
10458
10583
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
10459
10584
|
);
|
|
10460
10585
|
}
|
|
10461
|
-
const targetDir =
|
|
10462
|
-
if (!
|
|
10463
|
-
const firstParty =
|
|
10464
|
-
if (
|
|
10586
|
+
const targetDir = join38(servicesDir, name);
|
|
10587
|
+
if (!existsSync36(targetDir)) {
|
|
10588
|
+
const firstParty = join38(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
|
|
10589
|
+
if (existsSync36(firstParty)) {
|
|
10465
10590
|
throw new Error(
|
|
10466
10591
|
`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.`
|
|
10467
10592
|
);
|
|
10468
10593
|
}
|
|
10469
10594
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
10470
10595
|
}
|
|
10471
|
-
const
|
|
10472
|
-
const
|
|
10596
|
+
const installedManifest = readInstalledManifest(targetDir);
|
|
10597
|
+
const version = installedManifest?.version;
|
|
10598
|
+
const modulesDir = join38(options.cwd, "modules", "plugins", name);
|
|
10473
10599
|
const stagePaths = [`services/${name}`];
|
|
10474
|
-
if (
|
|
10600
|
+
if (existsSync36(modulesDir)) {
|
|
10475
10601
|
stagePaths.push(`modules/plugins/${name}`);
|
|
10476
10602
|
}
|
|
10603
|
+
const hasUserFrontend = installedManifest?.user_frontend !== void 0;
|
|
10604
|
+
if (hasUserFrontend) {
|
|
10605
|
+
stagePaths.push(PLUGIN_REGISTRY_RELATIVE_PATH);
|
|
10606
|
+
}
|
|
10477
10607
|
if (options.dryRun) {
|
|
10478
10608
|
printDryRun5(name, version, stagePaths, options.keepData);
|
|
10479
10609
|
return;
|
|
@@ -10491,7 +10621,10 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
10491
10621
|
`${options.cwd} is not a git repository \u2014 biffo plugin uninstall must be run from a Biffo project checkout.`
|
|
10492
10622
|
);
|
|
10493
10623
|
}
|
|
10494
|
-
if (
|
|
10624
|
+
if (hasUserFrontend) {
|
|
10625
|
+
assertPluginRegistryReady(options.cwd, name);
|
|
10626
|
+
}
|
|
10627
|
+
if (existsSync36(modulesDir)) {
|
|
10495
10628
|
const refs = findPluginModuleReferences(options.cwd, name).filter(
|
|
10496
10629
|
(r) => !r.file.endsWith(`/${GENERATED_TF_FILE}`) && r.file !== GENERATED_TF_FILE
|
|
10497
10630
|
);
|
|
@@ -10506,7 +10639,7 @@ Remove the reference(s) above first, then re-run uninstall.`
|
|
|
10506
10639
|
}
|
|
10507
10640
|
rmSync9(targetDir, { recursive: true, force: true });
|
|
10508
10641
|
log.success(`Removed services/${name}/`);
|
|
10509
|
-
if (
|
|
10642
|
+
if (existsSync36(modulesDir)) {
|
|
10510
10643
|
rmSync9(modulesDir, { recursive: true, force: true });
|
|
10511
10644
|
log.success(`Removed modules/plugins/${name}/`);
|
|
10512
10645
|
const wiring = syncPluginTerraform(options.cwd);
|
|
@@ -10517,6 +10650,10 @@ Remove the reference(s) above first, then re-run uninstall.`
|
|
|
10517
10650
|
);
|
|
10518
10651
|
}
|
|
10519
10652
|
}
|
|
10653
|
+
if (hasUserFrontend) {
|
|
10654
|
+
removePluginRegistryEntry(options.cwd, name);
|
|
10655
|
+
log.success(`Removed ${name} from ${PLUGIN_REGISTRY_RELATIVE_PATH}`);
|
|
10656
|
+
}
|
|
10520
10657
|
const label = version ? `${name}@${version}` : name;
|
|
10521
10658
|
const commitMessage = `chore(plugins): uninstall ${label}`;
|
|
10522
10659
|
await deps.git.add(options.cwd, stagePaths);
|
|
@@ -10541,17 +10678,17 @@ Remove the reference(s) above first, then re-run uninstall.`
|
|
|
10541
10678
|
"Any tables this plugin created remain in the database, and its migration file at services/api/migrations/versions/ is NOT removed (it is a permanent historical record \u2014 see notes). Dropping tables, if desired, requires a manual Alembic migration written against the Core API."
|
|
10542
10679
|
);
|
|
10543
10680
|
}
|
|
10544
|
-
if (
|
|
10681
|
+
if (existsSync36(join38(options.cwd, pluginSeedImportDir(name)))) {
|
|
10545
10682
|
log.warn(
|
|
10546
10683
|
`${pluginSeedImportDir(name)}/ (this plugin's vendored baseline-row seed, biffo-template#1554) was NOT removed either, for the same reason \u2014 see notes. Delete it by hand if you are certain the rows it applied should go too, but note nothing drops rows already applied to the database; that still needs a manual migration.`
|
|
10547
10684
|
);
|
|
10548
10685
|
}
|
|
10549
10686
|
}
|
|
10550
|
-
function
|
|
10551
|
-
const manifestPath =
|
|
10552
|
-
if (!
|
|
10687
|
+
function readInstalledManifest(targetDir) {
|
|
10688
|
+
const manifestPath = join38(targetDir, "biffo.plugin.json");
|
|
10689
|
+
if (!existsSync36(manifestPath)) return void 0;
|
|
10553
10690
|
try {
|
|
10554
|
-
return validateManifest(JSON.parse(
|
|
10691
|
+
return validateManifest(JSON.parse(readFileSync27(manifestPath, "utf8")));
|
|
10555
10692
|
} catch {
|
|
10556
10693
|
return void 0;
|
|
10557
10694
|
}
|
|
@@ -10584,8 +10721,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
|
|
|
10584
10721
|
}
|
|
10585
10722
|
|
|
10586
10723
|
// src/commands/plugin-upgrade.ts
|
|
10587
|
-
import { cpSync as cpSync6, existsSync as
|
|
10588
|
-
import { join as
|
|
10724
|
+
import { cpSync as cpSync6, existsSync as existsSync37, mkdirSync as mkdirSync12, readFileSync as readFileSync28, rmSync as rmSync10 } from "fs";
|
|
10725
|
+
import { join as join39, relative as relative7, resolve as resolve17 } from "path";
|
|
10589
10726
|
import chalk19 from "chalk";
|
|
10590
10727
|
import { Command as Command20 } from "commander";
|
|
10591
10728
|
import inquirer7 from "inquirer";
|
|
@@ -10632,8 +10769,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10632
10769
|
`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\`).`
|
|
10633
10770
|
);
|
|
10634
10771
|
}
|
|
10635
|
-
const servicesDir =
|
|
10636
|
-
if (!
|
|
10772
|
+
const servicesDir = join39(options.cwd, "services");
|
|
10773
|
+
if (!existsSync37(servicesDir)) {
|
|
10637
10774
|
throw new Error(
|
|
10638
10775
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
10639
10776
|
);
|
|
@@ -10642,13 +10779,13 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10642
10779
|
return runLocalPluginRefresh(options.local, options, deps);
|
|
10643
10780
|
}
|
|
10644
10781
|
const { name, minor } = parsePluginTarget(target);
|
|
10645
|
-
const targetDir =
|
|
10646
|
-
if (!
|
|
10782
|
+
const targetDir = join39(servicesDir, name);
|
|
10783
|
+
if (!existsSync37(targetDir)) {
|
|
10647
10784
|
throw new Error(
|
|
10648
10785
|
`Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
|
|
10649
10786
|
);
|
|
10650
10787
|
}
|
|
10651
|
-
const currentVersion =
|
|
10788
|
+
const currentVersion = readInstalledVersion(targetDir);
|
|
10652
10789
|
log.info(`Resolving ${name}@${minor} from the plugin registry...`);
|
|
10653
10790
|
const entry = await deps.registry.resolvePlugin(name, minor);
|
|
10654
10791
|
log.success(`Resolved ${entry.name}@${entry.version} \u2014 ${entry.repo}`);
|
|
@@ -10657,7 +10794,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10657
10794
|
`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.`
|
|
10658
10795
|
);
|
|
10659
10796
|
}
|
|
10660
|
-
const modulesDir =
|
|
10797
|
+
const modulesDir = join39(options.cwd, "modules", "plugins", entry.name);
|
|
10661
10798
|
if (options.dryRun) {
|
|
10662
10799
|
printDryRun6(entry, currentVersion);
|
|
10663
10800
|
return;
|
|
@@ -10685,11 +10822,11 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10685
10822
|
log.success(
|
|
10686
10823
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
10687
10824
|
);
|
|
10688
|
-
const retiredShapeReasons = findRetiredFrontendShape(
|
|
10825
|
+
const retiredShapeReasons = findRetiredFrontendShape(join39(tmpDir, "terraform"));
|
|
10689
10826
|
if (retiredShapeReasons.length > 0) {
|
|
10690
10827
|
throw new Error(retiredFrontendShapeError(entry.name, retiredShapeReasons));
|
|
10691
10828
|
}
|
|
10692
|
-
if (!
|
|
10829
|
+
if (!existsSync37(join39(tmpDir, "terraform"))) {
|
|
10693
10830
|
refuseIfModuleStillReferenced(options.cwd, modulesDir, entry.name);
|
|
10694
10831
|
}
|
|
10695
10832
|
const previousProvenance = readProvenance(targetDir);
|
|
@@ -10706,11 +10843,11 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10706
10843
|
applyWorkspaceSources(targetDir, options.cwd, `services/${entry.name}`);
|
|
10707
10844
|
const newPyproject = readPyprojectIfPresent(targetDir);
|
|
10708
10845
|
const stagePaths = [`services/${entry.name}`];
|
|
10709
|
-
if (
|
|
10846
|
+
if (existsSync37(modulesDir)) {
|
|
10710
10847
|
rmSync10(modulesDir, { recursive: true, force: true });
|
|
10711
10848
|
}
|
|
10712
|
-
const tfSourceDir =
|
|
10713
|
-
if (
|
|
10849
|
+
const tfSourceDir = join39(targetDir, "terraform");
|
|
10850
|
+
if (existsSync37(tfSourceDir)) {
|
|
10714
10851
|
mkdirSync12(modulesDir, { recursive: true });
|
|
10715
10852
|
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
10716
10853
|
stagePaths.push(`modules/plugins/${entry.name}`);
|
|
@@ -10771,16 +10908,16 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10771
10908
|
async function runLocalPluginRefresh(localPath, options, deps) {
|
|
10772
10909
|
const source = resolveLocalPlugin(localPath);
|
|
10773
10910
|
log.success(`Resolved ${source.name}@${source.version} from ${source.origin}`);
|
|
10774
|
-
const servicesDir =
|
|
10775
|
-
const targetDir =
|
|
10776
|
-
if (!
|
|
10911
|
+
const servicesDir = join39(options.cwd, "services");
|
|
10912
|
+
const targetDir = join39(servicesDir, source.name);
|
|
10913
|
+
if (!existsSync37(targetDir)) {
|
|
10777
10914
|
throw new Error(
|
|
10778
10915
|
`Plugin '${source.name}' is not installed at services/${source.name}/. Use 'biffo plugin install --local ${localPath}' instead.`
|
|
10779
10916
|
);
|
|
10780
10917
|
}
|
|
10781
10918
|
const inTreeSource = resolve17(source.sourceDir) === resolve17(targetDir);
|
|
10782
|
-
const currentVersion =
|
|
10783
|
-
const modulesDir =
|
|
10919
|
+
const currentVersion = readInstalledVersion(targetDir);
|
|
10920
|
+
const modulesDir = join39(options.cwd, "modules", "plugins", source.name);
|
|
10784
10921
|
if (options.dryRun) {
|
|
10785
10922
|
printLocalDryRun(source, currentVersion, inTreeSource);
|
|
10786
10923
|
return;
|
|
@@ -10803,11 +10940,11 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
10803
10940
|
log.success(
|
|
10804
10941
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
10805
10942
|
);
|
|
10806
|
-
const retiredShapeReasons = findRetiredFrontendShape(
|
|
10943
|
+
const retiredShapeReasons = findRetiredFrontendShape(join39(source.sourceDir, "terraform"));
|
|
10807
10944
|
if (retiredShapeReasons.length > 0) {
|
|
10808
10945
|
throw new Error(retiredFrontendShapeError(source.name, retiredShapeReasons));
|
|
10809
10946
|
}
|
|
10810
|
-
if (!
|
|
10947
|
+
if (!existsSync37(join39(source.sourceDir, "terraform"))) {
|
|
10811
10948
|
refuseIfModuleStillReferenced(options.cwd, modulesDir, source.name);
|
|
10812
10949
|
}
|
|
10813
10950
|
const previousProvenance = readProvenance(targetDir);
|
|
@@ -10827,11 +10964,11 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
10827
10964
|
applyWorkspaceSources(targetDir, options.cwd, `services/${source.name}`);
|
|
10828
10965
|
const newPyproject = readPyprojectIfPresent(targetDir);
|
|
10829
10966
|
const stagePaths = [`services/${source.name}`];
|
|
10830
|
-
if (
|
|
10967
|
+
if (existsSync37(modulesDir)) {
|
|
10831
10968
|
rmSync10(modulesDir, { recursive: true, force: true });
|
|
10832
10969
|
}
|
|
10833
|
-
const tfSourceDir =
|
|
10834
|
-
if (
|
|
10970
|
+
const tfSourceDir = join39(targetDir, "terraform");
|
|
10971
|
+
if (existsSync37(tfSourceDir)) {
|
|
10835
10972
|
mkdirSync12(modulesDir, { recursive: true });
|
|
10836
10973
|
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
10837
10974
|
stagePaths.push(`modules/plugins/${source.name}`);
|
|
@@ -10895,7 +11032,7 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
10895
11032
|
}
|
|
10896
11033
|
}
|
|
10897
11034
|
function refuseIfModuleStillReferenced(cwd, modulesDir, name) {
|
|
10898
|
-
if (!
|
|
11035
|
+
if (!existsSync37(modulesDir)) return;
|
|
10899
11036
|
const refs = findPluginModuleReferences(cwd, name);
|
|
10900
11037
|
if (refs.length === 0) return;
|
|
10901
11038
|
const refList = refs.map((r) => ` ${r.file}:${r.line} ${r.text}`).join("\n");
|
|
@@ -10956,8 +11093,8 @@ var defaultRunCommand2 = async (command, cwd) => {
|
|
|
10956
11093
|
}
|
|
10957
11094
|
};
|
|
10958
11095
|
function readPyprojectIfPresent(targetDir) {
|
|
10959
|
-
const path =
|
|
10960
|
-
return
|
|
11096
|
+
const path = join39(targetDir, "pyproject.toml");
|
|
11097
|
+
return existsSync37(path) ? readFileSync28(path, "utf8") : null;
|
|
10961
11098
|
}
|
|
10962
11099
|
function dependenciesChanged(before, after) {
|
|
10963
11100
|
if (before === after) return false;
|
|
@@ -10984,11 +11121,11 @@ function tomlTableBody(text, header) {
|
|
|
10984
11121
|
const nextHeader = /^\[/m.exec(rest);
|
|
10985
11122
|
return nextHeader ? rest.slice(0, nextHeader.index) : rest;
|
|
10986
11123
|
}
|
|
10987
|
-
function
|
|
10988
|
-
const manifestPath =
|
|
10989
|
-
if (!
|
|
11124
|
+
function readInstalledVersion(targetDir) {
|
|
11125
|
+
const manifestPath = join39(targetDir, "biffo.plugin.json");
|
|
11126
|
+
if (!existsSync37(manifestPath)) return void 0;
|
|
10990
11127
|
try {
|
|
10991
|
-
return validateManifest(JSON.parse(
|
|
11128
|
+
return validateManifest(JSON.parse(readFileSync28(manifestPath, "utf8"))).version;
|
|
10992
11129
|
} catch {
|
|
10993
11130
|
return void 0;
|
|
10994
11131
|
}
|
|
@@ -11071,7 +11208,7 @@ pluginCommand.addCommand(pluginStalenessCommand);
|
|
|
11071
11208
|
import { Command as Command23 } from "commander";
|
|
11072
11209
|
|
|
11073
11210
|
// src/commands/sibling-check-identity.ts
|
|
11074
|
-
import { existsSync as
|
|
11211
|
+
import { existsSync as existsSync38, readFileSync as readFileSync29 } from "fs";
|
|
11075
11212
|
import { resolve as resolve18 } from "path";
|
|
11076
11213
|
import chalk20 from "chalk";
|
|
11077
11214
|
import { Command as Command22 } from "commander";
|
|
@@ -11265,7 +11402,7 @@ async function fetchPublishedIdentity(portalUrl) {
|
|
|
11265
11402
|
}
|
|
11266
11403
|
async function resolveConfig4(options) {
|
|
11267
11404
|
if (options.config) {
|
|
11268
|
-
const raw = JSON.parse(
|
|
11405
|
+
const raw = JSON.parse(readFileSync29(resolve18(options.config), "utf8"));
|
|
11269
11406
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
11270
11407
|
if (!result.success) {
|
|
11271
11408
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -11285,8 +11422,8 @@ async function resolveConfig4(options) {
|
|
|
11285
11422
|
return cfg;
|
|
11286
11423
|
}
|
|
11287
11424
|
const localConfigPath = resolve18(process.cwd(), "biffo.config.json");
|
|
11288
|
-
if (
|
|
11289
|
-
const raw = JSON.parse(
|
|
11425
|
+
if (existsSync38(localConfigPath)) {
|
|
11426
|
+
const raw = JSON.parse(readFileSync29(localConfigPath, "utf8"));
|
|
11290
11427
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
11291
11428
|
if (result.success) return result.data;
|
|
11292
11429
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -11332,20 +11469,20 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
|
11332
11469
|
import { Command as Command24 } from "commander";
|
|
11333
11470
|
|
|
11334
11471
|
// src/scripts/check-adr-numbering.ts
|
|
11335
|
-
import { existsSync as
|
|
11336
|
-
import { join as
|
|
11472
|
+
import { existsSync as existsSync40 } from "fs";
|
|
11473
|
+
import { join as join41 } from "path";
|
|
11337
11474
|
|
|
11338
11475
|
// src/lib/adr-numbering-guard.ts
|
|
11339
|
-
import { existsSync as
|
|
11340
|
-
import { join as
|
|
11476
|
+
import { existsSync as existsSync39, readdirSync as readdirSync15, readFileSync as readFileSync30 } from "fs";
|
|
11477
|
+
import { join as join40 } from "path";
|
|
11341
11478
|
var ADR_FILENAME = /^(\d{4})-.+\.md$/;
|
|
11342
11479
|
var ALLOWLIST_FILENAME = ".numbering-allowlist";
|
|
11343
11480
|
var TEMPLATE_ADR_RESERVED_UPTO = "0099";
|
|
11344
11481
|
function readAdrNumberingAllowlist(adrDir) {
|
|
11345
|
-
const path =
|
|
11346
|
-
if (!
|
|
11482
|
+
const path = join40(adrDir, ALLOWLIST_FILENAME);
|
|
11483
|
+
if (!existsSync39(path)) return /* @__PURE__ */ new Set();
|
|
11347
11484
|
const numbers = /* @__PURE__ */ new Set();
|
|
11348
|
-
for (const rawLine of
|
|
11485
|
+
for (const rawLine of readFileSync30(path, "utf8").split("\n")) {
|
|
11349
11486
|
const line = rawLine.split("#")[0].trim();
|
|
11350
11487
|
if (line) numbers.add(line);
|
|
11351
11488
|
}
|
|
@@ -11353,7 +11490,7 @@ function readAdrNumberingAllowlist(adrDir) {
|
|
|
11353
11490
|
}
|
|
11354
11491
|
function adrNumbersIn(adrDir) {
|
|
11355
11492
|
const claims = /* @__PURE__ */ new Map();
|
|
11356
|
-
if (!
|
|
11493
|
+
if (!existsSync39(adrDir)) return claims;
|
|
11357
11494
|
for (const entry of readdirSync15(adrDir).sort()) {
|
|
11358
11495
|
const match = ADR_FILENAME.exec(entry);
|
|
11359
11496
|
if (!match) continue;
|
|
@@ -11408,8 +11545,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
|
|
|
11408
11545
|
// src/scripts/check-adr-numbering.ts
|
|
11409
11546
|
async function runAdrNumberingCheck() {
|
|
11410
11547
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11411
|
-
const adrDir =
|
|
11412
|
-
if (!
|
|
11548
|
+
const adrDir = join41(root, "docs", "ADR");
|
|
11549
|
+
if (!existsSync40(adrDir)) {
|
|
11413
11550
|
console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
|
|
11414
11551
|
return;
|
|
11415
11552
|
}
|
|
@@ -11447,8 +11584,8 @@ Already accepted? List it in docs/ADR/${ALLOWLIST_FILENAME} instead of leaving t
|
|
|
11447
11584
|
}
|
|
11448
11585
|
|
|
11449
11586
|
// src/lib/api-gateway-integration-guard.ts
|
|
11450
|
-
import { readFileSync as
|
|
11451
|
-
import { join as
|
|
11587
|
+
import { readFileSync as readFileSync31, readdirSync as readdirSync16, statSync as statSync9 } from "fs";
|
|
11588
|
+
import { join as join42 } from "path";
|
|
11452
11589
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
11453
11590
|
var MODULE_TYPE = "module";
|
|
11454
11591
|
var INTEGRATION_TYPE = "aws_apigatewayv2_integration";
|
|
@@ -11530,7 +11667,7 @@ function walkTerraformFiles(root) {
|
|
|
11530
11667
|
return;
|
|
11531
11668
|
}
|
|
11532
11669
|
for (const entry of entries) {
|
|
11533
|
-
const p =
|
|
11670
|
+
const p = join42(dir, entry);
|
|
11534
11671
|
let st;
|
|
11535
11672
|
try {
|
|
11536
11673
|
st = statSync9(p);
|
|
@@ -11574,7 +11711,7 @@ function auditApiGatewayIntegrations(root) {
|
|
|
11574
11711
|
let rawModuleCount = 0;
|
|
11575
11712
|
let rawIntegrationCount = 0;
|
|
11576
11713
|
for (const file of files) {
|
|
11577
|
-
const text =
|
|
11714
|
+
const text = readFileSync31(file, "utf8");
|
|
11578
11715
|
rawModuleCount += countRawResourceDeclarations(text, MODULE_TYPE);
|
|
11579
11716
|
rawIntegrationCount += countRawResourceDeclarations(text, INTEGRATION_TYPE);
|
|
11580
11717
|
moduleBlocks.push(...findModuleBlocks(text, file));
|
|
@@ -11946,18 +12083,18 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
|
|
|
11946
12083
|
}
|
|
11947
12084
|
|
|
11948
12085
|
// src/lib/claim-invocation-parity.ts
|
|
11949
|
-
import { existsSync as
|
|
11950
|
-
import { join as
|
|
12086
|
+
import { existsSync as existsSync41, readFileSync as readFileSync32, readdirSync as readdirSync17 } from "fs";
|
|
12087
|
+
import { join as join43 } from "path";
|
|
11951
12088
|
function distributedAgentsDocs(root) {
|
|
11952
12089
|
const docs = [];
|
|
11953
|
-
const own =
|
|
11954
|
-
if (
|
|
11955
|
-
const skeletons =
|
|
11956
|
-
if (
|
|
12090
|
+
const own = join43(root, "AGENTS.md");
|
|
12091
|
+
if (existsSync41(own)) docs.push({ path: "AGENTS.md", text: readFileSync32(own, "utf8") });
|
|
12092
|
+
const skeletons = join43(root, "_skeletons");
|
|
12093
|
+
if (existsSync41(skeletons)) {
|
|
11957
12094
|
for (const name of readdirSync17(skeletons).sort()) {
|
|
11958
|
-
const abs =
|
|
11959
|
-
if (!
|
|
11960
|
-
docs.push({ path: `_skeletons/${name}/AGENTS.md`, text:
|
|
12095
|
+
const abs = join43(skeletons, name, "AGENTS.md");
|
|
12096
|
+
if (!existsSync41(abs)) continue;
|
|
12097
|
+
docs.push({ path: `_skeletons/${name}/AGENTS.md`, text: readFileSync32(abs, "utf8") });
|
|
11961
12098
|
}
|
|
11962
12099
|
}
|
|
11963
12100
|
return docs;
|
|
@@ -12089,12 +12226,12 @@ async function runClaimInvocationCheck() {
|
|
|
12089
12226
|
}
|
|
12090
12227
|
|
|
12091
12228
|
// src/scripts/check-codeql-suppression.ts
|
|
12092
|
-
import { existsSync as
|
|
12093
|
-
import { join as
|
|
12229
|
+
import { existsSync as existsSync42 } from "fs";
|
|
12230
|
+
import { join as join45, relative as relative8 } from "path";
|
|
12094
12231
|
|
|
12095
12232
|
// src/lib/codeql-suppression-guard.ts
|
|
12096
|
-
import { readdirSync as readdirSync18, readFileSync as
|
|
12097
|
-
import { join as
|
|
12233
|
+
import { readdirSync as readdirSync18, readFileSync as readFileSync33, statSync as statSync10 } from "fs";
|
|
12234
|
+
import { join as join44 } from "path";
|
|
12098
12235
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
12099
12236
|
".git",
|
|
12100
12237
|
".worktrees",
|
|
@@ -12125,7 +12262,7 @@ function walkSourceFiles(root) {
|
|
|
12125
12262
|
return;
|
|
12126
12263
|
}
|
|
12127
12264
|
for (const entry of entries) {
|
|
12128
|
-
const p =
|
|
12265
|
+
const p = join44(dir, entry);
|
|
12129
12266
|
let st;
|
|
12130
12267
|
try {
|
|
12131
12268
|
st = statSync10(p);
|
|
@@ -12151,7 +12288,7 @@ function countSourceFiles(root) {
|
|
|
12151
12288
|
function sweepCodeqlSuppressionComments(root) {
|
|
12152
12289
|
const hits = [];
|
|
12153
12290
|
for (const path of walkSourceFiles(root)) {
|
|
12154
|
-
const text =
|
|
12291
|
+
const text = readFileSync33(path, "utf8");
|
|
12155
12292
|
for (const line of findCodeqlSuppressionComments(text)) {
|
|
12156
12293
|
hits.push({ path, line, text: text.split("\n")[line - 1] ?? "" });
|
|
12157
12294
|
}
|
|
@@ -12162,8 +12299,8 @@ function sweepCodeqlSuppressionComments(root) {
|
|
|
12162
12299
|
// src/scripts/check-codeql-suppression.ts
|
|
12163
12300
|
async function runCodeqlSuppressionCheck() {
|
|
12164
12301
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12165
|
-
const scanRoot =
|
|
12166
|
-
if (!
|
|
12302
|
+
const scanRoot = join45(root, "cli", "src");
|
|
12303
|
+
if (!existsSync42(scanRoot)) {
|
|
12167
12304
|
console.log(
|
|
12168
12305
|
"\u2014 codeql-suppression guard: skipped \u2014 no cli/src in this repo, so there is no CLI source to scan."
|
|
12169
12306
|
);
|
|
@@ -12187,12 +12324,12 @@ async function runCodeqlSuppressionCheck() {
|
|
|
12187
12324
|
}
|
|
12188
12325
|
|
|
12189
12326
|
// src/scripts/check-cognito-invite-template.ts
|
|
12190
|
-
import { existsSync as
|
|
12191
|
-
import { join as
|
|
12327
|
+
import { existsSync as existsSync43 } from "fs";
|
|
12328
|
+
import { join as join47 } from "path";
|
|
12192
12329
|
|
|
12193
12330
|
// src/lib/cognito-invite-template-guard.ts
|
|
12194
|
-
import { readdirSync as readdirSync19, readFileSync as
|
|
12195
|
-
import { join as
|
|
12331
|
+
import { readdirSync as readdirSync19, readFileSync as readFileSync34, statSync as statSync11 } from "fs";
|
|
12332
|
+
import { join as join46 } from "path";
|
|
12196
12333
|
var REQUIRED_INVITE_MEMBERS = ["email_subject", "email_message", "sms_message"];
|
|
12197
12334
|
var REQUIRED_INVITE_PLACEHOLDERS = ["{username}", "{####}"];
|
|
12198
12335
|
var PLACEHOLDER_MEMBERS = ["email_message", "sms_message"];
|
|
@@ -12276,7 +12413,7 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
12276
12413
|
for (const entry of entries) {
|
|
12277
12414
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees" || entry === ".venv")
|
|
12278
12415
|
continue;
|
|
12279
|
-
const full =
|
|
12416
|
+
const full = join46(dir, entry);
|
|
12280
12417
|
const rel = `${relative11}/${entry}`;
|
|
12281
12418
|
let isDir;
|
|
12282
12419
|
try {
|
|
@@ -12291,12 +12428,12 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
12291
12428
|
}
|
|
12292
12429
|
}
|
|
12293
12430
|
};
|
|
12294
|
-
walk2(
|
|
12431
|
+
walk2(join46(repoRoot, "modules"), "modules");
|
|
12295
12432
|
return found.sort();
|
|
12296
12433
|
}
|
|
12297
12434
|
function checkCognitoInviteTemplates(repoRoot) {
|
|
12298
12435
|
return findModuleTerraformFiles(repoRoot).flatMap(
|
|
12299
|
-
(file) => checkInviteTemplateSource(file,
|
|
12436
|
+
(file) => checkInviteTemplateSource(file, readFileSync34(join46(repoRoot, file), "utf8"))
|
|
12300
12437
|
);
|
|
12301
12438
|
}
|
|
12302
12439
|
|
|
@@ -12306,7 +12443,7 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
12306
12443
|
const files = findModuleTerraformFiles(root);
|
|
12307
12444
|
console.log(`audited ${files.length} .tf file(s) under modules/ under ${root}`);
|
|
12308
12445
|
if (files.length === 0) {
|
|
12309
|
-
if (!
|
|
12446
|
+
if (!existsSync43(join47(root, "modules"))) {
|
|
12310
12447
|
console.log(
|
|
12311
12448
|
`\xB7 Cognito invite template guard: not applicable \u2014 no modules/ directory under ${root} \u2014 this is not a template/instance tree (a satellite repo never carries the template-owned Terraform modules). Skipping.`
|
|
12312
12449
|
);
|
|
@@ -12330,11 +12467,11 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
12330
12467
|
}
|
|
12331
12468
|
|
|
12332
12469
|
// src/scripts/check-core-direct-paths.ts
|
|
12333
|
-
import { join as
|
|
12470
|
+
import { join as join49 } from "path";
|
|
12334
12471
|
|
|
12335
12472
|
// src/lib/core-direct-paths-audit.ts
|
|
12336
|
-
import { existsSync as
|
|
12337
|
-
import { join as
|
|
12473
|
+
import { existsSync as existsSync44, readFileSync as readFileSync35, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
|
|
12474
|
+
import { join as join48 } from "path";
|
|
12338
12475
|
var EXTERNAL_BASE_IDENTIFIERS = ["CORE_API_URL"];
|
|
12339
12476
|
var API_ROUTE_PREFIX = "/api/v1";
|
|
12340
12477
|
var TEST_FILE_SUFFIXES = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
|
|
@@ -12498,7 +12635,7 @@ function walkFiles(root, accept, skipDir) {
|
|
|
12498
12635
|
return;
|
|
12499
12636
|
}
|
|
12500
12637
|
for (const entry of entries) {
|
|
12501
|
-
const p =
|
|
12638
|
+
const p = join48(dir, entry);
|
|
12502
12639
|
let st;
|
|
12503
12640
|
try {
|
|
12504
12641
|
st = statSync12(p);
|
|
@@ -12528,7 +12665,7 @@ function auditFrontendExtraction(frontendSrcDir, externalBases = EXTERNAL_BASE_I
|
|
|
12528
12665
|
const extracted = [];
|
|
12529
12666
|
let rawTotal = 0;
|
|
12530
12667
|
for (const file of files) {
|
|
12531
|
-
const text =
|
|
12668
|
+
const text = readFileSync35(file, "utf8");
|
|
12532
12669
|
rawTotal += countRawExternalOccurrences(text, externalBases);
|
|
12533
12670
|
extracted.push(...extractCoreDirectPaths(text, file, externalBases));
|
|
12534
12671
|
}
|
|
@@ -12577,7 +12714,7 @@ function auditCoreRouteExtraction(apiSrcDir) {
|
|
|
12577
12714
|
const prefixSet = /* @__PURE__ */ new Set();
|
|
12578
12715
|
let rawApiRouterCount = 0;
|
|
12579
12716
|
for (const file of files) {
|
|
12580
|
-
const text =
|
|
12717
|
+
const text = readFileSync35(file, "utf8");
|
|
12581
12718
|
const extraction = extractCoreRoutePrefixes(text);
|
|
12582
12719
|
rawApiRouterCount += extraction.rawApiRouterCount;
|
|
12583
12720
|
for (const p of extraction.prefixes) prefixSet.add(normalizePrefix(p));
|
|
@@ -12593,10 +12730,10 @@ function pathMatchesAnyCorePrefix(normalized, corePrefixes, apiRoutePrefix = API
|
|
|
12593
12730
|
}
|
|
12594
12731
|
function resolveSiblingCoreSrc(params) {
|
|
12595
12732
|
const { estateDir, sibling } = params;
|
|
12596
|
-
const configPath =
|
|
12733
|
+
const configPath = join48(estateDir, sibling, "biffo.sibling.json");
|
|
12597
12734
|
let raw;
|
|
12598
12735
|
try {
|
|
12599
|
-
raw =
|
|
12736
|
+
raw = readFileSync35(configPath, "utf8");
|
|
12600
12737
|
} catch (err) {
|
|
12601
12738
|
throw new Error(
|
|
12602
12739
|
`cannot resolve ${sibling}'s core: ${configPath} does not exist or is unreadable (${err.message}) -- refusing to guess which core serves this sibling.`
|
|
@@ -12616,8 +12753,8 @@ function resolveSiblingCoreSrc(params) {
|
|
|
12616
12753
|
`cannot resolve ${sibling}'s core: ${configPath} has no non-empty "core_project" field.`
|
|
12617
12754
|
);
|
|
12618
12755
|
}
|
|
12619
|
-
const coreApiSrcDir =
|
|
12620
|
-
if (!
|
|
12756
|
+
const coreApiSrcDir = join48(estateDir, coreProject, "services", "api", "src");
|
|
12757
|
+
if (!existsSync44(coreApiSrcDir)) {
|
|
12621
12758
|
throw new Error(
|
|
12622
12759
|
`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.`
|
|
12623
12760
|
);
|
|
@@ -12670,7 +12807,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
12670
12807
|
}
|
|
12671
12808
|
}
|
|
12672
12809
|
const sibling = opts.sibling ?? "sibling-template (self-check)";
|
|
12673
|
-
const frontendSrcDir = opts.frontendSrc ??
|
|
12810
|
+
const frontendSrcDir = opts.frontendSrc ?? join49(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
|
|
12674
12811
|
let coreApiSrcDir;
|
|
12675
12812
|
let coreProject = null;
|
|
12676
12813
|
if (opts.coreSrc) {
|
|
@@ -12686,7 +12823,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
12686
12823
|
coreApiSrcDir = resolution.coreApiSrcDir;
|
|
12687
12824
|
coreProject = resolution.coreProject;
|
|
12688
12825
|
} else {
|
|
12689
|
-
coreApiSrcDir =
|
|
12826
|
+
coreApiSrcDir = join49(root, "services", "api", "src");
|
|
12690
12827
|
}
|
|
12691
12828
|
const report = auditSiblingCoreDirectPaths({ sibling, frontendSrcDir, coreApiSrcDir });
|
|
12692
12829
|
console.log(
|
|
@@ -12813,8 +12950,8 @@ async function runOwnershipCheck(argv) {
|
|
|
12813
12950
|
const { stdout } = await execa("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
12814
12951
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
12815
12952
|
if (messageFile) {
|
|
12816
|
-
const { readFileSync:
|
|
12817
|
-
if (
|
|
12953
|
+
const { readFileSync: readFileSync49, existsSync: existsSync59 } = await import("fs");
|
|
12954
|
+
if (existsSync59(messageFile)) commitMessage = readFileSync49(messageFile, "utf8");
|
|
12818
12955
|
}
|
|
12819
12956
|
} else {
|
|
12820
12957
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -12937,21 +13074,21 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
12937
13074
|
}
|
|
12938
13075
|
|
|
12939
13076
|
// src/scripts/check-distribution-inventory.ts
|
|
12940
|
-
import { existsSync as
|
|
12941
|
-
import { join as
|
|
13077
|
+
import { existsSync as existsSync46 } from "fs";
|
|
13078
|
+
import { join as join51 } from "path";
|
|
12942
13079
|
|
|
12943
13080
|
// src/lib/distribution-inventory.ts
|
|
12944
|
-
import { existsSync as
|
|
12945
|
-
import { join as
|
|
13081
|
+
import { existsSync as existsSync45, readFileSync as readFileSync36 } from "fs";
|
|
13082
|
+
import { join as join50 } from "path";
|
|
12946
13083
|
var INVENTORY_FILENAME = "distribution-inventory.json";
|
|
12947
13084
|
function loadDistributionInventory(root) {
|
|
12948
|
-
const path =
|
|
12949
|
-
if (!
|
|
13085
|
+
const path = join50(root, INVENTORY_FILENAME);
|
|
13086
|
+
if (!existsSync45(path)) {
|
|
12950
13087
|
throw new Error(
|
|
12951
13088
|
`${INVENTORY_FILENAME} not found at ${root} -- expected it beside core-manifest.json`
|
|
12952
13089
|
);
|
|
12953
13090
|
}
|
|
12954
|
-
return JSON.parse(
|
|
13091
|
+
return JSON.parse(readFileSync36(path, "utf8"));
|
|
12955
13092
|
}
|
|
12956
13093
|
function validateInventory(inventory) {
|
|
12957
13094
|
const violations = [];
|
|
@@ -13111,7 +13248,7 @@ async function runDistributionInventoryCheck(root) {
|
|
|
13111
13248
|
);
|
|
13112
13249
|
return;
|
|
13113
13250
|
}
|
|
13114
|
-
if (!
|
|
13251
|
+
if (!existsSync46(join51(repoRoot, INVENTORY_FILENAME))) {
|
|
13115
13252
|
console.log(
|
|
13116
13253
|
`\u2713 distribution-inventory: skipped \u2014 no ${INVENTORY_FILENAME} at ${repoRoot}. This is a template-only registry; a checkout that legitimately has none has nothing for this guard to check.`
|
|
13117
13254
|
);
|
|
@@ -13137,7 +13274,7 @@ async function runDistributionInventoryCheck(root) {
|
|
|
13137
13274
|
}
|
|
13138
13275
|
|
|
13139
13276
|
// src/scripts/check-distribution-remote-state.ts
|
|
13140
|
-
import { join as
|
|
13277
|
+
import { join as join52 } from "path";
|
|
13141
13278
|
async function ghExecCommand(file, args) {
|
|
13142
13279
|
const result = await execa(file, args, { reject: false });
|
|
13143
13280
|
return { stdout: String(result.stdout ?? ""), exitCode: result.exitCode ?? null };
|
|
@@ -13152,7 +13289,7 @@ async function runDistributionRemoteStateCheck(root) {
|
|
|
13152
13289
|
}
|
|
13153
13290
|
}
|
|
13154
13291
|
console.log(
|
|
13155
|
-
`distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${
|
|
13292
|
+
`distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${join52(repoRoot, "distribution-inventory.json")})`
|
|
13156
13293
|
);
|
|
13157
13294
|
if (assertions.length === 0) {
|
|
13158
13295
|
console.log("\u2713 distribution-remote-state: nothing declared to check");
|
|
@@ -13197,8 +13334,8 @@ ${assertion.ref}`;
|
|
|
13197
13334
|
}
|
|
13198
13335
|
|
|
13199
13336
|
// src/lib/eventbridge-log-permission-guard.ts
|
|
13200
|
-
import { readFileSync as
|
|
13201
|
-
import { join as
|
|
13337
|
+
import { readFileSync as readFileSync37, readdirSync as readdirSync21, statSync as statSync13 } from "fs";
|
|
13338
|
+
import { join as join53 } from "path";
|
|
13202
13339
|
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
13203
13340
|
var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
|
|
13204
13341
|
var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
|
|
@@ -13275,7 +13412,7 @@ function walkTerraformFiles2(root) {
|
|
|
13275
13412
|
return;
|
|
13276
13413
|
}
|
|
13277
13414
|
for (const entry of entries) {
|
|
13278
|
-
const p =
|
|
13415
|
+
const p = join53(dir, entry);
|
|
13279
13416
|
let st;
|
|
13280
13417
|
try {
|
|
13281
13418
|
st = statSync13(p);
|
|
@@ -13318,7 +13455,7 @@ function auditEventBridgeLogPermissions(root) {
|
|
|
13318
13455
|
let rawEventTargetCount = 0;
|
|
13319
13456
|
let rawLogPolicyCount = 0;
|
|
13320
13457
|
for (const file of files) {
|
|
13321
|
-
const text =
|
|
13458
|
+
const text = readFileSync37(file, "utf8");
|
|
13322
13459
|
rawEventTargetCount += countRawResourceDeclarations2(text, EVENT_TARGET_TYPE);
|
|
13323
13460
|
rawLogPolicyCount += countRawResourceDeclarations2(text, LOG_RESOURCE_POLICY_TYPE);
|
|
13324
13461
|
eventTargetBlocks.push(...findResourceBlocks2(text, file, EVENT_TARGET_TYPE));
|
|
@@ -13415,8 +13552,8 @@ async function runEventBridgeLogPermissionCheck() {
|
|
|
13415
13552
|
}
|
|
13416
13553
|
|
|
13417
13554
|
// src/scripts/check-instance-adoption.ts
|
|
13418
|
-
import { existsSync as
|
|
13419
|
-
import { join as
|
|
13555
|
+
import { existsSync as existsSync47 } from "fs";
|
|
13556
|
+
import { join as join54 } from "path";
|
|
13420
13557
|
async function runInstanceAdoptionCheck(opts = {}) {
|
|
13421
13558
|
if (!opts.instanceDir) {
|
|
13422
13559
|
console.error(
|
|
@@ -13424,7 +13561,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
13424
13561
|
);
|
|
13425
13562
|
process.exit(2);
|
|
13426
13563
|
}
|
|
13427
|
-
if (!
|
|
13564
|
+
if (!existsSync47(opts.instanceDir)) {
|
|
13428
13565
|
console.error(
|
|
13429
13566
|
`\u2717 instance-adoption guard: --instance-dir ${opts.instanceDir} does not exist \u2014 cannot tell whether it is adopted, and that is not the same as a clean pass.`
|
|
13430
13567
|
);
|
|
@@ -13432,7 +13569,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
13432
13569
|
}
|
|
13433
13570
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13434
13571
|
const theirsDir = opts.theirsDir ?? root;
|
|
13435
|
-
const instanceLabel = opts.instance ??
|
|
13572
|
+
const instanceLabel = opts.instance ?? join54(opts.instanceDir).split("/").filter(Boolean).pop();
|
|
13436
13573
|
const report = checkInstanceAdoption(theirsDir, opts.instanceDir);
|
|
13437
13574
|
console.log(
|
|
13438
13575
|
`examined ${report.examinedInstances} instance (${instanceLabel}) against ${report.registeredPairs} registered pair(s), ${report.applicablePairs} applicable to this instance, against template tree ${theirsDir}`
|
|
@@ -13454,12 +13591,12 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
13454
13591
|
}
|
|
13455
13592
|
|
|
13456
13593
|
// src/lib/lambda-output-guard.ts
|
|
13457
|
-
import { readFileSync as
|
|
13458
|
-
import { join as
|
|
13594
|
+
import { readFileSync as readFileSync39 } from "fs";
|
|
13595
|
+
import { join as join56 } from "path";
|
|
13459
13596
|
|
|
13460
13597
|
// src/lib/terraform-input-guard.ts
|
|
13461
|
-
import { existsSync as
|
|
13462
|
-
import { join as
|
|
13598
|
+
import { existsSync as existsSync48, readdirSync as readdirSync22, readFileSync as readFileSync38, statSync as statSync14 } from "fs";
|
|
13599
|
+
import { join as join55 } from "path";
|
|
13463
13600
|
var GUARDED_SUBCOMMANDS = [
|
|
13464
13601
|
"init",
|
|
13465
13602
|
"plan",
|
|
@@ -13473,7 +13610,7 @@ function stripComments2(source) {
|
|
|
13473
13610
|
return source.split("\n").map((line) => line.replace(/(^|\s)#.*$/, "$1")).join("\n");
|
|
13474
13611
|
}
|
|
13475
13612
|
function vendoredPluginServiceDirs(repoRoot) {
|
|
13476
|
-
const servicesDir =
|
|
13613
|
+
const servicesDir = join55(repoRoot, "services");
|
|
13477
13614
|
const result = /* @__PURE__ */ new Set();
|
|
13478
13615
|
let entries;
|
|
13479
13616
|
try {
|
|
@@ -13482,9 +13619,9 @@ function vendoredPluginServiceDirs(repoRoot) {
|
|
|
13482
13619
|
return result;
|
|
13483
13620
|
}
|
|
13484
13621
|
for (const entry of entries) {
|
|
13485
|
-
const full =
|
|
13486
|
-
if (!
|
|
13487
|
-
if (
|
|
13622
|
+
const full = join55(servicesDir, entry);
|
|
13623
|
+
if (!existsSync48(full) || !statSync14(full).isDirectory()) continue;
|
|
13624
|
+
if (existsSync48(join55(full, "biffo.plugin.json"))) {
|
|
13488
13625
|
result.add(entry);
|
|
13489
13626
|
}
|
|
13490
13627
|
}
|
|
@@ -13506,7 +13643,7 @@ function findWorkflowFiles(repoRoot) {
|
|
|
13506
13643
|
if (entry === ".github" && relative11.startsWith("services/") && vendoredPluginDirs.has(relative11.slice("services/".length))) {
|
|
13507
13644
|
continue;
|
|
13508
13645
|
}
|
|
13509
|
-
const full =
|
|
13646
|
+
const full = join55(dir, entry);
|
|
13510
13647
|
const rel = relative11 ? `${relative11}/${entry}` : entry;
|
|
13511
13648
|
let isDir;
|
|
13512
13649
|
try {
|
|
@@ -13556,7 +13693,7 @@ function checkWorkflowSource(file, rawSource) {
|
|
|
13556
13693
|
}
|
|
13557
13694
|
function checkTerraformInput(repoRoot) {
|
|
13558
13695
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
13559
|
-
(file) => checkWorkflowSource(file,
|
|
13696
|
+
(file) => checkWorkflowSource(file, readFileSync38(join55(repoRoot, file), "utf8"))
|
|
13560
13697
|
);
|
|
13561
13698
|
}
|
|
13562
13699
|
|
|
@@ -13614,7 +13751,7 @@ function checkWorkflowSource2(file, rawSource) {
|
|
|
13614
13751
|
}
|
|
13615
13752
|
function checkLambdaOutput(repoRoot) {
|
|
13616
13753
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
13617
|
-
(file) => checkWorkflowSource2(file,
|
|
13754
|
+
(file) => checkWorkflowSource2(file, readFileSync39(join56(repoRoot, file), "utf8"))
|
|
13618
13755
|
);
|
|
13619
13756
|
}
|
|
13620
13757
|
|
|
@@ -13642,8 +13779,8 @@ async function runLambdaOutputCheck() {
|
|
|
13642
13779
|
}
|
|
13643
13780
|
|
|
13644
13781
|
// src/scripts/check-migration-body-change.ts
|
|
13645
|
-
import { existsSync as
|
|
13646
|
-
import { join as
|
|
13782
|
+
import { existsSync as existsSync49 } from "fs";
|
|
13783
|
+
import { join as join57 } from "path";
|
|
13647
13784
|
|
|
13648
13785
|
// src/lib/migration-body-change-guard.ts
|
|
13649
13786
|
function checkMigrationBodyChangeMarkers(diffs) {
|
|
@@ -13725,7 +13862,7 @@ async function runMigrationBodyChangeCheck(argv) {
|
|
|
13725
13862
|
);
|
|
13726
13863
|
return;
|
|
13727
13864
|
}
|
|
13728
|
-
if (!
|
|
13865
|
+
if (!existsSync49(join57(root, MIGRATIONS_VERSIONS_DIR))) {
|
|
13729
13866
|
console.log(`\u2713 migration body-change guard: no ${MIGRATIONS_VERSIONS_DIR} in this repo.`);
|
|
13730
13867
|
return;
|
|
13731
13868
|
}
|
|
@@ -13831,8 +13968,8 @@ ${BOLD2}What to do${OFF2}
|
|
|
13831
13968
|
}
|
|
13832
13969
|
|
|
13833
13970
|
// src/scripts/check-orphan-ratchet.ts
|
|
13834
|
-
import { existsSync as
|
|
13835
|
-
import { basename as basename3, join as
|
|
13971
|
+
import { existsSync as existsSync50 } from "fs";
|
|
13972
|
+
import { basename as basename3, join as join58 } from "path";
|
|
13836
13973
|
function reportOrphan(entry, manifest) {
|
|
13837
13974
|
console.error(` ${entry.path}`);
|
|
13838
13975
|
const { templateOwnedMatch, nearestUserOwnedEntries } = explainOwnership(entry.path, manifest);
|
|
@@ -13854,7 +13991,7 @@ function reportOrphan(entry, manifest) {
|
|
|
13854
13991
|
}
|
|
13855
13992
|
}
|
|
13856
13993
|
async function runOrphanRatchetCheck(opts = {}) {
|
|
13857
|
-
if (opts.instanceDir !== void 0 && !
|
|
13994
|
+
if (opts.instanceDir !== void 0 && !existsSync50(opts.instanceDir)) {
|
|
13858
13995
|
console.error(
|
|
13859
13996
|
`\u2717 orphan-ratchet guard: --instance-dir ${opts.instanceDir} does not exist \u2014 cannot tell whether it carries unsanctioned files, and that is not the same as a clean pass.`
|
|
13860
13997
|
);
|
|
@@ -13871,7 +14008,7 @@ async function runOrphanRatchetCheck(opts = {}) {
|
|
|
13871
14008
|
"self-check mode: --instance-dir was not given, so it defaulted to --theirs-dir (this repo's own root) alongside --base-dir \u2014 all three trees are the same, so this run can only ever find zero orphans by construction. See this script's doc comment. Pass a real instance tree with --instance-dir for a check that can actually find something. biffo-template#1714."
|
|
13872
14009
|
);
|
|
13873
14010
|
}
|
|
13874
|
-
if (!
|
|
14011
|
+
if (!existsSync50(join58(theirsDir, CORE_MANIFEST_FILE))) {
|
|
13875
14012
|
console.log(
|
|
13876
14013
|
`orphan-ratchet guard (${label}): no ${CORE_MANIFEST_FILE} in ${theirsDir} \u2014 this is not a Biffo template/instance tree (a satellite repo never carries one), so there is no ownership manifest to classify paths against. Not applicable; skipping.`
|
|
13877
14014
|
);
|
|
@@ -13902,8 +14039,8 @@ baseline was ${String(ratchet.baseline)}, now ${String(ratchet.count)}. See biff
|
|
|
13902
14039
|
}
|
|
13903
14040
|
|
|
13904
14041
|
// src/lib/ownership-header-claim-guard.ts
|
|
13905
|
-
import { readFileSync as
|
|
13906
|
-
import { join as
|
|
14042
|
+
import { readFileSync as readFileSync40 } from "fs";
|
|
14043
|
+
import { join as join59 } from "path";
|
|
13907
14044
|
var OWNERSHIP_HEADER_SWEEP_DIRS = [
|
|
13908
14045
|
"scripts",
|
|
13909
14046
|
".githooks",
|
|
@@ -14112,7 +14249,7 @@ function sweepOwnershipHeaderClaims(root, options = {}) {
|
|
|
14112
14249
|
for (const rel of files) {
|
|
14113
14250
|
let content;
|
|
14114
14251
|
try {
|
|
14115
|
-
content =
|
|
14252
|
+
content = readFileSync40(join59(root, rel), "utf8");
|
|
14116
14253
|
} catch {
|
|
14117
14254
|
continue;
|
|
14118
14255
|
}
|
|
@@ -14187,8 +14324,8 @@ async function runOwnershipHeaderClaimCheck() {
|
|
|
14187
14324
|
}
|
|
14188
14325
|
|
|
14189
14326
|
// src/scripts/check-pipe-trap.ts
|
|
14190
|
-
import { readFileSync as
|
|
14191
|
-
import { join as
|
|
14327
|
+
import { readFileSync as readFileSync41, readdirSync as readdirSync23 } from "fs";
|
|
14328
|
+
import { join as join60, relative as relative9 } from "path";
|
|
14192
14329
|
|
|
14193
14330
|
// src/lib/pipe-trap-guard.ts
|
|
14194
14331
|
var STATUS_BEARING = [
|
|
@@ -14284,7 +14421,7 @@ function findPipeTraps(source) {
|
|
|
14284
14421
|
function shellFiles(root) {
|
|
14285
14422
|
const out = [];
|
|
14286
14423
|
for (const dir of ["scripts", ".githooks"]) {
|
|
14287
|
-
const full =
|
|
14424
|
+
const full = join60(root, dir);
|
|
14288
14425
|
let entries;
|
|
14289
14426
|
try {
|
|
14290
14427
|
entries = readdirSync23(full, { withFileTypes: true });
|
|
@@ -14294,7 +14431,7 @@ function shellFiles(root) {
|
|
|
14294
14431
|
for (const entry of entries) {
|
|
14295
14432
|
if (!entry.isFile()) continue;
|
|
14296
14433
|
if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
|
|
14297
|
-
out.push(
|
|
14434
|
+
out.push(join60(full, entry.name));
|
|
14298
14435
|
}
|
|
14299
14436
|
}
|
|
14300
14437
|
return out;
|
|
@@ -14310,7 +14447,7 @@ async function runPipeTrapCheck() {
|
|
|
14310
14447
|
process.exit(1);
|
|
14311
14448
|
}
|
|
14312
14449
|
const findings = files.flatMap(
|
|
14313
|
-
(file) => findPipeTraps(
|
|
14450
|
+
(file) => findPipeTraps(readFileSync41(file, "utf8")).map(
|
|
14314
14451
|
(t) => `${relative9(root, file)}:${t.line} ${t.text}
|
|
14315
14452
|
${t.reason}`
|
|
14316
14453
|
)
|
|
@@ -14327,8 +14464,8 @@ async function runPipeTrapCheck() {
|
|
|
14327
14464
|
}
|
|
14328
14465
|
|
|
14329
14466
|
// src/lib/plugin-allowlist-convention.ts
|
|
14330
|
-
import { readFileSync as
|
|
14331
|
-
import { join as
|
|
14467
|
+
import { readFileSync as readFileSync42 } from "fs";
|
|
14468
|
+
import { join as join61 } from "path";
|
|
14332
14469
|
var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
|
|
14333
14470
|
var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
|
|
14334
14471
|
var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
|
|
@@ -14339,7 +14476,7 @@ var PLUGIN = "<plugin>";
|
|
|
14339
14476
|
var ACCOUNT = "<account>";
|
|
14340
14477
|
function read(repoRoot, relative11) {
|
|
14341
14478
|
try {
|
|
14342
|
-
return
|
|
14479
|
+
return readFileSync42(join61(repoRoot, relative11), "utf8");
|
|
14343
14480
|
} catch {
|
|
14344
14481
|
throw new Error(`plugin-allowlist drift guard: cannot read ${relative11}`);
|
|
14345
14482
|
}
|
|
@@ -14467,32 +14604,32 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
14467
14604
|
}
|
|
14468
14605
|
|
|
14469
14606
|
// src/scripts/check-plugin-collisions.ts
|
|
14470
|
-
import { existsSync as
|
|
14471
|
-
import { join as
|
|
14607
|
+
import { existsSync as existsSync52 } from "fs";
|
|
14608
|
+
import { join as join63 } from "path";
|
|
14472
14609
|
|
|
14473
14610
|
// src/lib/plugin-collision-guard.ts
|
|
14474
|
-
import { existsSync as
|
|
14475
|
-
import { join as
|
|
14611
|
+
import { existsSync as existsSync51, readdirSync as readdirSync24, statSync as statSync15 } from "fs";
|
|
14612
|
+
import { join as join62 } from "path";
|
|
14476
14613
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
14477
14614
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
14478
14615
|
function subdirectories(dir) {
|
|
14479
|
-
if (!
|
|
14616
|
+
if (!existsSync51(dir)) return [];
|
|
14480
14617
|
return readdirSync24(dir).filter((entry) => {
|
|
14481
14618
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
14482
14619
|
try {
|
|
14483
|
-
return statSync15(
|
|
14620
|
+
return statSync15(join62(dir, entry)).isDirectory();
|
|
14484
14621
|
} catch {
|
|
14485
14622
|
return false;
|
|
14486
14623
|
}
|
|
14487
14624
|
});
|
|
14488
14625
|
}
|
|
14489
14626
|
function regularPackagesOf(pluginDir2) {
|
|
14490
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
14627
|
+
return subdirectories(pluginDir2).filter((name) => existsSync51(join62(pluginDir2, name, "__init__.py"))).sort();
|
|
14491
14628
|
}
|
|
14492
14629
|
function bareTestModulesOf(pluginDir2) {
|
|
14493
|
-
const testsDir =
|
|
14494
|
-
if (!
|
|
14495
|
-
if (
|
|
14630
|
+
const testsDir = join62(pluginDir2, "tests");
|
|
14631
|
+
if (!existsSync51(testsDir)) return [];
|
|
14632
|
+
if (existsSync51(join62(testsDir, "__init__.py"))) return [];
|
|
14496
14633
|
return readdirSync24(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
14497
14634
|
}
|
|
14498
14635
|
function findCollisions(servicesDir, pluginDirs) {
|
|
@@ -14501,7 +14638,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
14501
14638
|
const gather = (kind, namesOf) => {
|
|
14502
14639
|
const claims = /* @__PURE__ */ new Map();
|
|
14503
14640
|
for (const plugin of plugins) {
|
|
14504
|
-
for (const name of namesOf(
|
|
14641
|
+
for (const name of namesOf(join62(servicesDir, plugin))) {
|
|
14505
14642
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
14506
14643
|
}
|
|
14507
14644
|
}
|
|
@@ -14539,8 +14676,8 @@ function formatCollisions(collisions) {
|
|
|
14539
14676
|
// src/scripts/check-plugin-collisions.ts
|
|
14540
14677
|
async function runPluginCollisionCheck() {
|
|
14541
14678
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14542
|
-
const servicesDir =
|
|
14543
|
-
if (!
|
|
14679
|
+
const servicesDir = join63(root, "services");
|
|
14680
|
+
if (!existsSync52(servicesDir)) {
|
|
14544
14681
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
14545
14682
|
return;
|
|
14546
14683
|
}
|
|
@@ -14557,8 +14694,8 @@ async function runPluginCollisionCheck() {
|
|
|
14557
14694
|
}
|
|
14558
14695
|
|
|
14559
14696
|
// src/lib/plugin-terraform-guard.ts
|
|
14560
|
-
import { existsSync as
|
|
14561
|
-
import { dirname as dirname10, join as
|
|
14697
|
+
import { existsSync as existsSync53, readFileSync as readFileSync43, readdirSync as readdirSync25 } from "fs";
|
|
14698
|
+
import { dirname as dirname10, join as join64, relative as relative10, sep as sep4 } from "path";
|
|
14562
14699
|
var SKIP_DIRS4 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
14563
14700
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
14564
14701
|
function findPluginManifests(root) {
|
|
@@ -14573,9 +14710,9 @@ function findPluginManifests(root) {
|
|
|
14573
14710
|
for (const entry of entries) {
|
|
14574
14711
|
if (entry.isDirectory()) {
|
|
14575
14712
|
if (SKIP_DIRS4.has(entry.name)) continue;
|
|
14576
|
-
walk2(
|
|
14713
|
+
walk2(join64(dir, entry.name));
|
|
14577
14714
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
14578
|
-
found.push(relative10(root,
|
|
14715
|
+
found.push(relative10(root, join64(dir, entry.name)).split(sep4).join("/"));
|
|
14579
14716
|
}
|
|
14580
14717
|
}
|
|
14581
14718
|
};
|
|
@@ -14585,7 +14722,7 @@ function findPluginManifests(root) {
|
|
|
14585
14722
|
function readSubscriptions(absManifestPath) {
|
|
14586
14723
|
let parsed;
|
|
14587
14724
|
try {
|
|
14588
|
-
parsed = JSON.parse(
|
|
14725
|
+
parsed = JSON.parse(readFileSync43(absManifestPath, "utf8"));
|
|
14589
14726
|
} catch {
|
|
14590
14727
|
return null;
|
|
14591
14728
|
}
|
|
@@ -14600,14 +14737,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
14600
14737
|
}
|
|
14601
14738
|
function checkPluginTerraform(root) {
|
|
14602
14739
|
const violations = [];
|
|
14603
|
-
const coreManifest =
|
|
14740
|
+
const coreManifest = existsSync53(join64(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
14604
14741
|
for (const manifest of findPluginManifests(root)) {
|
|
14605
14742
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
14606
|
-
const absManifest =
|
|
14743
|
+
const absManifest = join64(root, manifest);
|
|
14607
14744
|
const subscriptions = readSubscriptions(absManifest);
|
|
14608
14745
|
if (subscriptions === null) continue;
|
|
14609
14746
|
const pluginDir2 = dirname10(absManifest);
|
|
14610
|
-
if (
|
|
14747
|
+
if (existsSync53(join64(pluginDir2, "terraform"))) continue;
|
|
14611
14748
|
const relPluginDir = relative10(root, pluginDir2).split(sep4).join("/");
|
|
14612
14749
|
violations.push({
|
|
14613
14750
|
manifest,
|
|
@@ -14638,12 +14775,12 @@ async function runPluginTerraformCheck() {
|
|
|
14638
14775
|
}
|
|
14639
14776
|
|
|
14640
14777
|
// src/scripts/check-plugin-tool-supply.ts
|
|
14641
|
-
import { existsSync as
|
|
14642
|
-
import { join as
|
|
14778
|
+
import { existsSync as existsSync55 } from "fs";
|
|
14779
|
+
import { join as join66 } from "path";
|
|
14643
14780
|
|
|
14644
14781
|
// src/lib/plugin-tool-supply-audit.ts
|
|
14645
|
-
import { existsSync as
|
|
14646
|
-
import { join as
|
|
14782
|
+
import { existsSync as existsSync54, readFileSync as readFileSync44, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
|
|
14783
|
+
import { join as join65 } from "path";
|
|
14647
14784
|
|
|
14648
14785
|
// src/lib/openrouter-model-snapshot.ts
|
|
14649
14786
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -15060,7 +15197,7 @@ function listDirs(root) {
|
|
|
15060
15197
|
}
|
|
15061
15198
|
return entries.filter((e) => {
|
|
15062
15199
|
try {
|
|
15063
|
-
return statSync16(
|
|
15200
|
+
return statSync16(join65(root, e)).isDirectory();
|
|
15064
15201
|
} catch {
|
|
15065
15202
|
return false;
|
|
15066
15203
|
}
|
|
@@ -15076,7 +15213,7 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
15076
15213
|
return;
|
|
15077
15214
|
}
|
|
15078
15215
|
for (const entry of entries) {
|
|
15079
|
-
const p =
|
|
15216
|
+
const p = join65(dir, entry);
|
|
15080
15217
|
let st;
|
|
15081
15218
|
try {
|
|
15082
15219
|
st = statSync16(p);
|
|
@@ -15102,14 +15239,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
15102
15239
|
);
|
|
15103
15240
|
}
|
|
15104
15241
|
function pluginTerraformFiles(pluginDir2) {
|
|
15105
|
-
const tfDir =
|
|
15242
|
+
const tfDir = join65(pluginDir2, "terraform");
|
|
15106
15243
|
let entries;
|
|
15107
15244
|
try {
|
|
15108
15245
|
entries = readdirSync26(tfDir);
|
|
15109
15246
|
} catch {
|
|
15110
15247
|
return [];
|
|
15111
15248
|
}
|
|
15112
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
15249
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join65(tfDir, e)).sort();
|
|
15113
15250
|
}
|
|
15114
15251
|
function extractManifestTools(manifestText) {
|
|
15115
15252
|
let parsed;
|
|
@@ -15361,8 +15498,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
15361
15498
|
function normalizeModelId(id) {
|
|
15362
15499
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
15363
15500
|
}
|
|
15364
|
-
var CONFIG_PY_PATH =
|
|
15365
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
15501
|
+
var CONFIG_PY_PATH = join65("services", "api", "src", "api", "config.py");
|
|
15502
|
+
var ORCHESTRATION_SCHEMA_PATH = join65(
|
|
15366
15503
|
"services",
|
|
15367
15504
|
"api",
|
|
15368
15505
|
"src",
|
|
@@ -15374,10 +15511,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
15374
15511
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
15375
15512
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
15376
15513
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
15377
|
-
const configPath =
|
|
15378
|
-
const orchestrationPath =
|
|
15379
|
-
const configMissing = !
|
|
15380
|
-
const orchestrationSchemaMissing = !
|
|
15514
|
+
const configPath = join65(repoRoot, CONFIG_PY_PATH);
|
|
15515
|
+
const orchestrationPath = join65(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
15516
|
+
const configMissing = !existsSync54(configPath);
|
|
15517
|
+
const orchestrationSchemaMissing = !existsSync54(orchestrationPath);
|
|
15381
15518
|
const knownSet = new Set(knownModelIds);
|
|
15382
15519
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
15383
15520
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -15395,13 +15532,13 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
15395
15532
|
};
|
|
15396
15533
|
let settingsBlind = false;
|
|
15397
15534
|
if (!configMissing) {
|
|
15398
|
-
const settingsFields = extractSettingsModelFields(
|
|
15535
|
+
const settingsFields = extractSettingsModelFields(readFileSync44(configPath, "utf8"));
|
|
15399
15536
|
if (settingsFields.length === 0) settingsBlind = true;
|
|
15400
15537
|
for (const { field, value } of settingsFields) record(`${CONFIG_PY_PATH}#${field}`, value);
|
|
15401
15538
|
}
|
|
15402
15539
|
let curatedFieldsBlind = false;
|
|
15403
15540
|
if (!orchestrationSchemaMissing) {
|
|
15404
|
-
const curated = extractCuratedModelFields(
|
|
15541
|
+
const curated = extractCuratedModelFields(readFileSync44(orchestrationPath, "utf8"));
|
|
15405
15542
|
if (curated.rawFieldCount > 0 && curated.fields.every((f) => f.defaultValue === null && f.optionValues.length === 0)) {
|
|
15406
15543
|
curatedFieldsBlind = true;
|
|
15407
15544
|
}
|
|
@@ -15448,7 +15585,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
15448
15585
|
function discoverPluginDirs(pluginsRoot) {
|
|
15449
15586
|
return listDirs(pluginsRoot).filter((name) => {
|
|
15450
15587
|
try {
|
|
15451
|
-
return statSync16(
|
|
15588
|
+
return statSync16(join65(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
15452
15589
|
} catch {
|
|
15453
15590
|
return false;
|
|
15454
15591
|
}
|
|
@@ -15461,8 +15598,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
15461
15598
|
let terraformBlind = false;
|
|
15462
15599
|
let totalDeclaredTools = 0;
|
|
15463
15600
|
for (const name of pluginNames) {
|
|
15464
|
-
const pluginDir2 =
|
|
15465
|
-
const manifestText =
|
|
15601
|
+
const pluginDir2 = join65(pluginsRoot, name);
|
|
15602
|
+
const manifestText = readFileSync44(join65(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
15466
15603
|
const manifest = extractManifestTools(manifestText);
|
|
15467
15604
|
if (manifest.parseError) {
|
|
15468
15605
|
findings.push({
|
|
@@ -15480,13 +15617,13 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
15480
15617
|
totalDeclaredTools += manifest.tools.length;
|
|
15481
15618
|
const pySources = pluginPythonFiles(pluginDir2).map((f) => ({
|
|
15482
15619
|
file: f,
|
|
15483
|
-
text:
|
|
15620
|
+
text: readFileSync44(f, "utf8")
|
|
15484
15621
|
}));
|
|
15485
15622
|
const resolver = buildSymbolResolver(pySources);
|
|
15486
15623
|
const registry = extractToolRegistryEntries(pySources, resolver);
|
|
15487
15624
|
if (registry.rawToolDefinitionCount > 0 && registry.entries.length === 0) registryBlind = true;
|
|
15488
15625
|
const tfFiles = pluginTerraformFiles(pluginDir2);
|
|
15489
|
-
const tfText = tfFiles.map((f) =>
|
|
15626
|
+
const tfText = tfFiles.map((f) => readFileSync44(f, "utf8")).join("\n");
|
|
15490
15627
|
const terraform = extractTerraformEnvKeys(tfText);
|
|
15491
15628
|
if (terraform.rawMarkerCount > 0 && terraform.resolvedBlockCount === 0) terraformBlind = true;
|
|
15492
15629
|
for (const toolName of manifest.tools) {
|
|
@@ -15560,7 +15697,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
15560
15697
|
requiredEnvVars: envResult.envVars,
|
|
15561
15698
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
15562
15699
|
status: anyWired ? "ok" : "missing-env",
|
|
15563
|
-
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 ${
|
|
15700
|
+
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 ${join65(pluginDir2, "terraform")}, so this deployment can never supply it`
|
|
15564
15701
|
});
|
|
15565
15702
|
}
|
|
15566
15703
|
}
|
|
@@ -15600,8 +15737,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
15600
15737
|
return;
|
|
15601
15738
|
}
|
|
15602
15739
|
let allOk = true;
|
|
15603
|
-
const pluginsRoot =
|
|
15604
|
-
if (!
|
|
15740
|
+
const pluginsRoot = join66(root, "services", "_plugins");
|
|
15741
|
+
if (!existsSync55(pluginsRoot)) {
|
|
15605
15742
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
15606
15743
|
} else {
|
|
15607
15744
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -15631,8 +15768,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
15631
15768
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
15632
15769
|
}
|
|
15633
15770
|
}
|
|
15634
|
-
const servicesApiRoot =
|
|
15635
|
-
if (!
|
|
15771
|
+
const servicesApiRoot = join66(root, "services", "api");
|
|
15772
|
+
if (!existsSync55(servicesApiRoot)) {
|
|
15636
15773
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
15637
15774
|
} else {
|
|
15638
15775
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -15806,10 +15943,10 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
15806
15943
|
}
|
|
15807
15944
|
|
|
15808
15945
|
// src/scripts/check-shared-file-reduction.ts
|
|
15809
|
-
import { readFileSync as
|
|
15946
|
+
import { readFileSync as readFileSync46 } from "fs";
|
|
15810
15947
|
|
|
15811
15948
|
// src/lib/shared-file-reduction-guard.ts
|
|
15812
|
-
import { readFileSync as
|
|
15949
|
+
import { readFileSync as readFileSync45 } from "fs";
|
|
15813
15950
|
var LEAF_TEST_CALLS = /* @__PURE__ */ new Set(["it", "test"]);
|
|
15814
15951
|
var SUITE_CALLS = /* @__PURE__ */ new Set(["describe", "suite"]);
|
|
15815
15952
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
@@ -15936,7 +16073,7 @@ function formatReductionReport(report) {
|
|
|
15936
16073
|
// src/scripts/check-shared-file-reduction.ts
|
|
15937
16074
|
function readStdin() {
|
|
15938
16075
|
try {
|
|
15939
|
-
return
|
|
16076
|
+
return readFileSync46(0, "utf8");
|
|
15940
16077
|
} catch {
|
|
15941
16078
|
return "";
|
|
15942
16079
|
}
|
|
@@ -15953,15 +16090,15 @@ function pairsFromTsv(tsv) {
|
|
|
15953
16090
|
const [target, existingPath, incomingPath] = fields;
|
|
15954
16091
|
pairs.push({
|
|
15955
16092
|
target,
|
|
15956
|
-
existing:
|
|
15957
|
-
incoming:
|
|
16093
|
+
existing: readFileSync46(existingPath, "utf8"),
|
|
16094
|
+
incoming: readFileSync46(incomingPath, "utf8")
|
|
15958
16095
|
});
|
|
15959
16096
|
}
|
|
15960
16097
|
return pairs;
|
|
15961
16098
|
}
|
|
15962
16099
|
function loadAccepted(manifestPath) {
|
|
15963
16100
|
if (!manifestPath) return {};
|
|
15964
|
-
const parsed = JSON.parse(
|
|
16101
|
+
const parsed = JSON.parse(readFileSync46(manifestPath, "utf8"));
|
|
15965
16102
|
return parsed.acceptedReductions ?? {};
|
|
15966
16103
|
}
|
|
15967
16104
|
async function runSharedFileReductionCheck(args) {
|
|
@@ -15969,13 +16106,13 @@ async function runSharedFileReductionCheck(args) {
|
|
|
15969
16106
|
let accepted;
|
|
15970
16107
|
try {
|
|
15971
16108
|
if (args.pairs) {
|
|
15972
|
-
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() :
|
|
16109
|
+
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() : readFileSync46(args.pairs, "utf8"));
|
|
15973
16110
|
} else if (args.target && args.existing && args.incoming) {
|
|
15974
16111
|
pairs = [
|
|
15975
16112
|
{
|
|
15976
16113
|
target: args.target,
|
|
15977
|
-
existing:
|
|
15978
|
-
incoming:
|
|
16114
|
+
existing: readFileSync46(args.existing, "utf8"),
|
|
16115
|
+
incoming: readFileSync46(args.incoming, "utf8")
|
|
15979
16116
|
}
|
|
15980
16117
|
];
|
|
15981
16118
|
} else {
|
|
@@ -16009,12 +16146,12 @@ async function runSharedFileReductionCheck(args) {
|
|
|
16009
16146
|
}
|
|
16010
16147
|
|
|
16011
16148
|
// src/scripts/check-skeleton-drift.ts
|
|
16012
|
-
import { existsSync as
|
|
16013
|
-
import { join as
|
|
16149
|
+
import { existsSync as existsSync56, readdirSync as readdirSync28 } from "fs";
|
|
16150
|
+
import { join as join68 } from "path";
|
|
16014
16151
|
|
|
16015
16152
|
// src/lib/skeleton-drift-guard.ts
|
|
16016
|
-
import { readFileSync as
|
|
16017
|
-
import { join as
|
|
16153
|
+
import { readFileSync as readFileSync47, readdirSync as readdirSync27, statSync as statSync17 } from "fs";
|
|
16154
|
+
import { join as join67 } from "path";
|
|
16018
16155
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
16019
16156
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
16020
16157
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -16078,7 +16215,7 @@ function walk(dir, base = dir) {
|
|
|
16078
16215
|
}
|
|
16079
16216
|
for (const entry of entries) {
|
|
16080
16217
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
16081
|
-
const abs =
|
|
16218
|
+
const abs = join67(dir, entry);
|
|
16082
16219
|
let isDir;
|
|
16083
16220
|
try {
|
|
16084
16221
|
isDir = statSync17(abs).isDirectory();
|
|
@@ -16100,7 +16237,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
16100
16237
|
if (!rule.appliesTo(rel)) continue;
|
|
16101
16238
|
let contents;
|
|
16102
16239
|
try {
|
|
16103
|
-
contents =
|
|
16240
|
+
contents = readFileSync47(join67(skeletonRoot, rel), "utf8");
|
|
16104
16241
|
} catch {
|
|
16105
16242
|
continue;
|
|
16106
16243
|
}
|
|
@@ -16129,23 +16266,23 @@ function formatViolations2(violations) {
|
|
|
16129
16266
|
|
|
16130
16267
|
// src/scripts/check-skeleton-drift.ts
|
|
16131
16268
|
function discoverSkeletons(root) {
|
|
16132
|
-
const skeletonsDir =
|
|
16269
|
+
const skeletonsDir = join68(root, "_skeletons");
|
|
16133
16270
|
let entries;
|
|
16134
16271
|
try {
|
|
16135
16272
|
entries = readdirSync28(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
16136
16273
|
} catch {
|
|
16137
16274
|
return [];
|
|
16138
16275
|
}
|
|
16139
|
-
return entries.filter((name) =>
|
|
16276
|
+
return entries.filter((name) => existsSync56(join68(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
16140
16277
|
}
|
|
16141
16278
|
async function runSkeletonDriftCheck() {
|
|
16142
16279
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
16143
16280
|
const skeletons = discoverSkeletons(root);
|
|
16144
16281
|
let filesConsidered = 0;
|
|
16145
16282
|
for (const name of skeletons) {
|
|
16146
|
-
const skeletonRoot =
|
|
16283
|
+
const skeletonRoot = join68(root, "_skeletons", name);
|
|
16147
16284
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
16148
|
-
if (
|
|
16285
|
+
if (existsSync56(join68(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
16149
16286
|
filesConsidered += 1;
|
|
16150
16287
|
}
|
|
16151
16288
|
}
|
|
@@ -16153,7 +16290,7 @@ async function runSkeletonDriftCheck() {
|
|
|
16153
16290
|
`audited ${skeletons.length} skeleton(s) (${skeletons.join(", ") || "none"}), ${filesConsidered} file(s) considered, under ${root}/_skeletons`
|
|
16154
16291
|
);
|
|
16155
16292
|
if (skeletons.length === 0) {
|
|
16156
|
-
if (!
|
|
16293
|
+
if (!existsSync56(join68(root, "_skeletons"))) {
|
|
16157
16294
|
console.log(
|
|
16158
16295
|
`\xB7 Skeleton-drift guard: no _skeletons/ directory under ${root} \u2014 this is not a repo that ships scaffolding (a satellite repo never carries one). Not applicable; skipping.`
|
|
16159
16296
|
);
|
|
@@ -16165,7 +16302,7 @@ async function runSkeletonDriftCheck() {
|
|
|
16165
16302
|
process.exit(1);
|
|
16166
16303
|
}
|
|
16167
16304
|
const violations = skeletons.flatMap(
|
|
16168
|
-
(name) => auditSkeleton(
|
|
16305
|
+
(name) => auditSkeleton(join68(root, "_skeletons", name), name)
|
|
16169
16306
|
);
|
|
16170
16307
|
if (violations.length > 0) {
|
|
16171
16308
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -16366,8 +16503,8 @@ function rawArgsAfter(subcommand) {
|
|
|
16366
16503
|
}
|
|
16367
16504
|
|
|
16368
16505
|
// src/commands/doctor.ts
|
|
16369
|
-
import { existsSync as
|
|
16370
|
-
import { join as
|
|
16506
|
+
import { existsSync as existsSync57, readFileSync as readFileSync48 } from "fs";
|
|
16507
|
+
import { join as join70, resolve as resolve20 } from "path";
|
|
16371
16508
|
import chalk21 from "chalk";
|
|
16372
16509
|
import { Command as Command25 } from "commander";
|
|
16373
16510
|
|
|
@@ -16708,11 +16845,11 @@ async function reapAllBareBranches(cwd, branches, worktrees, currentBranch, deps
|
|
|
16708
16845
|
|
|
16709
16846
|
// src/lib/scratch-clone-scan.ts
|
|
16710
16847
|
import { readdirSync as readdirSync29, statSync as statSync18 } from "fs";
|
|
16711
|
-
import { join as
|
|
16848
|
+
import { join as join69 } from "path";
|
|
16712
16849
|
var INTEGRATION_BRANCH = "dev";
|
|
16713
16850
|
function isPlainCloneDir(path) {
|
|
16714
16851
|
try {
|
|
16715
|
-
return statSync18(
|
|
16852
|
+
return statSync18(join69(path, ".git")).isDirectory();
|
|
16716
16853
|
} catch {
|
|
16717
16854
|
return false;
|
|
16718
16855
|
}
|
|
@@ -16727,7 +16864,7 @@ async function findScratchCloneCandidates(estateRoot, deps) {
|
|
|
16727
16864
|
const names = readdirSync29(estateRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
16728
16865
|
const candidates = [];
|
|
16729
16866
|
for (const name of names) {
|
|
16730
|
-
const path =
|
|
16867
|
+
const path = join69(estateRoot, name);
|
|
16731
16868
|
if (!isPlainCloneDir(path)) continue;
|
|
16732
16869
|
let branch;
|
|
16733
16870
|
try {
|
|
@@ -16907,10 +17044,10 @@ function printScratchCloneReports(estateRoot, reports) {
|
|
|
16907
17044
|
);
|
|
16908
17045
|
}
|
|
16909
17046
|
function readLocalCoreVersion(cwd) {
|
|
16910
|
-
const path =
|
|
16911
|
-
if (!
|
|
17047
|
+
const path = join70(cwd, INSTANCE_CORE_FILE);
|
|
17048
|
+
if (!existsSync57(path)) return null;
|
|
16912
17049
|
try {
|
|
16913
|
-
return extractVersionField(
|
|
17050
|
+
return extractVersionField(readFileSync48(path, "utf8"));
|
|
16914
17051
|
} catch {
|
|
16915
17052
|
return null;
|
|
16916
17053
|
}
|
|
@@ -16930,10 +17067,10 @@ function extractVersionField(contents) {
|
|
|
16930
17067
|
return match?.[1] ?? null;
|
|
16931
17068
|
}
|
|
16932
17069
|
function readFossil(cwd) {
|
|
16933
|
-
const path =
|
|
16934
|
-
if (!
|
|
17070
|
+
const path = join70(cwd, CORE_VERSION_FILE);
|
|
17071
|
+
if (!existsSync57(path)) return null;
|
|
16935
17072
|
try {
|
|
16936
|
-
const value =
|
|
17073
|
+
const value = readFileSync48(path, "utf8").trim();
|
|
16937
17074
|
return value === "" ? null : value;
|
|
16938
17075
|
} catch {
|
|
16939
17076
|
return null;
|
|
@@ -17471,13 +17608,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
17471
17608
|
import { Command as Command27 } from "commander";
|
|
17472
17609
|
|
|
17473
17610
|
// src/lib/packaged-scripts.ts
|
|
17474
|
-
import { existsSync as
|
|
17475
|
-
import { dirname as dirname11, join as
|
|
17611
|
+
import { existsSync as existsSync58 } from "fs";
|
|
17612
|
+
import { dirname as dirname11, join as join71 } from "path";
|
|
17476
17613
|
function findPackagedScript(startDir, relativePath) {
|
|
17477
17614
|
let dir = startDir;
|
|
17478
17615
|
for (; ; ) {
|
|
17479
|
-
const candidate =
|
|
17480
|
-
if (
|
|
17616
|
+
const candidate = join71(dir, relativePath);
|
|
17617
|
+
if (existsSync58(candidate)) return candidate;
|
|
17481
17618
|
const parent = dirname11(dir);
|
|
17482
17619
|
if (parent === dir) return null;
|
|
17483
17620
|
dir = parent;
|