@biffo/cli 0.315.9 → 0.316.1
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 +483 -315
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9169,10 +9169,10 @@ var RegistryPluginEntrySchema = z8.object({
|
|
|
9169
9169
|
ui_components: z8.array(UiComponentEntrySchema).optional(),
|
|
9170
9170
|
status: z8.enum(["active", "disabled"])
|
|
9171
9171
|
});
|
|
9172
|
-
var
|
|
9172
|
+
var PluginRegistryEnvelopeSchema = z8.object({
|
|
9173
9173
|
schema_version: z8.string(),
|
|
9174
9174
|
last_updated: z8.string(),
|
|
9175
|
-
plugins: z8.array(
|
|
9175
|
+
plugins: z8.array(z8.unknown())
|
|
9176
9176
|
});
|
|
9177
9177
|
var DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/keiranholloway/biffo-plugins-registry/main/plugins.json";
|
|
9178
9178
|
var RegistryAdapter = class {
|
|
@@ -9203,14 +9203,31 @@ var RegistryAdapter = class {
|
|
|
9203
9203
|
`Plugin registry at ${this.registryUrl} did not return valid JSON: ${err.message}`
|
|
9204
9204
|
);
|
|
9205
9205
|
}
|
|
9206
|
-
const
|
|
9207
|
-
if (!
|
|
9208
|
-
const messages =
|
|
9206
|
+
const envelope = PluginRegistryEnvelopeSchema.safeParse(raw);
|
|
9207
|
+
if (!envelope.success) {
|
|
9208
|
+
const messages = envelope.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
|
|
9209
9209
|
throw new Error(
|
|
9210
9210
|
`Plugin registry at ${this.registryUrl} has an invalid shape: ${messages.join("; ")}`
|
|
9211
9211
|
);
|
|
9212
9212
|
}
|
|
9213
|
-
|
|
9213
|
+
const plugins = [];
|
|
9214
|
+
for (const [index, entry] of envelope.data.plugins.entries()) {
|
|
9215
|
+
const entryResult = RegistryPluginEntrySchema.safeParse(entry);
|
|
9216
|
+
if (entryResult.success) {
|
|
9217
|
+
plugins.push(entryResult.data);
|
|
9218
|
+
continue;
|
|
9219
|
+
}
|
|
9220
|
+
const label = entry !== null && typeof entry === "object" && "name" in entry && typeof entry.name === "string" ? `'${entry.name}'` : `at index ${index}`;
|
|
9221
|
+
const messages = entryResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
|
|
9222
|
+
log.warn(
|
|
9223
|
+
`Plugin registry at ${this.registryUrl} has a malformed entry ${label} \u2014 skipped: ${messages.join("; ")}`
|
|
9224
|
+
);
|
|
9225
|
+
}
|
|
9226
|
+
return {
|
|
9227
|
+
schema_version: envelope.data.schema_version,
|
|
9228
|
+
last_updated: envelope.data.last_updated,
|
|
9229
|
+
plugins
|
|
9230
|
+
};
|
|
9214
9231
|
}
|
|
9215
9232
|
/**
|
|
9216
9233
|
* Resolves `name@minorVersion` (e.g. "rbac", "1.0") against the registry.
|
|
@@ -9311,8 +9328,8 @@ function printScopeSeamEntitlement(name) {
|
|
|
9311
9328
|
}
|
|
9312
9329
|
|
|
9313
9330
|
// src/commands/plugin-install.ts
|
|
9314
|
-
import { cpSync as cpSync5, existsSync as
|
|
9315
|
-
import { join as
|
|
9331
|
+
import { cpSync as cpSync5, existsSync as existsSync32, mkdirSync as mkdirSync11, readFileSync as readFileSync24, statSync as statSync7, writeFileSync as writeFileSync13 } from "fs";
|
|
9332
|
+
import { join as join34, relative as relative4, resolve as resolve12 } from "path";
|
|
9316
9333
|
import chalk15 from "chalk";
|
|
9317
9334
|
import { Command as Command15 } from "commander";
|
|
9318
9335
|
|
|
@@ -9409,9 +9426,122 @@ ${lines.join("\n")}
|
|
|
9409
9426
|
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
9427
|
}
|
|
9411
9428
|
|
|
9412
|
-
// src/lib/plugin-
|
|
9429
|
+
// src/lib/plugin-frontend-registry.ts
|
|
9413
9430
|
import { existsSync as existsSync28, readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "fs";
|
|
9414
9431
|
import { join as join29 } from "path";
|
|
9432
|
+
var PLUGIN_REGISTRY_RELATIVE_PATH = "apps/frontend/src/lib/plugins.ts";
|
|
9433
|
+
var REGISTRY_START_MARKER = "// BIFFO-PLUGIN-REGISTRY:START \u2014 managed by `biffo plugin install`/`uninstall`. Do not hand-edit.";
|
|
9434
|
+
var REGISTRY_END_MARKER = "// BIFFO-PLUGIN-REGISTRY:END";
|
|
9435
|
+
function titleFromSlug(slug) {
|
|
9436
|
+
return slug.split("-").filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
9437
|
+
}
|
|
9438
|
+
function frontendUrlForSlug(slug) {
|
|
9439
|
+
return `/api/v1/plugins/${slug}/ui`;
|
|
9440
|
+
}
|
|
9441
|
+
function registryPath2(cwd) {
|
|
9442
|
+
return join29(cwd, PLUGIN_REGISTRY_RELATIVE_PATH);
|
|
9443
|
+
}
|
|
9444
|
+
function missingRegistryError(cwd, pluginSlug) {
|
|
9445
|
+
return new Error(
|
|
9446
|
+
`${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.`
|
|
9447
|
+
);
|
|
9448
|
+
}
|
|
9449
|
+
function malformedRegistryError(cwd, pluginSlug) {
|
|
9450
|
+
return new Error(
|
|
9451
|
+
`${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.`
|
|
9452
|
+
);
|
|
9453
|
+
}
|
|
9454
|
+
function findManagedRegion(source) {
|
|
9455
|
+
const startIdx = source.indexOf(REGISTRY_START_MARKER);
|
|
9456
|
+
if (startIdx === -1) return null;
|
|
9457
|
+
const bodyStart = startIdx + REGISTRY_START_MARKER.length;
|
|
9458
|
+
const endIdx = source.indexOf(REGISTRY_END_MARKER, bodyStart);
|
|
9459
|
+
if (endIdx === -1) return null;
|
|
9460
|
+
return {
|
|
9461
|
+
region: { before: source.slice(0, bodyStart), after: source.slice(endIdx) },
|
|
9462
|
+
body: source.slice(bodyStart, endIdx)
|
|
9463
|
+
};
|
|
9464
|
+
}
|
|
9465
|
+
var SLUG_FIELD_PATTERN = /\bslug\s*:\s*(['"])([a-zA-Z0-9_-]+)\1/;
|
|
9466
|
+
function extractSlug(entryRaw) {
|
|
9467
|
+
const match = SLUG_FIELD_PATTERN.exec(entryRaw);
|
|
9468
|
+
return match ? match[2] : null;
|
|
9469
|
+
}
|
|
9470
|
+
function splitEntries(body) {
|
|
9471
|
+
const entries = [];
|
|
9472
|
+
const n = body.length;
|
|
9473
|
+
let i = 0;
|
|
9474
|
+
while (i < n) {
|
|
9475
|
+
const ch = body[i];
|
|
9476
|
+
if (ch !== "{") {
|
|
9477
|
+
i++;
|
|
9478
|
+
continue;
|
|
9479
|
+
}
|
|
9480
|
+
const start = i + 1;
|
|
9481
|
+
let depth = 1;
|
|
9482
|
+
let j = start;
|
|
9483
|
+
let quote = null;
|
|
9484
|
+
while (j < n && depth > 0) {
|
|
9485
|
+
const c = body[j];
|
|
9486
|
+
if (quote) {
|
|
9487
|
+
if (c === "\\") {
|
|
9488
|
+
j += 2;
|
|
9489
|
+
continue;
|
|
9490
|
+
}
|
|
9491
|
+
if (c === quote) quote = null;
|
|
9492
|
+
} else if (c === '"' || c === "'" || c === "`") {
|
|
9493
|
+
quote = c;
|
|
9494
|
+
} else if (c === "{") {
|
|
9495
|
+
depth++;
|
|
9496
|
+
} else if (c === "}") {
|
|
9497
|
+
depth--;
|
|
9498
|
+
}
|
|
9499
|
+
j++;
|
|
9500
|
+
}
|
|
9501
|
+
const raw = body.slice(start, j - 1);
|
|
9502
|
+
entries.push({ raw, slug: extractSlug(raw) });
|
|
9503
|
+
i = j;
|
|
9504
|
+
}
|
|
9505
|
+
return entries;
|
|
9506
|
+
}
|
|
9507
|
+
function serializeEntryRaw(entry) {
|
|
9508
|
+
return `
|
|
9509
|
+
slug: ${JSON.stringify(entry.slug)},
|
|
9510
|
+
title: ${JSON.stringify(entry.title)},
|
|
9511
|
+
frontendUrl: ${JSON.stringify(entry.frontendUrl)},
|
|
9512
|
+
`;
|
|
9513
|
+
}
|
|
9514
|
+
function assertPluginRegistryReady(cwd, pluginSlug) {
|
|
9515
|
+
readManagedEntries(cwd, pluginSlug);
|
|
9516
|
+
}
|
|
9517
|
+
function readManagedEntries(cwd, pluginSlug) {
|
|
9518
|
+
const path = registryPath2(cwd);
|
|
9519
|
+
if (!existsSync28(path)) throw missingRegistryError(cwd, pluginSlug);
|
|
9520
|
+
const source = readFileSync21(path, "utf8");
|
|
9521
|
+
const found = findManagedRegion(source);
|
|
9522
|
+
if (!found) throw malformedRegistryError(cwd, pluginSlug);
|
|
9523
|
+
return { source, region: found.region, entries: splitEntries(found.body) };
|
|
9524
|
+
}
|
|
9525
|
+
function writeManagedEntries(cwd, region, entries) {
|
|
9526
|
+
const body = entries.length > 0 ? "\n" + entries.map((e) => ` {${e.raw}},
|
|
9527
|
+
`).join("") : "\n";
|
|
9528
|
+
writeFileSync10(registryPath2(cwd), region.before + body + region.after, "utf8");
|
|
9529
|
+
}
|
|
9530
|
+
function upsertPluginRegistryEntry(cwd, entry) {
|
|
9531
|
+
const { region, entries } = readManagedEntries(cwd, entry.slug);
|
|
9532
|
+
const preserved = entries.filter((e) => e.slug !== entry.slug);
|
|
9533
|
+
const next = { raw: serializeEntryRaw(entry), slug: entry.slug };
|
|
9534
|
+
writeManagedEntries(cwd, region, [...preserved, next]);
|
|
9535
|
+
}
|
|
9536
|
+
function removePluginRegistryEntry(cwd, pluginSlug) {
|
|
9537
|
+
const { region, entries } = readManagedEntries(cwd, pluginSlug);
|
|
9538
|
+
const next = entries.filter((e) => e.slug !== pluginSlug);
|
|
9539
|
+
writeManagedEntries(cwd, region, next);
|
|
9540
|
+
}
|
|
9541
|
+
|
|
9542
|
+
// src/lib/plugin-provenance.ts
|
|
9543
|
+
import { existsSync as existsSync29, readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "fs";
|
|
9544
|
+
import { join as join30 } from "path";
|
|
9415
9545
|
var PLUGIN_PROVENANCE_FILENAME = ".biffo-plugin-provenance.json";
|
|
9416
9546
|
function isPluginProvenance(value) {
|
|
9417
9547
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -9419,11 +9549,11 @@ function isPluginProvenance(value) {
|
|
|
9419
9549
|
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
9550
|
}
|
|
9421
9551
|
function readProvenance(pluginDir2) {
|
|
9422
|
-
const path =
|
|
9423
|
-
if (!
|
|
9552
|
+
const path = join30(pluginDir2, PLUGIN_PROVENANCE_FILENAME);
|
|
9553
|
+
if (!existsSync29(path)) return { status: "absent" };
|
|
9424
9554
|
let parsed;
|
|
9425
9555
|
try {
|
|
9426
|
-
parsed = JSON.parse(
|
|
9556
|
+
parsed = JSON.parse(readFileSync22(path, "utf8"));
|
|
9427
9557
|
} catch (err) {
|
|
9428
9558
|
return {
|
|
9429
9559
|
status: "invalid",
|
|
@@ -9439,7 +9569,7 @@ function readProvenance(pluginDir2) {
|
|
|
9439
9569
|
return { status: "present", record: parsed };
|
|
9440
9570
|
}
|
|
9441
9571
|
function writePluginProvenance(pluginDir2, record) {
|
|
9442
|
-
|
|
9572
|
+
writeFileSync11(join30(pluginDir2, PLUGIN_PROVENANCE_FILENAME), `${JSON.stringify(record, null, 2)}
|
|
9443
9573
|
`);
|
|
9444
9574
|
}
|
|
9445
9575
|
function reconcileProvenance(previous, next) {
|
|
@@ -9489,8 +9619,8 @@ async function tryGit(cwd, args) {
|
|
|
9489
9619
|
}
|
|
9490
9620
|
|
|
9491
9621
|
// src/lib/plugin-seed-vendor.ts
|
|
9492
|
-
import { cpSync as cpSync3, existsSync as
|
|
9493
|
-
import { join as
|
|
9622
|
+
import { cpSync as cpSync3, existsSync as existsSync30, mkdirSync as mkdirSync9, readdirSync as readdirSync12, rmSync as rmSync8 } from "fs";
|
|
9623
|
+
import { join as join31 } from "path";
|
|
9494
9624
|
var VENDOR_PREFIX = "_plugin-";
|
|
9495
9625
|
function pluginSeedImportDir(pluginName) {
|
|
9496
9626
|
return `db/imports/${VENDOR_PREFIX}${pluginName}`;
|
|
@@ -9499,8 +9629,8 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
|
9499
9629
|
if (!manifest.seed) {
|
|
9500
9630
|
return { vendored: false };
|
|
9501
9631
|
}
|
|
9502
|
-
const sourceSeedDir =
|
|
9503
|
-
if (!
|
|
9632
|
+
const sourceSeedDir = join31(pluginSourceDir, manifest.seed.dir);
|
|
9633
|
+
if (!existsSync30(sourceSeedDir)) {
|
|
9504
9634
|
throw new Error(
|
|
9505
9635
|
`${manifest.name}'s manifest declares seed.dir '${manifest.seed.dir}', but ${sourceSeedDir} does not exist in the plugin's source.`
|
|
9506
9636
|
);
|
|
@@ -9512,11 +9642,11 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
|
9512
9642
|
);
|
|
9513
9643
|
}
|
|
9514
9644
|
const relTargetDir = pluginSeedImportDir(manifest.name);
|
|
9515
|
-
const targetDir =
|
|
9645
|
+
const targetDir = join31(cwd, relTargetDir);
|
|
9516
9646
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
9517
9647
|
mkdirSync9(targetDir, { recursive: true });
|
|
9518
9648
|
for (const file of sqlFiles) {
|
|
9519
|
-
cpSync3(
|
|
9649
|
+
cpSync3(join31(sourceSeedDir, file), join31(targetDir, file));
|
|
9520
9650
|
}
|
|
9521
9651
|
log.success(
|
|
9522
9652
|
`Vendored ${sqlFiles.length} seed file(s) to ${relTargetDir}/ (baseline_tables: ${manifest.seed.baseline_tables.join(", ") || "none declared"})`
|
|
@@ -9529,7 +9659,7 @@ function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
|
9529
9659
|
|
|
9530
9660
|
// src/lib/plugin-source-copy.ts
|
|
9531
9661
|
import { copyFileSync as copyFileSync2, cpSync as cpSync4, mkdirSync as mkdirSync10 } from "fs";
|
|
9532
|
-
import { basename as basename2, dirname as dirname9, join as
|
|
9662
|
+
import { basename as basename2, dirname as dirname9, join as join32 } from "path";
|
|
9533
9663
|
var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
9534
9664
|
".git",
|
|
9535
9665
|
".venv",
|
|
@@ -9545,9 +9675,9 @@ async function copyPluginSource(sourceDir, targetDir) {
|
|
|
9545
9675
|
if (await isGitWorkingTree2(sourceDir)) {
|
|
9546
9676
|
const files = await listGitFiles(sourceDir);
|
|
9547
9677
|
for (const relPath of files) {
|
|
9548
|
-
const destPath =
|
|
9678
|
+
const destPath = join32(targetDir, relPath);
|
|
9549
9679
|
mkdirSync10(dirname9(destPath), { recursive: true });
|
|
9550
|
-
copyFileSync2(
|
|
9680
|
+
copyFileSync2(join32(sourceDir, relPath), destPath);
|
|
9551
9681
|
}
|
|
9552
9682
|
return { usedGitIgnoreRules: true };
|
|
9553
9683
|
}
|
|
@@ -9579,8 +9709,8 @@ async function listGitFiles(dir) {
|
|
|
9579
9709
|
}
|
|
9580
9710
|
|
|
9581
9711
|
// src/lib/plugin-workspace-sources.ts
|
|
9582
|
-
import { existsSync as
|
|
9583
|
-
import { join as
|
|
9712
|
+
import { existsSync as existsSync31, readdirSync as readdirSync13, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
|
|
9713
|
+
import { join as join33 } from "path";
|
|
9584
9714
|
function readTomlStringArray(text, key) {
|
|
9585
9715
|
const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
|
|
9586
9716
|
if (!open) return [];
|
|
@@ -9624,9 +9754,9 @@ function readDependencyNames(text) {
|
|
|
9624
9754
|
return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
|
|
9625
9755
|
}
|
|
9626
9756
|
function workspaceMemberNames(instanceRoot) {
|
|
9627
|
-
const rootPyproject =
|
|
9628
|
-
if (!
|
|
9629
|
-
const text =
|
|
9757
|
+
const rootPyproject = join33(instanceRoot, "pyproject.toml");
|
|
9758
|
+
if (!existsSync31(rootPyproject)) return /* @__PURE__ */ new Set();
|
|
9759
|
+
const text = readFileSync23(rootPyproject, "utf8");
|
|
9630
9760
|
const members = readTomlStringArray(text, "members");
|
|
9631
9761
|
const excluded = new Set(readTomlStringArray(text, "exclude"));
|
|
9632
9762
|
const dirs = [];
|
|
@@ -9635,7 +9765,7 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
9635
9765
|
const base = member.slice(0, -2);
|
|
9636
9766
|
let entries;
|
|
9637
9767
|
try {
|
|
9638
|
-
entries = readdirSync13(
|
|
9768
|
+
entries = readdirSync13(join33(instanceRoot, base), { withFileTypes: true });
|
|
9639
9769
|
} catch {
|
|
9640
9770
|
continue;
|
|
9641
9771
|
}
|
|
@@ -9649,9 +9779,9 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
9649
9779
|
}
|
|
9650
9780
|
const names = /* @__PURE__ */ new Set();
|
|
9651
9781
|
for (const dir of dirs) {
|
|
9652
|
-
const pp =
|
|
9653
|
-
if (!
|
|
9654
|
-
const name = readProjectName(
|
|
9782
|
+
const pp = join33(instanceRoot, dir, "pyproject.toml");
|
|
9783
|
+
if (!existsSync31(pp)) continue;
|
|
9784
|
+
const name = readProjectName(readFileSync23(pp, "utf8"));
|
|
9655
9785
|
if (name) names.add(name);
|
|
9656
9786
|
}
|
|
9657
9787
|
return names;
|
|
@@ -9662,8 +9792,8 @@ function existingWorkspaceSources(text) {
|
|
|
9662
9792
|
);
|
|
9663
9793
|
}
|
|
9664
9794
|
function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
|
|
9665
|
-
if (!
|
|
9666
|
-
const text =
|
|
9795
|
+
if (!existsSync31(pluginPyprojectPath) || memberNames.size === 0) return [];
|
|
9796
|
+
const text = readFileSync23(pluginPyprojectPath, "utf8");
|
|
9667
9797
|
const already = existingWorkspaceSources(text);
|
|
9668
9798
|
const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
|
|
9669
9799
|
if (toAdd.length === 0) return [];
|
|
@@ -9683,12 +9813,12 @@ ${lines.join("\n")}${text.slice(insertAt)}`;
|
|
|
9683
9813
|
${lines.join("\n")}
|
|
9684
9814
|
`;
|
|
9685
9815
|
}
|
|
9686
|
-
|
|
9816
|
+
writeFileSync12(pluginPyprojectPath, updated);
|
|
9687
9817
|
return toAdd;
|
|
9688
9818
|
}
|
|
9689
9819
|
function applyWorkspaceSources(targetDir, cwd, relTargetDir) {
|
|
9690
|
-
const pluginPyproject =
|
|
9691
|
-
if (!
|
|
9820
|
+
const pluginPyproject = join33(targetDir, "pyproject.toml");
|
|
9821
|
+
if (!existsSync31(pluginPyproject)) return;
|
|
9692
9822
|
const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(cwd));
|
|
9693
9823
|
if (sourced.length > 0) {
|
|
9694
9824
|
log.info(
|
|
@@ -9734,14 +9864,14 @@ var pluginInstallCommand = new Command15("install").description(
|
|
|
9734
9864
|
}
|
|
9735
9865
|
);
|
|
9736
9866
|
function resolveLocalPlugin(localPath) {
|
|
9737
|
-
if (!
|
|
9867
|
+
if (!existsSync32(localPath)) {
|
|
9738
9868
|
throw new Error(`--local path does not exist: ${localPath}`);
|
|
9739
9869
|
}
|
|
9740
9870
|
if (!statSync7(localPath).isDirectory()) {
|
|
9741
9871
|
throw new Error(`--local path is not a directory: ${localPath}`);
|
|
9742
9872
|
}
|
|
9743
|
-
const manifestPath =
|
|
9744
|
-
if (!
|
|
9873
|
+
const manifestPath = join34(localPath, "biffo.plugin.json");
|
|
9874
|
+
if (!existsSync32(manifestPath)) {
|
|
9745
9875
|
throw new Error(
|
|
9746
9876
|
`${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
9877
|
);
|
|
@@ -9767,8 +9897,8 @@ function parsePluginTarget(target) {
|
|
|
9767
9897
|
async function cloneAndValidatePlugin(entry, git) {
|
|
9768
9898
|
const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
|
|
9769
9899
|
try {
|
|
9770
|
-
const manifestPath =
|
|
9771
|
-
if (!
|
|
9900
|
+
const manifestPath = join34(tmpDir, "biffo.plugin.json");
|
|
9901
|
+
if (!existsSync32(manifestPath)) {
|
|
9772
9902
|
throw new Error(
|
|
9773
9903
|
`Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
|
|
9774
9904
|
);
|
|
@@ -9796,8 +9926,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9796
9926
|
`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
9927
|
);
|
|
9798
9928
|
}
|
|
9799
|
-
const servicesDir =
|
|
9800
|
-
if (!
|
|
9929
|
+
const servicesDir = join34(options.cwd, "services");
|
|
9930
|
+
if (!existsSync32(servicesDir)) {
|
|
9801
9931
|
throw new Error(
|
|
9802
9932
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9803
9933
|
);
|
|
@@ -9815,10 +9945,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9815
9945
|
}
|
|
9816
9946
|
const pluginName = entry ? entry.name : source.name;
|
|
9817
9947
|
const relTargetDir = pluginDir(pluginName, "third-party");
|
|
9818
|
-
const targetDir =
|
|
9819
|
-
const modulesDir =
|
|
9948
|
+
const targetDir = join34(options.cwd, relTargetDir);
|
|
9949
|
+
const modulesDir = join34(options.cwd, "modules", "plugins", pluginName);
|
|
9820
9950
|
const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
|
|
9821
|
-
if (
|
|
9951
|
+
if (existsSync32(targetDir) && !inTreeSource) {
|
|
9822
9952
|
throw new Error(
|
|
9823
9953
|
`Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
|
|
9824
9954
|
);
|
|
@@ -9850,7 +9980,7 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9850
9980
|
log.success(
|
|
9851
9981
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
9852
9982
|
);
|
|
9853
|
-
const retiredShapeReasons = findRetiredFrontendShape(
|
|
9983
|
+
const retiredShapeReasons = findRetiredFrontendShape(join34(source.sourceDir, "terraform"));
|
|
9854
9984
|
if (retiredShapeReasons.length > 0) {
|
|
9855
9985
|
throw new Error(retiredFrontendShapeError(pluginName, retiredShapeReasons));
|
|
9856
9986
|
}
|
|
@@ -9862,6 +9992,9 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9862
9992
|
if (configSupply.missingRequired.length > 0) {
|
|
9863
9993
|
throw new Error(missingRequiredConfigMessage(pluginName, configSupply.missingRequired));
|
|
9864
9994
|
}
|
|
9995
|
+
if (manifest.user_frontend) {
|
|
9996
|
+
assertPluginRegistryReady(options.cwd, pluginName);
|
|
9997
|
+
}
|
|
9865
9998
|
if (inTreeSource) {
|
|
9866
9999
|
log.info(`${relTargetDir}/ is already in this checkout \u2014 installing in place.`);
|
|
9867
10000
|
} else {
|
|
@@ -9874,8 +10007,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9874
10007
|
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
9875
10008
|
applyWorkspaceSources(targetDir, options.cwd, relTargetDir);
|
|
9876
10009
|
const stagePaths = [relTargetDir];
|
|
9877
|
-
const tfSourceDir =
|
|
9878
|
-
if (
|
|
10010
|
+
const tfSourceDir = join34(targetDir, "terraform");
|
|
10011
|
+
if (existsSync32(tfSourceDir)) {
|
|
9879
10012
|
mkdirSync11(modulesDir, { recursive: true });
|
|
9880
10013
|
cpSync5(tfSourceDir, modulesDir, { recursive: true });
|
|
9881
10014
|
stagePaths.push(`modules/plugins/${pluginName}`);
|
|
@@ -9921,8 +10054,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9921
10054
|
stagePaths.push(seedResult.stagedPath);
|
|
9922
10055
|
}
|
|
9923
10056
|
if (manifest.config.length > 0) {
|
|
9924
|
-
const configFilePath =
|
|
9925
|
-
|
|
10057
|
+
const configFilePath = join34(targetDir, "biffo.plugin-config.json");
|
|
10058
|
+
writeFileSync13(
|
|
9926
10059
|
configFilePath,
|
|
9927
10060
|
JSON.stringify(
|
|
9928
10061
|
{
|
|
@@ -9945,6 +10078,15 @@ async function runPluginInstall(target, options, deps) {
|
|
|
9945
10078
|
`Recorded ${configSupply.resolved.length}/${manifest.config.length} declared config value(s) at ${relative4(options.cwd, configFilePath)}`
|
|
9946
10079
|
);
|
|
9947
10080
|
}
|
|
10081
|
+
if (manifest.user_frontend) {
|
|
10082
|
+
upsertPluginRegistryEntry(options.cwd, {
|
|
10083
|
+
slug: pluginName,
|
|
10084
|
+
title: titleFromSlug(pluginName),
|
|
10085
|
+
frontendUrl: frontendUrlForSlug(pluginName)
|
|
10086
|
+
});
|
|
10087
|
+
stagePaths.push(PLUGIN_REGISTRY_RELATIVE_PATH);
|
|
10088
|
+
log.success(`Registered ${pluginName} in ${PLUGIN_REGISTRY_RELATIVE_PATH}`);
|
|
10089
|
+
}
|
|
9948
10090
|
const commitMessage = `feat(plugins): install ${pluginName}@${source.version}`;
|
|
9949
10091
|
await deps.git.add(options.cwd, stagePaths);
|
|
9950
10092
|
await deps.git.commit(options.cwd, commitMessage);
|
|
@@ -9979,7 +10121,7 @@ function printConfigWiringInstructions(pluginName, resolved) {
|
|
|
9979
10121
|
}
|
|
9980
10122
|
function parseManifestFile(path) {
|
|
9981
10123
|
try {
|
|
9982
|
-
return JSON.parse(
|
|
10124
|
+
return JSON.parse(readFileSync24(path, "utf8"));
|
|
9983
10125
|
} catch (err) {
|
|
9984
10126
|
throw new Error(`Could not parse ${path} as JSON: ${err.message}`);
|
|
9985
10127
|
}
|
|
@@ -10033,8 +10175,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource, suppliedConfig
|
|
|
10033
10175
|
}
|
|
10034
10176
|
|
|
10035
10177
|
// src/commands/plugin-list.ts
|
|
10036
|
-
import { existsSync as
|
|
10037
|
-
import { join as
|
|
10178
|
+
import { existsSync as existsSync33, readFileSync as readFileSync25 } from "fs";
|
|
10179
|
+
import { join as join35, resolve as resolve13 } from "path";
|
|
10038
10180
|
import chalk16 from "chalk";
|
|
10039
10181
|
import { Command as Command16 } from "commander";
|
|
10040
10182
|
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 +10189,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
|
|
|
10047
10189
|
}
|
|
10048
10190
|
});
|
|
10049
10191
|
async function runPluginList(options) {
|
|
10050
|
-
const servicesDir =
|
|
10051
|
-
if (!
|
|
10192
|
+
const servicesDir = join35(options.cwd, "services");
|
|
10193
|
+
if (!existsSync33(servicesDir)) {
|
|
10052
10194
|
throw new Error(
|
|
10053
10195
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
10054
10196
|
);
|
|
@@ -10056,7 +10198,7 @@ async function runPluginList(options) {
|
|
|
10056
10198
|
const plugins = [];
|
|
10057
10199
|
for (const location of findInstalledPlugins(options.cwd)) {
|
|
10058
10200
|
try {
|
|
10059
|
-
const manifest = validateManifest(JSON.parse(
|
|
10201
|
+
const manifest = validateManifest(JSON.parse(readFileSync25(location.manifestPath, "utf8")));
|
|
10060
10202
|
plugins.push({
|
|
10061
10203
|
name: manifest.name,
|
|
10062
10204
|
version: manifest.version,
|
|
@@ -10097,30 +10239,35 @@ import { resolve as resolve14 } from "path";
|
|
|
10097
10239
|
import { Command as Command17 } from "commander";
|
|
10098
10240
|
|
|
10099
10241
|
// src/lib/plugin-staleness.ts
|
|
10100
|
-
import { existsSync as
|
|
10101
|
-
import { join as
|
|
10242
|
+
import { existsSync as existsSync34, readFileSync as readFileSync26, readdirSync as readdirSync14, statSync as statSync8 } from "fs";
|
|
10243
|
+
import { join as join36, relative as relative5 } from "path";
|
|
10102
10244
|
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) =>
|
|
10245
|
+
if (!existsSync34(servicesDir)) return [];
|
|
10246
|
+
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
10247
|
}
|
|
10106
10248
|
async function checkPluginStaleness(cwd, deps) {
|
|
10107
|
-
const servicesDir =
|
|
10249
|
+
const servicesDir = join36(cwd, "services");
|
|
10108
10250
|
const names = discoverVendoredPlugins(servicesDir);
|
|
10109
10251
|
let registryRepoByName = null;
|
|
10252
|
+
let registryFetchFailure = null;
|
|
10110
10253
|
const resolveRegistryRepo = async (name) => {
|
|
10111
|
-
if (registryRepoByName === null) {
|
|
10112
|
-
registryRepoByName = /* @__PURE__ */ new Map();
|
|
10254
|
+
if (registryRepoByName === null && registryFetchFailure === null) {
|
|
10113
10255
|
try {
|
|
10114
10256
|
const reg = await deps.registry.fetchRegistry();
|
|
10257
|
+
registryRepoByName = /* @__PURE__ */ new Map();
|
|
10115
10258
|
for (const entry of reg.plugins) registryRepoByName.set(entry.name, entry.repo);
|
|
10116
|
-
} catch {
|
|
10259
|
+
} catch (err) {
|
|
10260
|
+
registryFetchFailure = err.message;
|
|
10117
10261
|
}
|
|
10118
10262
|
}
|
|
10119
|
-
|
|
10263
|
+
if (registryFetchFailure !== null) {
|
|
10264
|
+
return { repo: null, fetchFailure: registryFetchFailure };
|
|
10265
|
+
}
|
|
10266
|
+
return { repo: registryRepoByName?.get(name) ?? null, fetchFailure: null };
|
|
10120
10267
|
};
|
|
10121
10268
|
const results = [];
|
|
10122
10269
|
for (const name of names) {
|
|
10123
|
-
results.push(await checkOnePlugin(
|
|
10270
|
+
results.push(await checkOnePlugin(join36(servicesDir, name), name, resolveRegistryRepo, deps.git));
|
|
10124
10271
|
}
|
|
10125
10272
|
return results;
|
|
10126
10273
|
}
|
|
@@ -10146,19 +10293,28 @@ async function checkOnePlugin(pluginDir2, name, resolveRegistryRepo, git) {
|
|
|
10146
10293
|
if (record?.sha && isFetchableUrl(record.origin)) {
|
|
10147
10294
|
return checkViaProvenance(name, record, record.origin, git);
|
|
10148
10295
|
}
|
|
10149
|
-
const localOrigin = record && !isFetchableUrl(record.origin) &&
|
|
10296
|
+
const localOrigin = record && !isFetchableUrl(record.origin) && existsSync34(record.origin) ? record.origin : null;
|
|
10150
10297
|
if (localOrigin) {
|
|
10151
10298
|
return checkViaContentDiff(name, pluginDir2, localOrigin, { isLocalDir: true }, git);
|
|
10152
10299
|
}
|
|
10153
|
-
const
|
|
10154
|
-
if (!
|
|
10300
|
+
const lookup = await resolveRegistryRepo(name);
|
|
10301
|
+
if (!lookup.repo) {
|
|
10302
|
+
if (lookup.fetchFailure) {
|
|
10303
|
+
return {
|
|
10304
|
+
name,
|
|
10305
|
+
status: "cannot-tell",
|
|
10306
|
+
method: "unresolvable",
|
|
10307
|
+
detail: record ? `provenance records origin '${record.origin}', which is neither a reachable git URL nor a local directory that still exists, and the plugin registry could not be fetched to resolve '${name}' by name either: ${lookup.fetchFailure}` : `no provenance recorded (vendored before #1547) and the plugin registry could not be fetched to resolve '${name}' by name: ${lookup.fetchFailure}`
|
|
10308
|
+
};
|
|
10309
|
+
}
|
|
10155
10310
|
return {
|
|
10156
10311
|
name,
|
|
10157
10312
|
status: "cannot-tell",
|
|
10158
10313
|
method: "unresolvable",
|
|
10159
|
-
detail: record ? `provenance records origin '${record.origin}', which is neither a reachable git URL nor a local directory that still exists, and '${name}' was not found in the plugin registry either` : `no provenance recorded (vendored before #1547
|
|
10314
|
+
detail: record ? `provenance records origin '${record.origin}', which is neither a reachable git URL nor a local directory that still exists, and '${name}' was not found in the plugin registry either` : `no provenance recorded (vendored before #1547) and '${name}' was not found in the plugin registry \u2014 nothing to compare against`
|
|
10160
10315
|
};
|
|
10161
10316
|
}
|
|
10317
|
+
const registryRepo = lookup.repo;
|
|
10162
10318
|
if (record?.sha) {
|
|
10163
10319
|
return checkViaProvenance(name, record, registryRepo, git);
|
|
10164
10320
|
}
|
|
@@ -10280,8 +10436,8 @@ async function countDifferingFiles(sourceDir, pluginDir2) {
|
|
|
10280
10436
|
differing++;
|
|
10281
10437
|
continue;
|
|
10282
10438
|
}
|
|
10283
|
-
const a =
|
|
10284
|
-
const b =
|
|
10439
|
+
const a = readFileSync26(join36(sourceDir, relPath));
|
|
10440
|
+
const b = readFileSync26(join36(pluginDir2, relPath));
|
|
10285
10441
|
if (!a.equals(b)) differing++;
|
|
10286
10442
|
}
|
|
10287
10443
|
return differing;
|
|
@@ -10296,11 +10452,11 @@ function vendorFileList(dir) {
|
|
|
10296
10452
|
return new Set(walkExcluding(dir, dir, LOCAL_COPY_EXCLUDES));
|
|
10297
10453
|
}
|
|
10298
10454
|
function walkExcluding(root, dir, excludes) {
|
|
10299
|
-
if (!
|
|
10455
|
+
if (!existsSync34(dir)) return [];
|
|
10300
10456
|
const out = [];
|
|
10301
10457
|
for (const entry of readdirSync14(dir)) {
|
|
10302
10458
|
if (excludes.has(entry) || entry === ".git") continue;
|
|
10303
|
-
const full =
|
|
10459
|
+
const full = join36(dir, entry);
|
|
10304
10460
|
let stat;
|
|
10305
10461
|
try {
|
|
10306
10462
|
stat = statSync8(full);
|
|
@@ -10357,8 +10513,8 @@ var pluginStalenessCommand = new Command17("staleness").description(
|
|
|
10357
10513
|
});
|
|
10358
10514
|
|
|
10359
10515
|
// src/commands/plugin-sync-migrations.ts
|
|
10360
|
-
import { existsSync as
|
|
10361
|
-
import { join as
|
|
10516
|
+
import { existsSync as existsSync35 } from "fs";
|
|
10517
|
+
import { join as join37, relative as relative6, resolve as resolve15 } from "path";
|
|
10362
10518
|
import chalk17 from "chalk";
|
|
10363
10519
|
import { Command as Command18 } from "commander";
|
|
10364
10520
|
var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
@@ -10379,11 +10535,11 @@ var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
|
10379
10535
|
}
|
|
10380
10536
|
);
|
|
10381
10537
|
async function runPluginSyncMigrations(name, options, deps) {
|
|
10382
|
-
const servicesDir =
|
|
10383
|
-
if (!
|
|
10538
|
+
const servicesDir = join37(options.cwd, "services");
|
|
10539
|
+
if (!existsSync35(servicesDir)) {
|
|
10384
10540
|
throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
|
|
10385
10541
|
}
|
|
10386
|
-
if (name && !
|
|
10542
|
+
if (name && !existsSync35(join37(servicesDir, name, "biffo.plugin.json"))) {
|
|
10387
10543
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
10388
10544
|
}
|
|
10389
10545
|
if (options.dryRun) {
|
|
@@ -10419,8 +10575,8 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
10419
10575
|
}
|
|
10420
10576
|
|
|
10421
10577
|
// src/commands/plugin-uninstall.ts
|
|
10422
|
-
import { existsSync as
|
|
10423
|
-
import { join as
|
|
10578
|
+
import { existsSync as existsSync36, readFileSync as readFileSync27, rmSync as rmSync9 } from "fs";
|
|
10579
|
+
import { join as join38, resolve as resolve16 } from "path";
|
|
10424
10580
|
import chalk18 from "chalk";
|
|
10425
10581
|
import { Command as Command19 } from "commander";
|
|
10426
10582
|
import inquirer6 from "inquirer";
|
|
@@ -10452,28 +10608,33 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
10452
10608
|
if (!NAME_PATTERN2.test(name)) {
|
|
10453
10609
|
throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
|
|
10454
10610
|
}
|
|
10455
|
-
const servicesDir =
|
|
10456
|
-
if (!
|
|
10611
|
+
const servicesDir = join38(options.cwd, "services");
|
|
10612
|
+
if (!existsSync36(servicesDir)) {
|
|
10457
10613
|
throw new Error(
|
|
10458
10614
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
10459
10615
|
);
|
|
10460
10616
|
}
|
|
10461
|
-
const targetDir =
|
|
10462
|
-
if (!
|
|
10463
|
-
const firstParty =
|
|
10464
|
-
if (
|
|
10617
|
+
const targetDir = join38(servicesDir, name);
|
|
10618
|
+
if (!existsSync36(targetDir)) {
|
|
10619
|
+
const firstParty = join38(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
|
|
10620
|
+
if (existsSync36(firstParty)) {
|
|
10465
10621
|
throw new Error(
|
|
10466
10622
|
`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
10623
|
);
|
|
10468
10624
|
}
|
|
10469
10625
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
10470
10626
|
}
|
|
10471
|
-
const
|
|
10472
|
-
const
|
|
10627
|
+
const installedManifest = readInstalledManifest(targetDir);
|
|
10628
|
+
const version = installedManifest?.version;
|
|
10629
|
+
const modulesDir = join38(options.cwd, "modules", "plugins", name);
|
|
10473
10630
|
const stagePaths = [`services/${name}`];
|
|
10474
|
-
if (
|
|
10631
|
+
if (existsSync36(modulesDir)) {
|
|
10475
10632
|
stagePaths.push(`modules/plugins/${name}`);
|
|
10476
10633
|
}
|
|
10634
|
+
const hasUserFrontend = installedManifest?.user_frontend !== void 0;
|
|
10635
|
+
if (hasUserFrontend) {
|
|
10636
|
+
stagePaths.push(PLUGIN_REGISTRY_RELATIVE_PATH);
|
|
10637
|
+
}
|
|
10477
10638
|
if (options.dryRun) {
|
|
10478
10639
|
printDryRun5(name, version, stagePaths, options.keepData);
|
|
10479
10640
|
return;
|
|
@@ -10491,7 +10652,10 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
10491
10652
|
`${options.cwd} is not a git repository \u2014 biffo plugin uninstall must be run from a Biffo project checkout.`
|
|
10492
10653
|
);
|
|
10493
10654
|
}
|
|
10494
|
-
if (
|
|
10655
|
+
if (hasUserFrontend) {
|
|
10656
|
+
assertPluginRegistryReady(options.cwd, name);
|
|
10657
|
+
}
|
|
10658
|
+
if (existsSync36(modulesDir)) {
|
|
10495
10659
|
const refs = findPluginModuleReferences(options.cwd, name).filter(
|
|
10496
10660
|
(r) => !r.file.endsWith(`/${GENERATED_TF_FILE}`) && r.file !== GENERATED_TF_FILE
|
|
10497
10661
|
);
|
|
@@ -10506,7 +10670,7 @@ Remove the reference(s) above first, then re-run uninstall.`
|
|
|
10506
10670
|
}
|
|
10507
10671
|
rmSync9(targetDir, { recursive: true, force: true });
|
|
10508
10672
|
log.success(`Removed services/${name}/`);
|
|
10509
|
-
if (
|
|
10673
|
+
if (existsSync36(modulesDir)) {
|
|
10510
10674
|
rmSync9(modulesDir, { recursive: true, force: true });
|
|
10511
10675
|
log.success(`Removed modules/plugins/${name}/`);
|
|
10512
10676
|
const wiring = syncPluginTerraform(options.cwd);
|
|
@@ -10517,6 +10681,10 @@ Remove the reference(s) above first, then re-run uninstall.`
|
|
|
10517
10681
|
);
|
|
10518
10682
|
}
|
|
10519
10683
|
}
|
|
10684
|
+
if (hasUserFrontend) {
|
|
10685
|
+
removePluginRegistryEntry(options.cwd, name);
|
|
10686
|
+
log.success(`Removed ${name} from ${PLUGIN_REGISTRY_RELATIVE_PATH}`);
|
|
10687
|
+
}
|
|
10520
10688
|
const label = version ? `${name}@${version}` : name;
|
|
10521
10689
|
const commitMessage = `chore(plugins): uninstall ${label}`;
|
|
10522
10690
|
await deps.git.add(options.cwd, stagePaths);
|
|
@@ -10541,17 +10709,17 @@ Remove the reference(s) above first, then re-run uninstall.`
|
|
|
10541
10709
|
"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
10710
|
);
|
|
10543
10711
|
}
|
|
10544
|
-
if (
|
|
10712
|
+
if (existsSync36(join38(options.cwd, pluginSeedImportDir(name)))) {
|
|
10545
10713
|
log.warn(
|
|
10546
10714
|
`${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
10715
|
);
|
|
10548
10716
|
}
|
|
10549
10717
|
}
|
|
10550
|
-
function
|
|
10551
|
-
const manifestPath =
|
|
10552
|
-
if (!
|
|
10718
|
+
function readInstalledManifest(targetDir) {
|
|
10719
|
+
const manifestPath = join38(targetDir, "biffo.plugin.json");
|
|
10720
|
+
if (!existsSync36(manifestPath)) return void 0;
|
|
10553
10721
|
try {
|
|
10554
|
-
return validateManifest(JSON.parse(
|
|
10722
|
+
return validateManifest(JSON.parse(readFileSync27(manifestPath, "utf8")));
|
|
10555
10723
|
} catch {
|
|
10556
10724
|
return void 0;
|
|
10557
10725
|
}
|
|
@@ -10584,8 +10752,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
|
|
|
10584
10752
|
}
|
|
10585
10753
|
|
|
10586
10754
|
// src/commands/plugin-upgrade.ts
|
|
10587
|
-
import { cpSync as cpSync6, existsSync as
|
|
10588
|
-
import { join as
|
|
10755
|
+
import { cpSync as cpSync6, existsSync as existsSync37, mkdirSync as mkdirSync12, readFileSync as readFileSync28, rmSync as rmSync10 } from "fs";
|
|
10756
|
+
import { join as join39, relative as relative7, resolve as resolve17 } from "path";
|
|
10589
10757
|
import chalk19 from "chalk";
|
|
10590
10758
|
import { Command as Command20 } from "commander";
|
|
10591
10759
|
import inquirer7 from "inquirer";
|
|
@@ -10632,8 +10800,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10632
10800
|
`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
10801
|
);
|
|
10634
10802
|
}
|
|
10635
|
-
const servicesDir =
|
|
10636
|
-
if (!
|
|
10803
|
+
const servicesDir = join39(options.cwd, "services");
|
|
10804
|
+
if (!existsSync37(servicesDir)) {
|
|
10637
10805
|
throw new Error(
|
|
10638
10806
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
10639
10807
|
);
|
|
@@ -10642,13 +10810,13 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10642
10810
|
return runLocalPluginRefresh(options.local, options, deps);
|
|
10643
10811
|
}
|
|
10644
10812
|
const { name, minor } = parsePluginTarget(target);
|
|
10645
|
-
const targetDir =
|
|
10646
|
-
if (!
|
|
10813
|
+
const targetDir = join39(servicesDir, name);
|
|
10814
|
+
if (!existsSync37(targetDir)) {
|
|
10647
10815
|
throw new Error(
|
|
10648
10816
|
`Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
|
|
10649
10817
|
);
|
|
10650
10818
|
}
|
|
10651
|
-
const currentVersion =
|
|
10819
|
+
const currentVersion = readInstalledVersion(targetDir);
|
|
10652
10820
|
log.info(`Resolving ${name}@${minor} from the plugin registry...`);
|
|
10653
10821
|
const entry = await deps.registry.resolvePlugin(name, minor);
|
|
10654
10822
|
log.success(`Resolved ${entry.name}@${entry.version} \u2014 ${entry.repo}`);
|
|
@@ -10657,7 +10825,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10657
10825
|
`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
10826
|
);
|
|
10659
10827
|
}
|
|
10660
|
-
const modulesDir =
|
|
10828
|
+
const modulesDir = join39(options.cwd, "modules", "plugins", entry.name);
|
|
10661
10829
|
if (options.dryRun) {
|
|
10662
10830
|
printDryRun6(entry, currentVersion);
|
|
10663
10831
|
return;
|
|
@@ -10685,11 +10853,11 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10685
10853
|
log.success(
|
|
10686
10854
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
10687
10855
|
);
|
|
10688
|
-
const retiredShapeReasons = findRetiredFrontendShape(
|
|
10856
|
+
const retiredShapeReasons = findRetiredFrontendShape(join39(tmpDir, "terraform"));
|
|
10689
10857
|
if (retiredShapeReasons.length > 0) {
|
|
10690
10858
|
throw new Error(retiredFrontendShapeError(entry.name, retiredShapeReasons));
|
|
10691
10859
|
}
|
|
10692
|
-
if (!
|
|
10860
|
+
if (!existsSync37(join39(tmpDir, "terraform"))) {
|
|
10693
10861
|
refuseIfModuleStillReferenced(options.cwd, modulesDir, entry.name);
|
|
10694
10862
|
}
|
|
10695
10863
|
const previousProvenance = readProvenance(targetDir);
|
|
@@ -10706,11 +10874,11 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10706
10874
|
applyWorkspaceSources(targetDir, options.cwd, `services/${entry.name}`);
|
|
10707
10875
|
const newPyproject = readPyprojectIfPresent(targetDir);
|
|
10708
10876
|
const stagePaths = [`services/${entry.name}`];
|
|
10709
|
-
if (
|
|
10877
|
+
if (existsSync37(modulesDir)) {
|
|
10710
10878
|
rmSync10(modulesDir, { recursive: true, force: true });
|
|
10711
10879
|
}
|
|
10712
|
-
const tfSourceDir =
|
|
10713
|
-
if (
|
|
10880
|
+
const tfSourceDir = join39(targetDir, "terraform");
|
|
10881
|
+
if (existsSync37(tfSourceDir)) {
|
|
10714
10882
|
mkdirSync12(modulesDir, { recursive: true });
|
|
10715
10883
|
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
10716
10884
|
stagePaths.push(`modules/plugins/${entry.name}`);
|
|
@@ -10771,16 +10939,16 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
10771
10939
|
async function runLocalPluginRefresh(localPath, options, deps) {
|
|
10772
10940
|
const source = resolveLocalPlugin(localPath);
|
|
10773
10941
|
log.success(`Resolved ${source.name}@${source.version} from ${source.origin}`);
|
|
10774
|
-
const servicesDir =
|
|
10775
|
-
const targetDir =
|
|
10776
|
-
if (!
|
|
10942
|
+
const servicesDir = join39(options.cwd, "services");
|
|
10943
|
+
const targetDir = join39(servicesDir, source.name);
|
|
10944
|
+
if (!existsSync37(targetDir)) {
|
|
10777
10945
|
throw new Error(
|
|
10778
10946
|
`Plugin '${source.name}' is not installed at services/${source.name}/. Use 'biffo plugin install --local ${localPath}' instead.`
|
|
10779
10947
|
);
|
|
10780
10948
|
}
|
|
10781
10949
|
const inTreeSource = resolve17(source.sourceDir) === resolve17(targetDir);
|
|
10782
|
-
const currentVersion =
|
|
10783
|
-
const modulesDir =
|
|
10950
|
+
const currentVersion = readInstalledVersion(targetDir);
|
|
10951
|
+
const modulesDir = join39(options.cwd, "modules", "plugins", source.name);
|
|
10784
10952
|
if (options.dryRun) {
|
|
10785
10953
|
printLocalDryRun(source, currentVersion, inTreeSource);
|
|
10786
10954
|
return;
|
|
@@ -10803,11 +10971,11 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
10803
10971
|
log.success(
|
|
10804
10972
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
10805
10973
|
);
|
|
10806
|
-
const retiredShapeReasons = findRetiredFrontendShape(
|
|
10974
|
+
const retiredShapeReasons = findRetiredFrontendShape(join39(source.sourceDir, "terraform"));
|
|
10807
10975
|
if (retiredShapeReasons.length > 0) {
|
|
10808
10976
|
throw new Error(retiredFrontendShapeError(source.name, retiredShapeReasons));
|
|
10809
10977
|
}
|
|
10810
|
-
if (!
|
|
10978
|
+
if (!existsSync37(join39(source.sourceDir, "terraform"))) {
|
|
10811
10979
|
refuseIfModuleStillReferenced(options.cwd, modulesDir, source.name);
|
|
10812
10980
|
}
|
|
10813
10981
|
const previousProvenance = readProvenance(targetDir);
|
|
@@ -10827,11 +10995,11 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
10827
10995
|
applyWorkspaceSources(targetDir, options.cwd, `services/${source.name}`);
|
|
10828
10996
|
const newPyproject = readPyprojectIfPresent(targetDir);
|
|
10829
10997
|
const stagePaths = [`services/${source.name}`];
|
|
10830
|
-
if (
|
|
10998
|
+
if (existsSync37(modulesDir)) {
|
|
10831
10999
|
rmSync10(modulesDir, { recursive: true, force: true });
|
|
10832
11000
|
}
|
|
10833
|
-
const tfSourceDir =
|
|
10834
|
-
if (
|
|
11001
|
+
const tfSourceDir = join39(targetDir, "terraform");
|
|
11002
|
+
if (existsSync37(tfSourceDir)) {
|
|
10835
11003
|
mkdirSync12(modulesDir, { recursive: true });
|
|
10836
11004
|
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
10837
11005
|
stagePaths.push(`modules/plugins/${source.name}`);
|
|
@@ -10895,7 +11063,7 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
10895
11063
|
}
|
|
10896
11064
|
}
|
|
10897
11065
|
function refuseIfModuleStillReferenced(cwd, modulesDir, name) {
|
|
10898
|
-
if (!
|
|
11066
|
+
if (!existsSync37(modulesDir)) return;
|
|
10899
11067
|
const refs = findPluginModuleReferences(cwd, name);
|
|
10900
11068
|
if (refs.length === 0) return;
|
|
10901
11069
|
const refList = refs.map((r) => ` ${r.file}:${r.line} ${r.text}`).join("\n");
|
|
@@ -10956,8 +11124,8 @@ var defaultRunCommand2 = async (command, cwd) => {
|
|
|
10956
11124
|
}
|
|
10957
11125
|
};
|
|
10958
11126
|
function readPyprojectIfPresent(targetDir) {
|
|
10959
|
-
const path =
|
|
10960
|
-
return
|
|
11127
|
+
const path = join39(targetDir, "pyproject.toml");
|
|
11128
|
+
return existsSync37(path) ? readFileSync28(path, "utf8") : null;
|
|
10961
11129
|
}
|
|
10962
11130
|
function dependenciesChanged(before, after) {
|
|
10963
11131
|
if (before === after) return false;
|
|
@@ -10984,11 +11152,11 @@ function tomlTableBody(text, header) {
|
|
|
10984
11152
|
const nextHeader = /^\[/m.exec(rest);
|
|
10985
11153
|
return nextHeader ? rest.slice(0, nextHeader.index) : rest;
|
|
10986
11154
|
}
|
|
10987
|
-
function
|
|
10988
|
-
const manifestPath =
|
|
10989
|
-
if (!
|
|
11155
|
+
function readInstalledVersion(targetDir) {
|
|
11156
|
+
const manifestPath = join39(targetDir, "biffo.plugin.json");
|
|
11157
|
+
if (!existsSync37(manifestPath)) return void 0;
|
|
10990
11158
|
try {
|
|
10991
|
-
return validateManifest(JSON.parse(
|
|
11159
|
+
return validateManifest(JSON.parse(readFileSync28(manifestPath, "utf8"))).version;
|
|
10992
11160
|
} catch {
|
|
10993
11161
|
return void 0;
|
|
10994
11162
|
}
|
|
@@ -11071,7 +11239,7 @@ pluginCommand.addCommand(pluginStalenessCommand);
|
|
|
11071
11239
|
import { Command as Command23 } from "commander";
|
|
11072
11240
|
|
|
11073
11241
|
// src/commands/sibling-check-identity.ts
|
|
11074
|
-
import { existsSync as
|
|
11242
|
+
import { existsSync as existsSync38, readFileSync as readFileSync29 } from "fs";
|
|
11075
11243
|
import { resolve as resolve18 } from "path";
|
|
11076
11244
|
import chalk20 from "chalk";
|
|
11077
11245
|
import { Command as Command22 } from "commander";
|
|
@@ -11265,7 +11433,7 @@ async function fetchPublishedIdentity(portalUrl) {
|
|
|
11265
11433
|
}
|
|
11266
11434
|
async function resolveConfig4(options) {
|
|
11267
11435
|
if (options.config) {
|
|
11268
|
-
const raw = JSON.parse(
|
|
11436
|
+
const raw = JSON.parse(readFileSync29(resolve18(options.config), "utf8"));
|
|
11269
11437
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
11270
11438
|
if (!result.success) {
|
|
11271
11439
|
log.error(`Invalid config at ${options.config}:`);
|
|
@@ -11285,8 +11453,8 @@ async function resolveConfig4(options) {
|
|
|
11285
11453
|
return cfg;
|
|
11286
11454
|
}
|
|
11287
11455
|
const localConfigPath = resolve18(process.cwd(), "biffo.config.json");
|
|
11288
|
-
if (
|
|
11289
|
-
const raw = JSON.parse(
|
|
11456
|
+
if (existsSync38(localConfigPath)) {
|
|
11457
|
+
const raw = JSON.parse(readFileSync29(localConfigPath, "utf8"));
|
|
11290
11458
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
11291
11459
|
if (result.success) return result.data;
|
|
11292
11460
|
if (isTemplatePlaceholderConfig(raw)) {
|
|
@@ -11332,20 +11500,20 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
|
11332
11500
|
import { Command as Command24 } from "commander";
|
|
11333
11501
|
|
|
11334
11502
|
// src/scripts/check-adr-numbering.ts
|
|
11335
|
-
import { existsSync as
|
|
11336
|
-
import { join as
|
|
11503
|
+
import { existsSync as existsSync40 } from "fs";
|
|
11504
|
+
import { join as join41 } from "path";
|
|
11337
11505
|
|
|
11338
11506
|
// src/lib/adr-numbering-guard.ts
|
|
11339
|
-
import { existsSync as
|
|
11340
|
-
import { join as
|
|
11507
|
+
import { existsSync as existsSync39, readdirSync as readdirSync15, readFileSync as readFileSync30 } from "fs";
|
|
11508
|
+
import { join as join40 } from "path";
|
|
11341
11509
|
var ADR_FILENAME = /^(\d{4})-.+\.md$/;
|
|
11342
11510
|
var ALLOWLIST_FILENAME = ".numbering-allowlist";
|
|
11343
11511
|
var TEMPLATE_ADR_RESERVED_UPTO = "0099";
|
|
11344
11512
|
function readAdrNumberingAllowlist(adrDir) {
|
|
11345
|
-
const path =
|
|
11346
|
-
if (!
|
|
11513
|
+
const path = join40(adrDir, ALLOWLIST_FILENAME);
|
|
11514
|
+
if (!existsSync39(path)) return /* @__PURE__ */ new Set();
|
|
11347
11515
|
const numbers = /* @__PURE__ */ new Set();
|
|
11348
|
-
for (const rawLine of
|
|
11516
|
+
for (const rawLine of readFileSync30(path, "utf8").split("\n")) {
|
|
11349
11517
|
const line = rawLine.split("#")[0].trim();
|
|
11350
11518
|
if (line) numbers.add(line);
|
|
11351
11519
|
}
|
|
@@ -11353,7 +11521,7 @@ function readAdrNumberingAllowlist(adrDir) {
|
|
|
11353
11521
|
}
|
|
11354
11522
|
function adrNumbersIn(adrDir) {
|
|
11355
11523
|
const claims = /* @__PURE__ */ new Map();
|
|
11356
|
-
if (!
|
|
11524
|
+
if (!existsSync39(adrDir)) return claims;
|
|
11357
11525
|
for (const entry of readdirSync15(adrDir).sort()) {
|
|
11358
11526
|
const match = ADR_FILENAME.exec(entry);
|
|
11359
11527
|
if (!match) continue;
|
|
@@ -11408,8 +11576,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
|
|
|
11408
11576
|
// src/scripts/check-adr-numbering.ts
|
|
11409
11577
|
async function runAdrNumberingCheck() {
|
|
11410
11578
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11411
|
-
const adrDir =
|
|
11412
|
-
if (!
|
|
11579
|
+
const adrDir = join41(root, "docs", "ADR");
|
|
11580
|
+
if (!existsSync40(adrDir)) {
|
|
11413
11581
|
console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
|
|
11414
11582
|
return;
|
|
11415
11583
|
}
|
|
@@ -11447,8 +11615,8 @@ Already accepted? List it in docs/ADR/${ALLOWLIST_FILENAME} instead of leaving t
|
|
|
11447
11615
|
}
|
|
11448
11616
|
|
|
11449
11617
|
// src/lib/api-gateway-integration-guard.ts
|
|
11450
|
-
import { readFileSync as
|
|
11451
|
-
import { join as
|
|
11618
|
+
import { readFileSync as readFileSync31, readdirSync as readdirSync16, statSync as statSync9 } from "fs";
|
|
11619
|
+
import { join as join42 } from "path";
|
|
11452
11620
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
11453
11621
|
var MODULE_TYPE = "module";
|
|
11454
11622
|
var INTEGRATION_TYPE = "aws_apigatewayv2_integration";
|
|
@@ -11530,7 +11698,7 @@ function walkTerraformFiles(root) {
|
|
|
11530
11698
|
return;
|
|
11531
11699
|
}
|
|
11532
11700
|
for (const entry of entries) {
|
|
11533
|
-
const p =
|
|
11701
|
+
const p = join42(dir, entry);
|
|
11534
11702
|
let st;
|
|
11535
11703
|
try {
|
|
11536
11704
|
st = statSync9(p);
|
|
@@ -11574,7 +11742,7 @@ function auditApiGatewayIntegrations(root) {
|
|
|
11574
11742
|
let rawModuleCount = 0;
|
|
11575
11743
|
let rawIntegrationCount = 0;
|
|
11576
11744
|
for (const file of files) {
|
|
11577
|
-
const text =
|
|
11745
|
+
const text = readFileSync31(file, "utf8");
|
|
11578
11746
|
rawModuleCount += countRawResourceDeclarations(text, MODULE_TYPE);
|
|
11579
11747
|
rawIntegrationCount += countRawResourceDeclarations(text, INTEGRATION_TYPE);
|
|
11580
11748
|
moduleBlocks.push(...findModuleBlocks(text, file));
|
|
@@ -11946,18 +12114,18 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
|
|
|
11946
12114
|
}
|
|
11947
12115
|
|
|
11948
12116
|
// src/lib/claim-invocation-parity.ts
|
|
11949
|
-
import { existsSync as
|
|
11950
|
-
import { join as
|
|
12117
|
+
import { existsSync as existsSync41, readFileSync as readFileSync32, readdirSync as readdirSync17 } from "fs";
|
|
12118
|
+
import { join as join43 } from "path";
|
|
11951
12119
|
function distributedAgentsDocs(root) {
|
|
11952
12120
|
const docs = [];
|
|
11953
|
-
const own =
|
|
11954
|
-
if (
|
|
11955
|
-
const skeletons =
|
|
11956
|
-
if (
|
|
12121
|
+
const own = join43(root, "AGENTS.md");
|
|
12122
|
+
if (existsSync41(own)) docs.push({ path: "AGENTS.md", text: readFileSync32(own, "utf8") });
|
|
12123
|
+
const skeletons = join43(root, "_skeletons");
|
|
12124
|
+
if (existsSync41(skeletons)) {
|
|
11957
12125
|
for (const name of readdirSync17(skeletons).sort()) {
|
|
11958
|
-
const abs =
|
|
11959
|
-
if (!
|
|
11960
|
-
docs.push({ path: `_skeletons/${name}/AGENTS.md`, text:
|
|
12126
|
+
const abs = join43(skeletons, name, "AGENTS.md");
|
|
12127
|
+
if (!existsSync41(abs)) continue;
|
|
12128
|
+
docs.push({ path: `_skeletons/${name}/AGENTS.md`, text: readFileSync32(abs, "utf8") });
|
|
11961
12129
|
}
|
|
11962
12130
|
}
|
|
11963
12131
|
return docs;
|
|
@@ -12089,12 +12257,12 @@ async function runClaimInvocationCheck() {
|
|
|
12089
12257
|
}
|
|
12090
12258
|
|
|
12091
12259
|
// src/scripts/check-codeql-suppression.ts
|
|
12092
|
-
import { existsSync as
|
|
12093
|
-
import { join as
|
|
12260
|
+
import { existsSync as existsSync42 } from "fs";
|
|
12261
|
+
import { join as join45, relative as relative8 } from "path";
|
|
12094
12262
|
|
|
12095
12263
|
// src/lib/codeql-suppression-guard.ts
|
|
12096
|
-
import { readdirSync as readdirSync18, readFileSync as
|
|
12097
|
-
import { join as
|
|
12264
|
+
import { readdirSync as readdirSync18, readFileSync as readFileSync33, statSync as statSync10 } from "fs";
|
|
12265
|
+
import { join as join44 } from "path";
|
|
12098
12266
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
12099
12267
|
".git",
|
|
12100
12268
|
".worktrees",
|
|
@@ -12125,7 +12293,7 @@ function walkSourceFiles(root) {
|
|
|
12125
12293
|
return;
|
|
12126
12294
|
}
|
|
12127
12295
|
for (const entry of entries) {
|
|
12128
|
-
const p =
|
|
12296
|
+
const p = join44(dir, entry);
|
|
12129
12297
|
let st;
|
|
12130
12298
|
try {
|
|
12131
12299
|
st = statSync10(p);
|
|
@@ -12151,7 +12319,7 @@ function countSourceFiles(root) {
|
|
|
12151
12319
|
function sweepCodeqlSuppressionComments(root) {
|
|
12152
12320
|
const hits = [];
|
|
12153
12321
|
for (const path of walkSourceFiles(root)) {
|
|
12154
|
-
const text =
|
|
12322
|
+
const text = readFileSync33(path, "utf8");
|
|
12155
12323
|
for (const line of findCodeqlSuppressionComments(text)) {
|
|
12156
12324
|
hits.push({ path, line, text: text.split("\n")[line - 1] ?? "" });
|
|
12157
12325
|
}
|
|
@@ -12162,8 +12330,8 @@ function sweepCodeqlSuppressionComments(root) {
|
|
|
12162
12330
|
// src/scripts/check-codeql-suppression.ts
|
|
12163
12331
|
async function runCodeqlSuppressionCheck() {
|
|
12164
12332
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
12165
|
-
const scanRoot =
|
|
12166
|
-
if (!
|
|
12333
|
+
const scanRoot = join45(root, "cli", "src");
|
|
12334
|
+
if (!existsSync42(scanRoot)) {
|
|
12167
12335
|
console.log(
|
|
12168
12336
|
"\u2014 codeql-suppression guard: skipped \u2014 no cli/src in this repo, so there is no CLI source to scan."
|
|
12169
12337
|
);
|
|
@@ -12187,12 +12355,12 @@ async function runCodeqlSuppressionCheck() {
|
|
|
12187
12355
|
}
|
|
12188
12356
|
|
|
12189
12357
|
// src/scripts/check-cognito-invite-template.ts
|
|
12190
|
-
import { existsSync as
|
|
12191
|
-
import { join as
|
|
12358
|
+
import { existsSync as existsSync43 } from "fs";
|
|
12359
|
+
import { join as join47 } from "path";
|
|
12192
12360
|
|
|
12193
12361
|
// src/lib/cognito-invite-template-guard.ts
|
|
12194
|
-
import { readdirSync as readdirSync19, readFileSync as
|
|
12195
|
-
import { join as
|
|
12362
|
+
import { readdirSync as readdirSync19, readFileSync as readFileSync34, statSync as statSync11 } from "fs";
|
|
12363
|
+
import { join as join46 } from "path";
|
|
12196
12364
|
var REQUIRED_INVITE_MEMBERS = ["email_subject", "email_message", "sms_message"];
|
|
12197
12365
|
var REQUIRED_INVITE_PLACEHOLDERS = ["{username}", "{####}"];
|
|
12198
12366
|
var PLACEHOLDER_MEMBERS = ["email_message", "sms_message"];
|
|
@@ -12276,7 +12444,7 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
12276
12444
|
for (const entry of entries) {
|
|
12277
12445
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees" || entry === ".venv")
|
|
12278
12446
|
continue;
|
|
12279
|
-
const full =
|
|
12447
|
+
const full = join46(dir, entry);
|
|
12280
12448
|
const rel = `${relative11}/${entry}`;
|
|
12281
12449
|
let isDir;
|
|
12282
12450
|
try {
|
|
@@ -12291,12 +12459,12 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
12291
12459
|
}
|
|
12292
12460
|
}
|
|
12293
12461
|
};
|
|
12294
|
-
walk2(
|
|
12462
|
+
walk2(join46(repoRoot, "modules"), "modules");
|
|
12295
12463
|
return found.sort();
|
|
12296
12464
|
}
|
|
12297
12465
|
function checkCognitoInviteTemplates(repoRoot) {
|
|
12298
12466
|
return findModuleTerraformFiles(repoRoot).flatMap(
|
|
12299
|
-
(file) => checkInviteTemplateSource(file,
|
|
12467
|
+
(file) => checkInviteTemplateSource(file, readFileSync34(join46(repoRoot, file), "utf8"))
|
|
12300
12468
|
);
|
|
12301
12469
|
}
|
|
12302
12470
|
|
|
@@ -12306,7 +12474,7 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
12306
12474
|
const files = findModuleTerraformFiles(root);
|
|
12307
12475
|
console.log(`audited ${files.length} .tf file(s) under modules/ under ${root}`);
|
|
12308
12476
|
if (files.length === 0) {
|
|
12309
|
-
if (!
|
|
12477
|
+
if (!existsSync43(join47(root, "modules"))) {
|
|
12310
12478
|
console.log(
|
|
12311
12479
|
`\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
12480
|
);
|
|
@@ -12330,11 +12498,11 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
12330
12498
|
}
|
|
12331
12499
|
|
|
12332
12500
|
// src/scripts/check-core-direct-paths.ts
|
|
12333
|
-
import { join as
|
|
12501
|
+
import { join as join49 } from "path";
|
|
12334
12502
|
|
|
12335
12503
|
// src/lib/core-direct-paths-audit.ts
|
|
12336
|
-
import { existsSync as
|
|
12337
|
-
import { join as
|
|
12504
|
+
import { existsSync as existsSync44, readFileSync as readFileSync35, readdirSync as readdirSync20, statSync as statSync12 } from "fs";
|
|
12505
|
+
import { join as join48 } from "path";
|
|
12338
12506
|
var EXTERNAL_BASE_IDENTIFIERS = ["CORE_API_URL"];
|
|
12339
12507
|
var API_ROUTE_PREFIX = "/api/v1";
|
|
12340
12508
|
var TEST_FILE_SUFFIXES = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
|
|
@@ -12498,7 +12666,7 @@ function walkFiles(root, accept, skipDir) {
|
|
|
12498
12666
|
return;
|
|
12499
12667
|
}
|
|
12500
12668
|
for (const entry of entries) {
|
|
12501
|
-
const p =
|
|
12669
|
+
const p = join48(dir, entry);
|
|
12502
12670
|
let st;
|
|
12503
12671
|
try {
|
|
12504
12672
|
st = statSync12(p);
|
|
@@ -12528,7 +12696,7 @@ function auditFrontendExtraction(frontendSrcDir, externalBases = EXTERNAL_BASE_I
|
|
|
12528
12696
|
const extracted = [];
|
|
12529
12697
|
let rawTotal = 0;
|
|
12530
12698
|
for (const file of files) {
|
|
12531
|
-
const text =
|
|
12699
|
+
const text = readFileSync35(file, "utf8");
|
|
12532
12700
|
rawTotal += countRawExternalOccurrences(text, externalBases);
|
|
12533
12701
|
extracted.push(...extractCoreDirectPaths(text, file, externalBases));
|
|
12534
12702
|
}
|
|
@@ -12577,7 +12745,7 @@ function auditCoreRouteExtraction(apiSrcDir) {
|
|
|
12577
12745
|
const prefixSet = /* @__PURE__ */ new Set();
|
|
12578
12746
|
let rawApiRouterCount = 0;
|
|
12579
12747
|
for (const file of files) {
|
|
12580
|
-
const text =
|
|
12748
|
+
const text = readFileSync35(file, "utf8");
|
|
12581
12749
|
const extraction = extractCoreRoutePrefixes(text);
|
|
12582
12750
|
rawApiRouterCount += extraction.rawApiRouterCount;
|
|
12583
12751
|
for (const p of extraction.prefixes) prefixSet.add(normalizePrefix(p));
|
|
@@ -12593,10 +12761,10 @@ function pathMatchesAnyCorePrefix(normalized, corePrefixes, apiRoutePrefix = API
|
|
|
12593
12761
|
}
|
|
12594
12762
|
function resolveSiblingCoreSrc(params) {
|
|
12595
12763
|
const { estateDir, sibling } = params;
|
|
12596
|
-
const configPath =
|
|
12764
|
+
const configPath = join48(estateDir, sibling, "biffo.sibling.json");
|
|
12597
12765
|
let raw;
|
|
12598
12766
|
try {
|
|
12599
|
-
raw =
|
|
12767
|
+
raw = readFileSync35(configPath, "utf8");
|
|
12600
12768
|
} catch (err) {
|
|
12601
12769
|
throw new Error(
|
|
12602
12770
|
`cannot resolve ${sibling}'s core: ${configPath} does not exist or is unreadable (${err.message}) -- refusing to guess which core serves this sibling.`
|
|
@@ -12616,8 +12784,8 @@ function resolveSiblingCoreSrc(params) {
|
|
|
12616
12784
|
`cannot resolve ${sibling}'s core: ${configPath} has no non-empty "core_project" field.`
|
|
12617
12785
|
);
|
|
12618
12786
|
}
|
|
12619
|
-
const coreApiSrcDir =
|
|
12620
|
-
if (!
|
|
12787
|
+
const coreApiSrcDir = join48(estateDir, coreProject, "services", "api", "src");
|
|
12788
|
+
if (!existsSync44(coreApiSrcDir)) {
|
|
12621
12789
|
throw new Error(
|
|
12622
12790
|
`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
12791
|
);
|
|
@@ -12670,7 +12838,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
12670
12838
|
}
|
|
12671
12839
|
}
|
|
12672
12840
|
const sibling = opts.sibling ?? "sibling-template (self-check)";
|
|
12673
|
-
const frontendSrcDir = opts.frontendSrc ??
|
|
12841
|
+
const frontendSrcDir = opts.frontendSrc ?? join49(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
|
|
12674
12842
|
let coreApiSrcDir;
|
|
12675
12843
|
let coreProject = null;
|
|
12676
12844
|
if (opts.coreSrc) {
|
|
@@ -12686,7 +12854,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
12686
12854
|
coreApiSrcDir = resolution.coreApiSrcDir;
|
|
12687
12855
|
coreProject = resolution.coreProject;
|
|
12688
12856
|
} else {
|
|
12689
|
-
coreApiSrcDir =
|
|
12857
|
+
coreApiSrcDir = join49(root, "services", "api", "src");
|
|
12690
12858
|
}
|
|
12691
12859
|
const report = auditSiblingCoreDirectPaths({ sibling, frontendSrcDir, coreApiSrcDir });
|
|
12692
12860
|
console.log(
|
|
@@ -12813,8 +12981,8 @@ async function runOwnershipCheck(argv) {
|
|
|
12813
12981
|
const { stdout } = await execa("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
12814
12982
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
12815
12983
|
if (messageFile) {
|
|
12816
|
-
const { readFileSync:
|
|
12817
|
-
if (
|
|
12984
|
+
const { readFileSync: readFileSync49, existsSync: existsSync59 } = await import("fs");
|
|
12985
|
+
if (existsSync59(messageFile)) commitMessage = readFileSync49(messageFile, "utf8");
|
|
12818
12986
|
}
|
|
12819
12987
|
} else {
|
|
12820
12988
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -12937,21 +13105,21 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
12937
13105
|
}
|
|
12938
13106
|
|
|
12939
13107
|
// src/scripts/check-distribution-inventory.ts
|
|
12940
|
-
import { existsSync as
|
|
12941
|
-
import { join as
|
|
13108
|
+
import { existsSync as existsSync46 } from "fs";
|
|
13109
|
+
import { join as join51 } from "path";
|
|
12942
13110
|
|
|
12943
13111
|
// src/lib/distribution-inventory.ts
|
|
12944
|
-
import { existsSync as
|
|
12945
|
-
import { join as
|
|
13112
|
+
import { existsSync as existsSync45, readFileSync as readFileSync36 } from "fs";
|
|
13113
|
+
import { join as join50 } from "path";
|
|
12946
13114
|
var INVENTORY_FILENAME = "distribution-inventory.json";
|
|
12947
13115
|
function loadDistributionInventory(root) {
|
|
12948
|
-
const path =
|
|
12949
|
-
if (!
|
|
13116
|
+
const path = join50(root, INVENTORY_FILENAME);
|
|
13117
|
+
if (!existsSync45(path)) {
|
|
12950
13118
|
throw new Error(
|
|
12951
13119
|
`${INVENTORY_FILENAME} not found at ${root} -- expected it beside core-manifest.json`
|
|
12952
13120
|
);
|
|
12953
13121
|
}
|
|
12954
|
-
return JSON.parse(
|
|
13122
|
+
return JSON.parse(readFileSync36(path, "utf8"));
|
|
12955
13123
|
}
|
|
12956
13124
|
function validateInventory(inventory) {
|
|
12957
13125
|
const violations = [];
|
|
@@ -13111,7 +13279,7 @@ async function runDistributionInventoryCheck(root) {
|
|
|
13111
13279
|
);
|
|
13112
13280
|
return;
|
|
13113
13281
|
}
|
|
13114
|
-
if (!
|
|
13282
|
+
if (!existsSync46(join51(repoRoot, INVENTORY_FILENAME))) {
|
|
13115
13283
|
console.log(
|
|
13116
13284
|
`\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
13285
|
);
|
|
@@ -13137,7 +13305,7 @@ async function runDistributionInventoryCheck(root) {
|
|
|
13137
13305
|
}
|
|
13138
13306
|
|
|
13139
13307
|
// src/scripts/check-distribution-remote-state.ts
|
|
13140
|
-
import { join as
|
|
13308
|
+
import { join as join52 } from "path";
|
|
13141
13309
|
async function ghExecCommand(file, args) {
|
|
13142
13310
|
const result = await execa(file, args, { reject: false });
|
|
13143
13311
|
return { stdout: String(result.stdout ?? ""), exitCode: result.exitCode ?? null };
|
|
@@ -13152,7 +13320,7 @@ async function runDistributionRemoteStateCheck(root) {
|
|
|
13152
13320
|
}
|
|
13153
13321
|
}
|
|
13154
13322
|
console.log(
|
|
13155
|
-
`distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${
|
|
13323
|
+
`distribution-remote-state: examined ${assertions.length} remote content assertion(s) declared across ${inventory.entries.length} inventory entries (${join52(repoRoot, "distribution-inventory.json")})`
|
|
13156
13324
|
);
|
|
13157
13325
|
if (assertions.length === 0) {
|
|
13158
13326
|
console.log("\u2713 distribution-remote-state: nothing declared to check");
|
|
@@ -13197,8 +13365,8 @@ ${assertion.ref}`;
|
|
|
13197
13365
|
}
|
|
13198
13366
|
|
|
13199
13367
|
// src/lib/eventbridge-log-permission-guard.ts
|
|
13200
|
-
import { readFileSync as
|
|
13201
|
-
import { join as
|
|
13368
|
+
import { readFileSync as readFileSync37, readdirSync as readdirSync21, statSync as statSync13 } from "fs";
|
|
13369
|
+
import { join as join53 } from "path";
|
|
13202
13370
|
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
13203
13371
|
var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
|
|
13204
13372
|
var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
|
|
@@ -13275,7 +13443,7 @@ function walkTerraformFiles2(root) {
|
|
|
13275
13443
|
return;
|
|
13276
13444
|
}
|
|
13277
13445
|
for (const entry of entries) {
|
|
13278
|
-
const p =
|
|
13446
|
+
const p = join53(dir, entry);
|
|
13279
13447
|
let st;
|
|
13280
13448
|
try {
|
|
13281
13449
|
st = statSync13(p);
|
|
@@ -13318,7 +13486,7 @@ function auditEventBridgeLogPermissions(root) {
|
|
|
13318
13486
|
let rawEventTargetCount = 0;
|
|
13319
13487
|
let rawLogPolicyCount = 0;
|
|
13320
13488
|
for (const file of files) {
|
|
13321
|
-
const text =
|
|
13489
|
+
const text = readFileSync37(file, "utf8");
|
|
13322
13490
|
rawEventTargetCount += countRawResourceDeclarations2(text, EVENT_TARGET_TYPE);
|
|
13323
13491
|
rawLogPolicyCount += countRawResourceDeclarations2(text, LOG_RESOURCE_POLICY_TYPE);
|
|
13324
13492
|
eventTargetBlocks.push(...findResourceBlocks2(text, file, EVENT_TARGET_TYPE));
|
|
@@ -13415,8 +13583,8 @@ async function runEventBridgeLogPermissionCheck() {
|
|
|
13415
13583
|
}
|
|
13416
13584
|
|
|
13417
13585
|
// src/scripts/check-instance-adoption.ts
|
|
13418
|
-
import { existsSync as
|
|
13419
|
-
import { join as
|
|
13586
|
+
import { existsSync as existsSync47 } from "fs";
|
|
13587
|
+
import { join as join54 } from "path";
|
|
13420
13588
|
async function runInstanceAdoptionCheck(opts = {}) {
|
|
13421
13589
|
if (!opts.instanceDir) {
|
|
13422
13590
|
console.error(
|
|
@@ -13424,7 +13592,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
13424
13592
|
);
|
|
13425
13593
|
process.exit(2);
|
|
13426
13594
|
}
|
|
13427
|
-
if (!
|
|
13595
|
+
if (!existsSync47(opts.instanceDir)) {
|
|
13428
13596
|
console.error(
|
|
13429
13597
|
`\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
13598
|
);
|
|
@@ -13432,7 +13600,7 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
13432
13600
|
}
|
|
13433
13601
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13434
13602
|
const theirsDir = opts.theirsDir ?? root;
|
|
13435
|
-
const instanceLabel = opts.instance ??
|
|
13603
|
+
const instanceLabel = opts.instance ?? join54(opts.instanceDir).split("/").filter(Boolean).pop();
|
|
13436
13604
|
const report = checkInstanceAdoption(theirsDir, opts.instanceDir);
|
|
13437
13605
|
console.log(
|
|
13438
13606
|
`examined ${report.examinedInstances} instance (${instanceLabel}) against ${report.registeredPairs} registered pair(s), ${report.applicablePairs} applicable to this instance, against template tree ${theirsDir}`
|
|
@@ -13454,12 +13622,12 @@ async function runInstanceAdoptionCheck(opts = {}) {
|
|
|
13454
13622
|
}
|
|
13455
13623
|
|
|
13456
13624
|
// src/lib/lambda-output-guard.ts
|
|
13457
|
-
import { readFileSync as
|
|
13458
|
-
import { join as
|
|
13625
|
+
import { readFileSync as readFileSync39 } from "fs";
|
|
13626
|
+
import { join as join56 } from "path";
|
|
13459
13627
|
|
|
13460
13628
|
// src/lib/terraform-input-guard.ts
|
|
13461
|
-
import { existsSync as
|
|
13462
|
-
import { join as
|
|
13629
|
+
import { existsSync as existsSync48, readdirSync as readdirSync22, readFileSync as readFileSync38, statSync as statSync14 } from "fs";
|
|
13630
|
+
import { join as join55 } from "path";
|
|
13463
13631
|
var GUARDED_SUBCOMMANDS = [
|
|
13464
13632
|
"init",
|
|
13465
13633
|
"plan",
|
|
@@ -13473,7 +13641,7 @@ function stripComments2(source) {
|
|
|
13473
13641
|
return source.split("\n").map((line) => line.replace(/(^|\s)#.*$/, "$1")).join("\n");
|
|
13474
13642
|
}
|
|
13475
13643
|
function vendoredPluginServiceDirs(repoRoot) {
|
|
13476
|
-
const servicesDir =
|
|
13644
|
+
const servicesDir = join55(repoRoot, "services");
|
|
13477
13645
|
const result = /* @__PURE__ */ new Set();
|
|
13478
13646
|
let entries;
|
|
13479
13647
|
try {
|
|
@@ -13482,9 +13650,9 @@ function vendoredPluginServiceDirs(repoRoot) {
|
|
|
13482
13650
|
return result;
|
|
13483
13651
|
}
|
|
13484
13652
|
for (const entry of entries) {
|
|
13485
|
-
const full =
|
|
13486
|
-
if (!
|
|
13487
|
-
if (
|
|
13653
|
+
const full = join55(servicesDir, entry);
|
|
13654
|
+
if (!existsSync48(full) || !statSync14(full).isDirectory()) continue;
|
|
13655
|
+
if (existsSync48(join55(full, "biffo.plugin.json"))) {
|
|
13488
13656
|
result.add(entry);
|
|
13489
13657
|
}
|
|
13490
13658
|
}
|
|
@@ -13506,7 +13674,7 @@ function findWorkflowFiles(repoRoot) {
|
|
|
13506
13674
|
if (entry === ".github" && relative11.startsWith("services/") && vendoredPluginDirs.has(relative11.slice("services/".length))) {
|
|
13507
13675
|
continue;
|
|
13508
13676
|
}
|
|
13509
|
-
const full =
|
|
13677
|
+
const full = join55(dir, entry);
|
|
13510
13678
|
const rel = relative11 ? `${relative11}/${entry}` : entry;
|
|
13511
13679
|
let isDir;
|
|
13512
13680
|
try {
|
|
@@ -13556,7 +13724,7 @@ function checkWorkflowSource(file, rawSource) {
|
|
|
13556
13724
|
}
|
|
13557
13725
|
function checkTerraformInput(repoRoot) {
|
|
13558
13726
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
13559
|
-
(file) => checkWorkflowSource(file,
|
|
13727
|
+
(file) => checkWorkflowSource(file, readFileSync38(join55(repoRoot, file), "utf8"))
|
|
13560
13728
|
);
|
|
13561
13729
|
}
|
|
13562
13730
|
|
|
@@ -13614,7 +13782,7 @@ function checkWorkflowSource2(file, rawSource) {
|
|
|
13614
13782
|
}
|
|
13615
13783
|
function checkLambdaOutput(repoRoot) {
|
|
13616
13784
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
13617
|
-
(file) => checkWorkflowSource2(file,
|
|
13785
|
+
(file) => checkWorkflowSource2(file, readFileSync39(join56(repoRoot, file), "utf8"))
|
|
13618
13786
|
);
|
|
13619
13787
|
}
|
|
13620
13788
|
|
|
@@ -13642,8 +13810,8 @@ async function runLambdaOutputCheck() {
|
|
|
13642
13810
|
}
|
|
13643
13811
|
|
|
13644
13812
|
// src/scripts/check-migration-body-change.ts
|
|
13645
|
-
import { existsSync as
|
|
13646
|
-
import { join as
|
|
13813
|
+
import { existsSync as existsSync49 } from "fs";
|
|
13814
|
+
import { join as join57 } from "path";
|
|
13647
13815
|
|
|
13648
13816
|
// src/lib/migration-body-change-guard.ts
|
|
13649
13817
|
function checkMigrationBodyChangeMarkers(diffs) {
|
|
@@ -13725,7 +13893,7 @@ async function runMigrationBodyChangeCheck(argv) {
|
|
|
13725
13893
|
);
|
|
13726
13894
|
return;
|
|
13727
13895
|
}
|
|
13728
|
-
if (!
|
|
13896
|
+
if (!existsSync49(join57(root, MIGRATIONS_VERSIONS_DIR))) {
|
|
13729
13897
|
console.log(`\u2713 migration body-change guard: no ${MIGRATIONS_VERSIONS_DIR} in this repo.`);
|
|
13730
13898
|
return;
|
|
13731
13899
|
}
|
|
@@ -13831,8 +13999,8 @@ ${BOLD2}What to do${OFF2}
|
|
|
13831
13999
|
}
|
|
13832
14000
|
|
|
13833
14001
|
// src/scripts/check-orphan-ratchet.ts
|
|
13834
|
-
import { existsSync as
|
|
13835
|
-
import { basename as basename3, join as
|
|
14002
|
+
import { existsSync as existsSync50 } from "fs";
|
|
14003
|
+
import { basename as basename3, join as join58 } from "path";
|
|
13836
14004
|
function reportOrphan(entry, manifest) {
|
|
13837
14005
|
console.error(` ${entry.path}`);
|
|
13838
14006
|
const { templateOwnedMatch, nearestUserOwnedEntries } = explainOwnership(entry.path, manifest);
|
|
@@ -13854,7 +14022,7 @@ function reportOrphan(entry, manifest) {
|
|
|
13854
14022
|
}
|
|
13855
14023
|
}
|
|
13856
14024
|
async function runOrphanRatchetCheck(opts = {}) {
|
|
13857
|
-
if (opts.instanceDir !== void 0 && !
|
|
14025
|
+
if (opts.instanceDir !== void 0 && !existsSync50(opts.instanceDir)) {
|
|
13858
14026
|
console.error(
|
|
13859
14027
|
`\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
14028
|
);
|
|
@@ -13871,7 +14039,7 @@ async function runOrphanRatchetCheck(opts = {}) {
|
|
|
13871
14039
|
"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
14040
|
);
|
|
13873
14041
|
}
|
|
13874
|
-
if (!
|
|
14042
|
+
if (!existsSync50(join58(theirsDir, CORE_MANIFEST_FILE))) {
|
|
13875
14043
|
console.log(
|
|
13876
14044
|
`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
14045
|
);
|
|
@@ -13902,8 +14070,8 @@ baseline was ${String(ratchet.baseline)}, now ${String(ratchet.count)}. See biff
|
|
|
13902
14070
|
}
|
|
13903
14071
|
|
|
13904
14072
|
// src/lib/ownership-header-claim-guard.ts
|
|
13905
|
-
import { readFileSync as
|
|
13906
|
-
import { join as
|
|
14073
|
+
import { readFileSync as readFileSync40 } from "fs";
|
|
14074
|
+
import { join as join59 } from "path";
|
|
13907
14075
|
var OWNERSHIP_HEADER_SWEEP_DIRS = [
|
|
13908
14076
|
"scripts",
|
|
13909
14077
|
".githooks",
|
|
@@ -14112,7 +14280,7 @@ function sweepOwnershipHeaderClaims(root, options = {}) {
|
|
|
14112
14280
|
for (const rel of files) {
|
|
14113
14281
|
let content;
|
|
14114
14282
|
try {
|
|
14115
|
-
content =
|
|
14283
|
+
content = readFileSync40(join59(root, rel), "utf8");
|
|
14116
14284
|
} catch {
|
|
14117
14285
|
continue;
|
|
14118
14286
|
}
|
|
@@ -14187,8 +14355,8 @@ async function runOwnershipHeaderClaimCheck() {
|
|
|
14187
14355
|
}
|
|
14188
14356
|
|
|
14189
14357
|
// src/scripts/check-pipe-trap.ts
|
|
14190
|
-
import { readFileSync as
|
|
14191
|
-
import { join as
|
|
14358
|
+
import { readFileSync as readFileSync41, readdirSync as readdirSync23 } from "fs";
|
|
14359
|
+
import { join as join60, relative as relative9 } from "path";
|
|
14192
14360
|
|
|
14193
14361
|
// src/lib/pipe-trap-guard.ts
|
|
14194
14362
|
var STATUS_BEARING = [
|
|
@@ -14284,7 +14452,7 @@ function findPipeTraps(source) {
|
|
|
14284
14452
|
function shellFiles(root) {
|
|
14285
14453
|
const out = [];
|
|
14286
14454
|
for (const dir of ["scripts", ".githooks"]) {
|
|
14287
|
-
const full =
|
|
14455
|
+
const full = join60(root, dir);
|
|
14288
14456
|
let entries;
|
|
14289
14457
|
try {
|
|
14290
14458
|
entries = readdirSync23(full, { withFileTypes: true });
|
|
@@ -14294,7 +14462,7 @@ function shellFiles(root) {
|
|
|
14294
14462
|
for (const entry of entries) {
|
|
14295
14463
|
if (!entry.isFile()) continue;
|
|
14296
14464
|
if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
|
|
14297
|
-
out.push(
|
|
14465
|
+
out.push(join60(full, entry.name));
|
|
14298
14466
|
}
|
|
14299
14467
|
}
|
|
14300
14468
|
return out;
|
|
@@ -14310,7 +14478,7 @@ async function runPipeTrapCheck() {
|
|
|
14310
14478
|
process.exit(1);
|
|
14311
14479
|
}
|
|
14312
14480
|
const findings = files.flatMap(
|
|
14313
|
-
(file) => findPipeTraps(
|
|
14481
|
+
(file) => findPipeTraps(readFileSync41(file, "utf8")).map(
|
|
14314
14482
|
(t) => `${relative9(root, file)}:${t.line} ${t.text}
|
|
14315
14483
|
${t.reason}`
|
|
14316
14484
|
)
|
|
@@ -14327,8 +14495,8 @@ async function runPipeTrapCheck() {
|
|
|
14327
14495
|
}
|
|
14328
14496
|
|
|
14329
14497
|
// src/lib/plugin-allowlist-convention.ts
|
|
14330
|
-
import { readFileSync as
|
|
14331
|
-
import { join as
|
|
14498
|
+
import { readFileSync as readFileSync42 } from "fs";
|
|
14499
|
+
import { join as join61 } from "path";
|
|
14332
14500
|
var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
|
|
14333
14501
|
var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
|
|
14334
14502
|
var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
|
|
@@ -14339,7 +14507,7 @@ var PLUGIN = "<plugin>";
|
|
|
14339
14507
|
var ACCOUNT = "<account>";
|
|
14340
14508
|
function read(repoRoot, relative11) {
|
|
14341
14509
|
try {
|
|
14342
|
-
return
|
|
14510
|
+
return readFileSync42(join61(repoRoot, relative11), "utf8");
|
|
14343
14511
|
} catch {
|
|
14344
14512
|
throw new Error(`plugin-allowlist drift guard: cannot read ${relative11}`);
|
|
14345
14513
|
}
|
|
@@ -14467,32 +14635,32 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
14467
14635
|
}
|
|
14468
14636
|
|
|
14469
14637
|
// src/scripts/check-plugin-collisions.ts
|
|
14470
|
-
import { existsSync as
|
|
14471
|
-
import { join as
|
|
14638
|
+
import { existsSync as existsSync52 } from "fs";
|
|
14639
|
+
import { join as join63 } from "path";
|
|
14472
14640
|
|
|
14473
14641
|
// src/lib/plugin-collision-guard.ts
|
|
14474
|
-
import { existsSync as
|
|
14475
|
-
import { join as
|
|
14642
|
+
import { existsSync as existsSync51, readdirSync as readdirSync24, statSync as statSync15 } from "fs";
|
|
14643
|
+
import { join as join62 } from "path";
|
|
14476
14644
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
14477
14645
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
14478
14646
|
function subdirectories(dir) {
|
|
14479
|
-
if (!
|
|
14647
|
+
if (!existsSync51(dir)) return [];
|
|
14480
14648
|
return readdirSync24(dir).filter((entry) => {
|
|
14481
14649
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
14482
14650
|
try {
|
|
14483
|
-
return statSync15(
|
|
14651
|
+
return statSync15(join62(dir, entry)).isDirectory();
|
|
14484
14652
|
} catch {
|
|
14485
14653
|
return false;
|
|
14486
14654
|
}
|
|
14487
14655
|
});
|
|
14488
14656
|
}
|
|
14489
14657
|
function regularPackagesOf(pluginDir2) {
|
|
14490
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
14658
|
+
return subdirectories(pluginDir2).filter((name) => existsSync51(join62(pluginDir2, name, "__init__.py"))).sort();
|
|
14491
14659
|
}
|
|
14492
14660
|
function bareTestModulesOf(pluginDir2) {
|
|
14493
|
-
const testsDir =
|
|
14494
|
-
if (!
|
|
14495
|
-
if (
|
|
14661
|
+
const testsDir = join62(pluginDir2, "tests");
|
|
14662
|
+
if (!existsSync51(testsDir)) return [];
|
|
14663
|
+
if (existsSync51(join62(testsDir, "__init__.py"))) return [];
|
|
14496
14664
|
return readdirSync24(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
14497
14665
|
}
|
|
14498
14666
|
function findCollisions(servicesDir, pluginDirs) {
|
|
@@ -14501,7 +14669,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
14501
14669
|
const gather = (kind, namesOf) => {
|
|
14502
14670
|
const claims = /* @__PURE__ */ new Map();
|
|
14503
14671
|
for (const plugin of plugins) {
|
|
14504
|
-
for (const name of namesOf(
|
|
14672
|
+
for (const name of namesOf(join62(servicesDir, plugin))) {
|
|
14505
14673
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
14506
14674
|
}
|
|
14507
14675
|
}
|
|
@@ -14539,8 +14707,8 @@ function formatCollisions(collisions) {
|
|
|
14539
14707
|
// src/scripts/check-plugin-collisions.ts
|
|
14540
14708
|
async function runPluginCollisionCheck() {
|
|
14541
14709
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
14542
|
-
const servicesDir =
|
|
14543
|
-
if (!
|
|
14710
|
+
const servicesDir = join63(root, "services");
|
|
14711
|
+
if (!existsSync52(servicesDir)) {
|
|
14544
14712
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
14545
14713
|
return;
|
|
14546
14714
|
}
|
|
@@ -14557,8 +14725,8 @@ async function runPluginCollisionCheck() {
|
|
|
14557
14725
|
}
|
|
14558
14726
|
|
|
14559
14727
|
// src/lib/plugin-terraform-guard.ts
|
|
14560
|
-
import { existsSync as
|
|
14561
|
-
import { dirname as dirname10, join as
|
|
14728
|
+
import { existsSync as existsSync53, readFileSync as readFileSync43, readdirSync as readdirSync25 } from "fs";
|
|
14729
|
+
import { dirname as dirname10, join as join64, relative as relative10, sep as sep4 } from "path";
|
|
14562
14730
|
var SKIP_DIRS4 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
14563
14731
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
14564
14732
|
function findPluginManifests(root) {
|
|
@@ -14573,9 +14741,9 @@ function findPluginManifests(root) {
|
|
|
14573
14741
|
for (const entry of entries) {
|
|
14574
14742
|
if (entry.isDirectory()) {
|
|
14575
14743
|
if (SKIP_DIRS4.has(entry.name)) continue;
|
|
14576
|
-
walk2(
|
|
14744
|
+
walk2(join64(dir, entry.name));
|
|
14577
14745
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
14578
|
-
found.push(relative10(root,
|
|
14746
|
+
found.push(relative10(root, join64(dir, entry.name)).split(sep4).join("/"));
|
|
14579
14747
|
}
|
|
14580
14748
|
}
|
|
14581
14749
|
};
|
|
@@ -14585,7 +14753,7 @@ function findPluginManifests(root) {
|
|
|
14585
14753
|
function readSubscriptions(absManifestPath) {
|
|
14586
14754
|
let parsed;
|
|
14587
14755
|
try {
|
|
14588
|
-
parsed = JSON.parse(
|
|
14756
|
+
parsed = JSON.parse(readFileSync43(absManifestPath, "utf8"));
|
|
14589
14757
|
} catch {
|
|
14590
14758
|
return null;
|
|
14591
14759
|
}
|
|
@@ -14600,14 +14768,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
14600
14768
|
}
|
|
14601
14769
|
function checkPluginTerraform(root) {
|
|
14602
14770
|
const violations = [];
|
|
14603
|
-
const coreManifest =
|
|
14771
|
+
const coreManifest = existsSync53(join64(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
14604
14772
|
for (const manifest of findPluginManifests(root)) {
|
|
14605
14773
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
14606
|
-
const absManifest =
|
|
14774
|
+
const absManifest = join64(root, manifest);
|
|
14607
14775
|
const subscriptions = readSubscriptions(absManifest);
|
|
14608
14776
|
if (subscriptions === null) continue;
|
|
14609
14777
|
const pluginDir2 = dirname10(absManifest);
|
|
14610
|
-
if (
|
|
14778
|
+
if (existsSync53(join64(pluginDir2, "terraform"))) continue;
|
|
14611
14779
|
const relPluginDir = relative10(root, pluginDir2).split(sep4).join("/");
|
|
14612
14780
|
violations.push({
|
|
14613
14781
|
manifest,
|
|
@@ -14638,12 +14806,12 @@ async function runPluginTerraformCheck() {
|
|
|
14638
14806
|
}
|
|
14639
14807
|
|
|
14640
14808
|
// src/scripts/check-plugin-tool-supply.ts
|
|
14641
|
-
import { existsSync as
|
|
14642
|
-
import { join as
|
|
14809
|
+
import { existsSync as existsSync55 } from "fs";
|
|
14810
|
+
import { join as join66 } from "path";
|
|
14643
14811
|
|
|
14644
14812
|
// src/lib/plugin-tool-supply-audit.ts
|
|
14645
|
-
import { existsSync as
|
|
14646
|
-
import { join as
|
|
14813
|
+
import { existsSync as existsSync54, readFileSync as readFileSync44, readdirSync as readdirSync26, statSync as statSync16 } from "fs";
|
|
14814
|
+
import { join as join65 } from "path";
|
|
14647
14815
|
|
|
14648
14816
|
// src/lib/openrouter-model-snapshot.ts
|
|
14649
14817
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -15060,7 +15228,7 @@ function listDirs(root) {
|
|
|
15060
15228
|
}
|
|
15061
15229
|
return entries.filter((e) => {
|
|
15062
15230
|
try {
|
|
15063
|
-
return statSync16(
|
|
15231
|
+
return statSync16(join65(root, e)).isDirectory();
|
|
15064
15232
|
} catch {
|
|
15065
15233
|
return false;
|
|
15066
15234
|
}
|
|
@@ -15076,7 +15244,7 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
15076
15244
|
return;
|
|
15077
15245
|
}
|
|
15078
15246
|
for (const entry of entries) {
|
|
15079
|
-
const p =
|
|
15247
|
+
const p = join65(dir, entry);
|
|
15080
15248
|
let st;
|
|
15081
15249
|
try {
|
|
15082
15250
|
st = statSync16(p);
|
|
@@ -15102,14 +15270,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
15102
15270
|
);
|
|
15103
15271
|
}
|
|
15104
15272
|
function pluginTerraformFiles(pluginDir2) {
|
|
15105
|
-
const tfDir =
|
|
15273
|
+
const tfDir = join65(pluginDir2, "terraform");
|
|
15106
15274
|
let entries;
|
|
15107
15275
|
try {
|
|
15108
15276
|
entries = readdirSync26(tfDir);
|
|
15109
15277
|
} catch {
|
|
15110
15278
|
return [];
|
|
15111
15279
|
}
|
|
15112
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
15280
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join65(tfDir, e)).sort();
|
|
15113
15281
|
}
|
|
15114
15282
|
function extractManifestTools(manifestText) {
|
|
15115
15283
|
let parsed;
|
|
@@ -15361,8 +15529,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
15361
15529
|
function normalizeModelId(id) {
|
|
15362
15530
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
15363
15531
|
}
|
|
15364
|
-
var CONFIG_PY_PATH =
|
|
15365
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
15532
|
+
var CONFIG_PY_PATH = join65("services", "api", "src", "api", "config.py");
|
|
15533
|
+
var ORCHESTRATION_SCHEMA_PATH = join65(
|
|
15366
15534
|
"services",
|
|
15367
15535
|
"api",
|
|
15368
15536
|
"src",
|
|
@@ -15374,10 +15542,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
15374
15542
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
15375
15543
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
15376
15544
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
15377
|
-
const configPath =
|
|
15378
|
-
const orchestrationPath =
|
|
15379
|
-
const configMissing = !
|
|
15380
|
-
const orchestrationSchemaMissing = !
|
|
15545
|
+
const configPath = join65(repoRoot, CONFIG_PY_PATH);
|
|
15546
|
+
const orchestrationPath = join65(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
15547
|
+
const configMissing = !existsSync54(configPath);
|
|
15548
|
+
const orchestrationSchemaMissing = !existsSync54(orchestrationPath);
|
|
15381
15549
|
const knownSet = new Set(knownModelIds);
|
|
15382
15550
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
15383
15551
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -15395,13 +15563,13 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
15395
15563
|
};
|
|
15396
15564
|
let settingsBlind = false;
|
|
15397
15565
|
if (!configMissing) {
|
|
15398
|
-
const settingsFields = extractSettingsModelFields(
|
|
15566
|
+
const settingsFields = extractSettingsModelFields(readFileSync44(configPath, "utf8"));
|
|
15399
15567
|
if (settingsFields.length === 0) settingsBlind = true;
|
|
15400
15568
|
for (const { field, value } of settingsFields) record(`${CONFIG_PY_PATH}#${field}`, value);
|
|
15401
15569
|
}
|
|
15402
15570
|
let curatedFieldsBlind = false;
|
|
15403
15571
|
if (!orchestrationSchemaMissing) {
|
|
15404
|
-
const curated = extractCuratedModelFields(
|
|
15572
|
+
const curated = extractCuratedModelFields(readFileSync44(orchestrationPath, "utf8"));
|
|
15405
15573
|
if (curated.rawFieldCount > 0 && curated.fields.every((f) => f.defaultValue === null && f.optionValues.length === 0)) {
|
|
15406
15574
|
curatedFieldsBlind = true;
|
|
15407
15575
|
}
|
|
@@ -15448,7 +15616,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
15448
15616
|
function discoverPluginDirs(pluginsRoot) {
|
|
15449
15617
|
return listDirs(pluginsRoot).filter((name) => {
|
|
15450
15618
|
try {
|
|
15451
|
-
return statSync16(
|
|
15619
|
+
return statSync16(join65(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
15452
15620
|
} catch {
|
|
15453
15621
|
return false;
|
|
15454
15622
|
}
|
|
@@ -15461,8 +15629,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
15461
15629
|
let terraformBlind = false;
|
|
15462
15630
|
let totalDeclaredTools = 0;
|
|
15463
15631
|
for (const name of pluginNames) {
|
|
15464
|
-
const pluginDir2 =
|
|
15465
|
-
const manifestText =
|
|
15632
|
+
const pluginDir2 = join65(pluginsRoot, name);
|
|
15633
|
+
const manifestText = readFileSync44(join65(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
15466
15634
|
const manifest = extractManifestTools(manifestText);
|
|
15467
15635
|
if (manifest.parseError) {
|
|
15468
15636
|
findings.push({
|
|
@@ -15480,13 +15648,13 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
15480
15648
|
totalDeclaredTools += manifest.tools.length;
|
|
15481
15649
|
const pySources = pluginPythonFiles(pluginDir2).map((f) => ({
|
|
15482
15650
|
file: f,
|
|
15483
|
-
text:
|
|
15651
|
+
text: readFileSync44(f, "utf8")
|
|
15484
15652
|
}));
|
|
15485
15653
|
const resolver = buildSymbolResolver(pySources);
|
|
15486
15654
|
const registry = extractToolRegistryEntries(pySources, resolver);
|
|
15487
15655
|
if (registry.rawToolDefinitionCount > 0 && registry.entries.length === 0) registryBlind = true;
|
|
15488
15656
|
const tfFiles = pluginTerraformFiles(pluginDir2);
|
|
15489
|
-
const tfText = tfFiles.map((f) =>
|
|
15657
|
+
const tfText = tfFiles.map((f) => readFileSync44(f, "utf8")).join("\n");
|
|
15490
15658
|
const terraform = extractTerraformEnvKeys(tfText);
|
|
15491
15659
|
if (terraform.rawMarkerCount > 0 && terraform.resolvedBlockCount === 0) terraformBlind = true;
|
|
15492
15660
|
for (const toolName of manifest.tools) {
|
|
@@ -15560,7 +15728,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
15560
15728
|
requiredEnvVars: envResult.envVars,
|
|
15561
15729
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
15562
15730
|
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 ${
|
|
15731
|
+
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
15732
|
});
|
|
15565
15733
|
}
|
|
15566
15734
|
}
|
|
@@ -15600,8 +15768,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
15600
15768
|
return;
|
|
15601
15769
|
}
|
|
15602
15770
|
let allOk = true;
|
|
15603
|
-
const pluginsRoot =
|
|
15604
|
-
if (!
|
|
15771
|
+
const pluginsRoot = join66(root, "services", "_plugins");
|
|
15772
|
+
if (!existsSync55(pluginsRoot)) {
|
|
15605
15773
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
15606
15774
|
} else {
|
|
15607
15775
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -15631,8 +15799,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
15631
15799
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
15632
15800
|
}
|
|
15633
15801
|
}
|
|
15634
|
-
const servicesApiRoot =
|
|
15635
|
-
if (!
|
|
15802
|
+
const servicesApiRoot = join66(root, "services", "api");
|
|
15803
|
+
if (!existsSync55(servicesApiRoot)) {
|
|
15636
15804
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
15637
15805
|
} else {
|
|
15638
15806
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -15806,10 +15974,10 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
15806
15974
|
}
|
|
15807
15975
|
|
|
15808
15976
|
// src/scripts/check-shared-file-reduction.ts
|
|
15809
|
-
import { readFileSync as
|
|
15977
|
+
import { readFileSync as readFileSync46 } from "fs";
|
|
15810
15978
|
|
|
15811
15979
|
// src/lib/shared-file-reduction-guard.ts
|
|
15812
|
-
import { readFileSync as
|
|
15980
|
+
import { readFileSync as readFileSync45 } from "fs";
|
|
15813
15981
|
var LEAF_TEST_CALLS = /* @__PURE__ */ new Set(["it", "test"]);
|
|
15814
15982
|
var SUITE_CALLS = /* @__PURE__ */ new Set(["describe", "suite"]);
|
|
15815
15983
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/;
|
|
@@ -15936,7 +16104,7 @@ function formatReductionReport(report) {
|
|
|
15936
16104
|
// src/scripts/check-shared-file-reduction.ts
|
|
15937
16105
|
function readStdin() {
|
|
15938
16106
|
try {
|
|
15939
|
-
return
|
|
16107
|
+
return readFileSync46(0, "utf8");
|
|
15940
16108
|
} catch {
|
|
15941
16109
|
return "";
|
|
15942
16110
|
}
|
|
@@ -15953,15 +16121,15 @@ function pairsFromTsv(tsv) {
|
|
|
15953
16121
|
const [target, existingPath, incomingPath] = fields;
|
|
15954
16122
|
pairs.push({
|
|
15955
16123
|
target,
|
|
15956
|
-
existing:
|
|
15957
|
-
incoming:
|
|
16124
|
+
existing: readFileSync46(existingPath, "utf8"),
|
|
16125
|
+
incoming: readFileSync46(incomingPath, "utf8")
|
|
15958
16126
|
});
|
|
15959
16127
|
}
|
|
15960
16128
|
return pairs;
|
|
15961
16129
|
}
|
|
15962
16130
|
function loadAccepted(manifestPath) {
|
|
15963
16131
|
if (!manifestPath) return {};
|
|
15964
|
-
const parsed = JSON.parse(
|
|
16132
|
+
const parsed = JSON.parse(readFileSync46(manifestPath, "utf8"));
|
|
15965
16133
|
return parsed.acceptedReductions ?? {};
|
|
15966
16134
|
}
|
|
15967
16135
|
async function runSharedFileReductionCheck(args) {
|
|
@@ -15969,13 +16137,13 @@ async function runSharedFileReductionCheck(args) {
|
|
|
15969
16137
|
let accepted;
|
|
15970
16138
|
try {
|
|
15971
16139
|
if (args.pairs) {
|
|
15972
|
-
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() :
|
|
16140
|
+
pairs = pairsFromTsv(args.pairs === "-" ? readStdin() : readFileSync46(args.pairs, "utf8"));
|
|
15973
16141
|
} else if (args.target && args.existing && args.incoming) {
|
|
15974
16142
|
pairs = [
|
|
15975
16143
|
{
|
|
15976
16144
|
target: args.target,
|
|
15977
|
-
existing:
|
|
15978
|
-
incoming:
|
|
16145
|
+
existing: readFileSync46(args.existing, "utf8"),
|
|
16146
|
+
incoming: readFileSync46(args.incoming, "utf8")
|
|
15979
16147
|
}
|
|
15980
16148
|
];
|
|
15981
16149
|
} else {
|
|
@@ -16009,12 +16177,12 @@ async function runSharedFileReductionCheck(args) {
|
|
|
16009
16177
|
}
|
|
16010
16178
|
|
|
16011
16179
|
// src/scripts/check-skeleton-drift.ts
|
|
16012
|
-
import { existsSync as
|
|
16013
|
-
import { join as
|
|
16180
|
+
import { existsSync as existsSync56, readdirSync as readdirSync28 } from "fs";
|
|
16181
|
+
import { join as join68 } from "path";
|
|
16014
16182
|
|
|
16015
16183
|
// src/lib/skeleton-drift-guard.ts
|
|
16016
|
-
import { readFileSync as
|
|
16017
|
-
import { join as
|
|
16184
|
+
import { readFileSync as readFileSync47, readdirSync as readdirSync27, statSync as statSync17 } from "fs";
|
|
16185
|
+
import { join as join67 } from "path";
|
|
16018
16186
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
16019
16187
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
16020
16188
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -16078,7 +16246,7 @@ function walk(dir, base = dir) {
|
|
|
16078
16246
|
}
|
|
16079
16247
|
for (const entry of entries) {
|
|
16080
16248
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
16081
|
-
const abs =
|
|
16249
|
+
const abs = join67(dir, entry);
|
|
16082
16250
|
let isDir;
|
|
16083
16251
|
try {
|
|
16084
16252
|
isDir = statSync17(abs).isDirectory();
|
|
@@ -16100,7 +16268,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
16100
16268
|
if (!rule.appliesTo(rel)) continue;
|
|
16101
16269
|
let contents;
|
|
16102
16270
|
try {
|
|
16103
|
-
contents =
|
|
16271
|
+
contents = readFileSync47(join67(skeletonRoot, rel), "utf8");
|
|
16104
16272
|
} catch {
|
|
16105
16273
|
continue;
|
|
16106
16274
|
}
|
|
@@ -16129,23 +16297,23 @@ function formatViolations2(violations) {
|
|
|
16129
16297
|
|
|
16130
16298
|
// src/scripts/check-skeleton-drift.ts
|
|
16131
16299
|
function discoverSkeletons(root) {
|
|
16132
|
-
const skeletonsDir =
|
|
16300
|
+
const skeletonsDir = join68(root, "_skeletons");
|
|
16133
16301
|
let entries;
|
|
16134
16302
|
try {
|
|
16135
16303
|
entries = readdirSync28(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
16136
16304
|
} catch {
|
|
16137
16305
|
return [];
|
|
16138
16306
|
}
|
|
16139
|
-
return entries.filter((name) =>
|
|
16307
|
+
return entries.filter((name) => existsSync56(join68(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
16140
16308
|
}
|
|
16141
16309
|
async function runSkeletonDriftCheck() {
|
|
16142
16310
|
const root = (await execa("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
16143
16311
|
const skeletons = discoverSkeletons(root);
|
|
16144
16312
|
let filesConsidered = 0;
|
|
16145
16313
|
for (const name of skeletons) {
|
|
16146
|
-
const skeletonRoot =
|
|
16314
|
+
const skeletonRoot = join68(root, "_skeletons", name);
|
|
16147
16315
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
16148
|
-
if (
|
|
16316
|
+
if (existsSync56(join68(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
16149
16317
|
filesConsidered += 1;
|
|
16150
16318
|
}
|
|
16151
16319
|
}
|
|
@@ -16153,7 +16321,7 @@ async function runSkeletonDriftCheck() {
|
|
|
16153
16321
|
`audited ${skeletons.length} skeleton(s) (${skeletons.join(", ") || "none"}), ${filesConsidered} file(s) considered, under ${root}/_skeletons`
|
|
16154
16322
|
);
|
|
16155
16323
|
if (skeletons.length === 0) {
|
|
16156
|
-
if (!
|
|
16324
|
+
if (!existsSync56(join68(root, "_skeletons"))) {
|
|
16157
16325
|
console.log(
|
|
16158
16326
|
`\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
16327
|
);
|
|
@@ -16165,7 +16333,7 @@ async function runSkeletonDriftCheck() {
|
|
|
16165
16333
|
process.exit(1);
|
|
16166
16334
|
}
|
|
16167
16335
|
const violations = skeletons.flatMap(
|
|
16168
|
-
(name) => auditSkeleton(
|
|
16336
|
+
(name) => auditSkeleton(join68(root, "_skeletons", name), name)
|
|
16169
16337
|
);
|
|
16170
16338
|
if (violations.length > 0) {
|
|
16171
16339
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -16366,8 +16534,8 @@ function rawArgsAfter(subcommand) {
|
|
|
16366
16534
|
}
|
|
16367
16535
|
|
|
16368
16536
|
// src/commands/doctor.ts
|
|
16369
|
-
import { existsSync as
|
|
16370
|
-
import { join as
|
|
16537
|
+
import { existsSync as existsSync57, readFileSync as readFileSync48 } from "fs";
|
|
16538
|
+
import { join as join70, resolve as resolve20 } from "path";
|
|
16371
16539
|
import chalk21 from "chalk";
|
|
16372
16540
|
import { Command as Command25 } from "commander";
|
|
16373
16541
|
|
|
@@ -16708,11 +16876,11 @@ async function reapAllBareBranches(cwd, branches, worktrees, currentBranch, deps
|
|
|
16708
16876
|
|
|
16709
16877
|
// src/lib/scratch-clone-scan.ts
|
|
16710
16878
|
import { readdirSync as readdirSync29, statSync as statSync18 } from "fs";
|
|
16711
|
-
import { join as
|
|
16879
|
+
import { join as join69 } from "path";
|
|
16712
16880
|
var INTEGRATION_BRANCH = "dev";
|
|
16713
16881
|
function isPlainCloneDir(path) {
|
|
16714
16882
|
try {
|
|
16715
|
-
return statSync18(
|
|
16883
|
+
return statSync18(join69(path, ".git")).isDirectory();
|
|
16716
16884
|
} catch {
|
|
16717
16885
|
return false;
|
|
16718
16886
|
}
|
|
@@ -16727,7 +16895,7 @@ async function findScratchCloneCandidates(estateRoot, deps) {
|
|
|
16727
16895
|
const names = readdirSync29(estateRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
16728
16896
|
const candidates = [];
|
|
16729
16897
|
for (const name of names) {
|
|
16730
|
-
const path =
|
|
16898
|
+
const path = join69(estateRoot, name);
|
|
16731
16899
|
if (!isPlainCloneDir(path)) continue;
|
|
16732
16900
|
let branch;
|
|
16733
16901
|
try {
|
|
@@ -16907,10 +17075,10 @@ function printScratchCloneReports(estateRoot, reports) {
|
|
|
16907
17075
|
);
|
|
16908
17076
|
}
|
|
16909
17077
|
function readLocalCoreVersion(cwd) {
|
|
16910
|
-
const path =
|
|
16911
|
-
if (!
|
|
17078
|
+
const path = join70(cwd, INSTANCE_CORE_FILE);
|
|
17079
|
+
if (!existsSync57(path)) return null;
|
|
16912
17080
|
try {
|
|
16913
|
-
return extractVersionField(
|
|
17081
|
+
return extractVersionField(readFileSync48(path, "utf8"));
|
|
16914
17082
|
} catch {
|
|
16915
17083
|
return null;
|
|
16916
17084
|
}
|
|
@@ -16930,10 +17098,10 @@ function extractVersionField(contents) {
|
|
|
16930
17098
|
return match?.[1] ?? null;
|
|
16931
17099
|
}
|
|
16932
17100
|
function readFossil(cwd) {
|
|
16933
|
-
const path =
|
|
16934
|
-
if (!
|
|
17101
|
+
const path = join70(cwd, CORE_VERSION_FILE);
|
|
17102
|
+
if (!existsSync57(path)) return null;
|
|
16935
17103
|
try {
|
|
16936
|
-
const value =
|
|
17104
|
+
const value = readFileSync48(path, "utf8").trim();
|
|
16937
17105
|
return value === "" ? null : value;
|
|
16938
17106
|
} catch {
|
|
16939
17107
|
return null;
|
|
@@ -17471,13 +17639,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
17471
17639
|
import { Command as Command27 } from "commander";
|
|
17472
17640
|
|
|
17473
17641
|
// src/lib/packaged-scripts.ts
|
|
17474
|
-
import { existsSync as
|
|
17475
|
-
import { dirname as dirname11, join as
|
|
17642
|
+
import { existsSync as existsSync58 } from "fs";
|
|
17643
|
+
import { dirname as dirname11, join as join71 } from "path";
|
|
17476
17644
|
function findPackagedScript(startDir, relativePath) {
|
|
17477
17645
|
let dir = startDir;
|
|
17478
17646
|
for (; ; ) {
|
|
17479
|
-
const candidate =
|
|
17480
|
-
if (
|
|
17647
|
+
const candidate = join71(dir, relativePath);
|
|
17648
|
+
if (existsSync58(candidate)) return candidate;
|
|
17481
17649
|
const parent = dirname11(dir);
|
|
17482
17650
|
if (parent === dir) return null;
|
|
17483
17651
|
dir = parent;
|