@biffo/cli 0.281.0 → 0.283.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_skeletons/plugin-template/README.md +41 -4
- package/_skeletons/plugin-template/biffo.plugin.json +4 -0
- package/_skeletons/plugin-template/db/seed/000_default_widget.sql +47 -0
- package/_skeletons/plugin-template/registry-schema.json +19 -0
- package/_skeletons/plugin-template/src/example_plugin/plugin.py +15 -4
- package/_skeletons/registry/registry-schema.json +19 -0
- package/dist/index.js +335 -230
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7744,6 +7744,10 @@ var UserIngressSchema = z7.object({
|
|
|
7744
7744
|
required_group: z7.string().min(1, NON_EMPTY_GROUP),
|
|
7745
7745
|
app: z7.string().regex(APP_REF, "must be an ASGI app reference '<module>:<attr>', e.g. 'ideation.app:app'")
|
|
7746
7746
|
}).strict();
|
|
7747
|
+
var AdminIngressSchema = z7.object({
|
|
7748
|
+
required_group: z7.string().min(1, NON_EMPTY_GROUP),
|
|
7749
|
+
app: z7.string().regex(APP_REF, "must be an ASGI app reference '<module>:<attr>', e.g. 'ideation.admin:app'")
|
|
7750
|
+
}).strict();
|
|
7747
7751
|
var UserFrontendSchema = z7.object({
|
|
7748
7752
|
dir: z7.string().regex(
|
|
7749
7753
|
REL_DIR,
|
|
@@ -7756,6 +7760,13 @@ var ToolDeclarationSchema = z7.object({
|
|
|
7756
7760
|
description: z7.string(),
|
|
7757
7761
|
parameters: z7.record(z7.string(), z7.unknown()).default({})
|
|
7758
7762
|
});
|
|
7763
|
+
var SeedDeclarationSchema = z7.object({
|
|
7764
|
+
dir: z7.string().regex(
|
|
7765
|
+
REL_DIR,
|
|
7766
|
+
"must be a plugin-relative path with no leading slash or traversal, e.g. db/seed"
|
|
7767
|
+
),
|
|
7768
|
+
baseline_tables: z7.array(z7.string()).default([])
|
|
7769
|
+
}).strict();
|
|
7759
7770
|
var ChatAgentDeclarationSchema = z7.object({
|
|
7760
7771
|
key: z7.string().regex(/^[a-z][a-z0-9-]*$/, "must be a lowercase kebab-case slug"),
|
|
7761
7772
|
agent_name: z7.string().optional(),
|
|
@@ -7781,9 +7792,10 @@ var PluginManifestSchema = z7.object({
|
|
|
7781
7792
|
// schema is the registry's, and this consumer only needs to count them.
|
|
7782
7793
|
event_subscriptions: z7.array(z7.object({ source: z7.string(), detail_type: z7.string() }).passthrough()).default([]),
|
|
7783
7794
|
required_core_version: z7.string().default(">=0.0.0"),
|
|
7784
|
-
// ADR-0018 user-facing surfaces. Optional: a plugin
|
|
7785
|
-
// ordinary (data/event/CRUD) plugin.
|
|
7795
|
+
// ADR-0018/0021 user-facing and admin-facing surfaces. Optional: a plugin
|
|
7796
|
+
// without them is an ordinary (data/event/CRUD) plugin.
|
|
7786
7797
|
user_ingress: UserIngressSchema.optional(),
|
|
7798
|
+
admin_ingress: AdminIngressSchema.optional(),
|
|
7787
7799
|
user_frontend: UserFrontendSchema.optional(),
|
|
7788
7800
|
// Tools the plugin's runtime exposes to an agentic worker (ADR-0014 §7,
|
|
7789
7801
|
// #569). Default empty — an ordinary plugin declares none. Parsing this
|
|
@@ -7794,7 +7806,11 @@ var PluginManifestSchema = z7.object({
|
|
|
7794
7806
|
tools: z7.array(ToolDeclarationSchema).default([]),
|
|
7795
7807
|
// Chat agents the plugin registers with Core (ADR-0017). Default empty — an
|
|
7796
7808
|
// ordinary plugin declares none.
|
|
7797
|
-
chat_agents: z7.array(ChatAgentDeclarationSchema).default([])
|
|
7809
|
+
chat_agents: z7.array(ChatAgentDeclarationSchema).default([]),
|
|
7810
|
+
// The plugin's tenant-scoped baseline-row seed (ADR-0005, biffo-template#1554).
|
|
7811
|
+
// Optional — a plugin with no baseline data omits this entirely, and
|
|
7812
|
+
// `biffo plugin install`/`upgrade` vendor nothing for it.
|
|
7813
|
+
seed: SeedDeclarationSchema.optional()
|
|
7798
7814
|
}).superRefine((manifest, ctx) => {
|
|
7799
7815
|
const tableNames = new Set(manifest.tables.map((t) => t.name));
|
|
7800
7816
|
for (const route of manifest.api_routes) {
|
|
@@ -7805,6 +7821,16 @@ var PluginManifestSchema = z7.object({
|
|
|
7805
7821
|
});
|
|
7806
7822
|
}
|
|
7807
7823
|
}
|
|
7824
|
+
if (manifest.seed) {
|
|
7825
|
+
for (const table of manifest.seed.baseline_tables) {
|
|
7826
|
+
if (!tableNames.has(table)) {
|
|
7827
|
+
ctx.addIssue({
|
|
7828
|
+
code: z7.ZodIssueCode.custom,
|
|
7829
|
+
message: `seed.baseline_tables references table '${table}', which is not declared in this manifest's 'tables' (${[...tableNames].sort().join(", ") || "none"})`
|
|
7830
|
+
});
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
}
|
|
7808
7834
|
});
|
|
7809
7835
|
function validateManifest(raw) {
|
|
7810
7836
|
const result = PluginManifestSchema.safeParse(raw);
|
|
@@ -8319,6 +8345,14 @@ var RegistryPluginEntrySchema = z8.object({
|
|
|
8319
8345
|
description: z8.string().optional(),
|
|
8320
8346
|
author: z8.string().optional(),
|
|
8321
8347
|
tags: z8.array(z8.string()).optional(),
|
|
8348
|
+
// Summary-form mirror of the manifest's `seed.baseline_tables` (see
|
|
8349
|
+
// ../../lib/plugin-manifest.ts's SeedDeclarationSchema and
|
|
8350
|
+
// _skeletons/registry/registry-schema.json's `seed`, biffo-template#1554).
|
|
8351
|
+
// The registry entry only ever needs to know WHICH tables a plugin promises
|
|
8352
|
+
// baseline rows for, never the seed `dir` itself — that only matters to the
|
|
8353
|
+
// install/upgrade vendoring step, which reads it from the plugin's own
|
|
8354
|
+
// biffo.plugin.json after cloning, not from this summary.
|
|
8355
|
+
baseline_tables: z8.array(z8.string()).optional(),
|
|
8322
8356
|
required_core_version: z8.string().optional(),
|
|
8323
8357
|
infra_modules: z8.array(z8.string()).optional(),
|
|
8324
8358
|
api_routes: z8.array(z8.string()).optional(),
|
|
@@ -8445,8 +8479,8 @@ function printEntry(entry) {
|
|
|
8445
8479
|
}
|
|
8446
8480
|
|
|
8447
8481
|
// src/commands/plugin-install.ts
|
|
8448
|
-
import { cpSync as
|
|
8449
|
-
import { join as
|
|
8482
|
+
import { cpSync as cpSync5, existsSync as existsSync30, mkdirSync as mkdirSync11, readFileSync as readFileSync22, statSync as statSync6 } from "fs";
|
|
8483
|
+
import { join as join32, relative as relative3, resolve as resolve12 } from "path";
|
|
8450
8484
|
import chalk15 from "chalk";
|
|
8451
8485
|
import { Command as Command15 } from "commander";
|
|
8452
8486
|
|
|
@@ -8572,9 +8606,48 @@ async function tryGit(cwd, args) {
|
|
|
8572
8606
|
}
|
|
8573
8607
|
}
|
|
8574
8608
|
|
|
8609
|
+
// src/lib/plugin-seed-vendor.ts
|
|
8610
|
+
import { cpSync as cpSync3, existsSync as existsSync28, mkdirSync as mkdirSync9, readdirSync as readdirSync12, rmSync as rmSync8 } from "fs";
|
|
8611
|
+
import { join as join29 } from "path";
|
|
8612
|
+
var VENDOR_PREFIX = "_plugin-";
|
|
8613
|
+
function pluginSeedImportDir(pluginName) {
|
|
8614
|
+
return `db/imports/${VENDOR_PREFIX}${pluginName}`;
|
|
8615
|
+
}
|
|
8616
|
+
function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
8617
|
+
if (!manifest.seed) {
|
|
8618
|
+
return { vendored: false };
|
|
8619
|
+
}
|
|
8620
|
+
const sourceSeedDir = join29(pluginSourceDir, manifest.seed.dir);
|
|
8621
|
+
if (!existsSync28(sourceSeedDir)) {
|
|
8622
|
+
throw new Error(
|
|
8623
|
+
`${manifest.name}'s manifest declares seed.dir '${manifest.seed.dir}', but ${sourceSeedDir} does not exist in the plugin's source.`
|
|
8624
|
+
);
|
|
8625
|
+
}
|
|
8626
|
+
const sqlFiles = readdirSync12(sourceSeedDir).filter((f) => f.endsWith(".sql"));
|
|
8627
|
+
if (sqlFiles.length === 0) {
|
|
8628
|
+
throw new Error(
|
|
8629
|
+
`${manifest.name}'s manifest declares seed.dir '${manifest.seed.dir}', but ${sourceSeedDir} contains no *.sql files.`
|
|
8630
|
+
);
|
|
8631
|
+
}
|
|
8632
|
+
const relTargetDir = pluginSeedImportDir(manifest.name);
|
|
8633
|
+
const targetDir = join29(cwd, relTargetDir);
|
|
8634
|
+
rmSync8(targetDir, { recursive: true, force: true });
|
|
8635
|
+
mkdirSync9(targetDir, { recursive: true });
|
|
8636
|
+
for (const file of sqlFiles) {
|
|
8637
|
+
cpSync3(join29(sourceSeedDir, file), join29(targetDir, file));
|
|
8638
|
+
}
|
|
8639
|
+
log.success(
|
|
8640
|
+
`Vendored ${sqlFiles.length} seed file(s) to ${relTargetDir}/ (baseline_tables: ${manifest.seed.baseline_tables.join(", ") || "none declared"})`
|
|
8641
|
+
);
|
|
8642
|
+
log.info(
|
|
8643
|
+
`${relTargetDir}/*.sql are checksum-tracked once applied (ADR-0005 section 4) \u2014 a later version must ship a new, additively-numbered file for a changed seed, never edit one already released, or the next deploy fails loudly.`
|
|
8644
|
+
);
|
|
8645
|
+
return { vendored: true, stagedPath: relTargetDir };
|
|
8646
|
+
}
|
|
8647
|
+
|
|
8575
8648
|
// src/lib/plugin-source-copy.ts
|
|
8576
|
-
import { copyFileSync as copyFileSync2, cpSync as
|
|
8577
|
-
import { basename, dirname as dirname9, join as
|
|
8649
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync4, mkdirSync as mkdirSync10 } from "fs";
|
|
8650
|
+
import { basename, dirname as dirname9, join as join30 } from "path";
|
|
8578
8651
|
import { execa as execa6 } from "execa";
|
|
8579
8652
|
var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
8580
8653
|
".git",
|
|
@@ -8591,17 +8664,17 @@ async function copyPluginSource(sourceDir, targetDir) {
|
|
|
8591
8664
|
if (await isGitWorkingTree2(sourceDir)) {
|
|
8592
8665
|
const files = await listGitFiles(sourceDir);
|
|
8593
8666
|
for (const relPath of files) {
|
|
8594
|
-
const destPath =
|
|
8595
|
-
|
|
8596
|
-
copyFileSync2(
|
|
8667
|
+
const destPath = join30(targetDir, relPath);
|
|
8668
|
+
mkdirSync10(dirname9(destPath), { recursive: true });
|
|
8669
|
+
copyFileSync2(join30(sourceDir, relPath), destPath);
|
|
8597
8670
|
}
|
|
8598
8671
|
return { usedGitIgnoreRules: true };
|
|
8599
8672
|
}
|
|
8600
8673
|
log.warn(
|
|
8601
8674
|
`${sourceDir} is not a git working tree \u2014 cannot honour .gitignore. Falling back to a fixed exclude list (.git, .venv, node_modules, caches); anything else it does not know about (e.g. an unfamiliar cache directory) will be copied.`
|
|
8602
8675
|
);
|
|
8603
|
-
|
|
8604
|
-
|
|
8676
|
+
mkdirSync10(targetDir, { recursive: true });
|
|
8677
|
+
cpSync4(sourceDir, targetDir, {
|
|
8605
8678
|
recursive: true,
|
|
8606
8679
|
filter: (src) => !LOCAL_COPY_EXCLUDES.has(basename(src))
|
|
8607
8680
|
});
|
|
@@ -8625,8 +8698,8 @@ async function listGitFiles(dir) {
|
|
|
8625
8698
|
}
|
|
8626
8699
|
|
|
8627
8700
|
// src/lib/plugin-workspace-sources.ts
|
|
8628
|
-
import { existsSync as
|
|
8629
|
-
import { join as
|
|
8701
|
+
import { existsSync as existsSync29, readdirSync as readdirSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "fs";
|
|
8702
|
+
import { join as join31 } from "path";
|
|
8630
8703
|
function readTomlStringArray(text, key) {
|
|
8631
8704
|
const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
|
|
8632
8705
|
if (!open) return [];
|
|
@@ -8670,8 +8743,8 @@ function readDependencyNames(text) {
|
|
|
8670
8743
|
return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
|
|
8671
8744
|
}
|
|
8672
8745
|
function workspaceMemberNames(instanceRoot) {
|
|
8673
|
-
const rootPyproject =
|
|
8674
|
-
if (!
|
|
8746
|
+
const rootPyproject = join31(instanceRoot, "pyproject.toml");
|
|
8747
|
+
if (!existsSync29(rootPyproject)) return /* @__PURE__ */ new Set();
|
|
8675
8748
|
const text = readFileSync21(rootPyproject, "utf8");
|
|
8676
8749
|
const members = readTomlStringArray(text, "members");
|
|
8677
8750
|
const excluded = new Set(readTomlStringArray(text, "exclude"));
|
|
@@ -8681,7 +8754,7 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8681
8754
|
const base = member.slice(0, -2);
|
|
8682
8755
|
let entries;
|
|
8683
8756
|
try {
|
|
8684
|
-
entries =
|
|
8757
|
+
entries = readdirSync13(join31(instanceRoot, base), { withFileTypes: true });
|
|
8685
8758
|
} catch {
|
|
8686
8759
|
continue;
|
|
8687
8760
|
}
|
|
@@ -8695,8 +8768,8 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8695
8768
|
}
|
|
8696
8769
|
const names = /* @__PURE__ */ new Set();
|
|
8697
8770
|
for (const dir of dirs) {
|
|
8698
|
-
const pp =
|
|
8699
|
-
if (!
|
|
8771
|
+
const pp = join31(instanceRoot, dir, "pyproject.toml");
|
|
8772
|
+
if (!existsSync29(pp)) continue;
|
|
8700
8773
|
const name = readProjectName(readFileSync21(pp, "utf8"));
|
|
8701
8774
|
if (name) names.add(name);
|
|
8702
8775
|
}
|
|
@@ -8708,7 +8781,7 @@ function existingWorkspaceSources(text) {
|
|
|
8708
8781
|
);
|
|
8709
8782
|
}
|
|
8710
8783
|
function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
|
|
8711
|
-
if (!
|
|
8784
|
+
if (!existsSync29(pluginPyprojectPath) || memberNames.size === 0) return [];
|
|
8712
8785
|
const text = readFileSync21(pluginPyprojectPath, "utf8");
|
|
8713
8786
|
const already = existingWorkspaceSources(text);
|
|
8714
8787
|
const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
|
|
@@ -8733,8 +8806,8 @@ ${lines.join("\n")}
|
|
|
8733
8806
|
return toAdd;
|
|
8734
8807
|
}
|
|
8735
8808
|
function applyWorkspaceSources(targetDir, cwd, relTargetDir) {
|
|
8736
|
-
const pluginPyproject =
|
|
8737
|
-
if (!
|
|
8809
|
+
const pluginPyproject = join31(targetDir, "pyproject.toml");
|
|
8810
|
+
if (!existsSync29(pluginPyproject)) return;
|
|
8738
8811
|
const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(cwd));
|
|
8739
8812
|
if (sourced.length > 0) {
|
|
8740
8813
|
log.info(
|
|
@@ -8774,14 +8847,14 @@ var pluginInstallCommand = new Command15("install").description(
|
|
|
8774
8847
|
}
|
|
8775
8848
|
);
|
|
8776
8849
|
function resolveLocalPlugin(localPath) {
|
|
8777
|
-
if (!
|
|
8850
|
+
if (!existsSync30(localPath)) {
|
|
8778
8851
|
throw new Error(`--local path does not exist: ${localPath}`);
|
|
8779
8852
|
}
|
|
8780
8853
|
if (!statSync6(localPath).isDirectory()) {
|
|
8781
8854
|
throw new Error(`--local path is not a directory: ${localPath}`);
|
|
8782
8855
|
}
|
|
8783
|
-
const manifestPath =
|
|
8784
|
-
if (!
|
|
8856
|
+
const manifestPath = join32(localPath, "biffo.plugin.json");
|
|
8857
|
+
if (!existsSync30(manifestPath)) {
|
|
8785
8858
|
throw new Error(
|
|
8786
8859
|
`${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>\`.)`
|
|
8787
8860
|
);
|
|
@@ -8807,8 +8880,8 @@ function parsePluginTarget(target) {
|
|
|
8807
8880
|
async function cloneAndValidatePlugin(entry, git) {
|
|
8808
8881
|
const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
|
|
8809
8882
|
try {
|
|
8810
|
-
const manifestPath =
|
|
8811
|
-
if (!
|
|
8883
|
+
const manifestPath = join32(tmpDir, "biffo.plugin.json");
|
|
8884
|
+
if (!existsSync30(manifestPath)) {
|
|
8812
8885
|
throw new Error(
|
|
8813
8886
|
`Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
|
|
8814
8887
|
);
|
|
@@ -8836,8 +8909,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8836
8909
|
`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\`).`
|
|
8837
8910
|
);
|
|
8838
8911
|
}
|
|
8839
|
-
const servicesDir =
|
|
8840
|
-
if (!
|
|
8912
|
+
const servicesDir = join32(options.cwd, "services");
|
|
8913
|
+
if (!existsSync30(servicesDir)) {
|
|
8841
8914
|
throw new Error(
|
|
8842
8915
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8843
8916
|
);
|
|
@@ -8855,10 +8928,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8855
8928
|
}
|
|
8856
8929
|
const pluginName = entry ? entry.name : source.name;
|
|
8857
8930
|
const relTargetDir = pluginDir(pluginName, "third-party");
|
|
8858
|
-
const targetDir =
|
|
8859
|
-
const modulesDir =
|
|
8931
|
+
const targetDir = join32(options.cwd, relTargetDir);
|
|
8932
|
+
const modulesDir = join32(options.cwd, "modules", "plugins", pluginName);
|
|
8860
8933
|
const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
|
|
8861
|
-
if (
|
|
8934
|
+
if (existsSync30(targetDir) && !inTreeSource) {
|
|
8862
8935
|
throw new Error(
|
|
8863
8936
|
`Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
|
|
8864
8937
|
);
|
|
@@ -8893,7 +8966,7 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8893
8966
|
if (inTreeSource) {
|
|
8894
8967
|
log.info(`${relTargetDir}/ is already in this checkout \u2014 installing in place.`);
|
|
8895
8968
|
} else {
|
|
8896
|
-
|
|
8969
|
+
mkdirSync11(targetDir, { recursive: true });
|
|
8897
8970
|
await copyPluginSource(source.sourceDir, targetDir);
|
|
8898
8971
|
log.success(`Installed plugin source at ${relTargetDir}/`);
|
|
8899
8972
|
}
|
|
@@ -8902,10 +8975,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8902
8975
|
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
8903
8976
|
applyWorkspaceSources(targetDir, options.cwd, relTargetDir);
|
|
8904
8977
|
const stagePaths = [relTargetDir];
|
|
8905
|
-
const tfSourceDir =
|
|
8906
|
-
if (
|
|
8907
|
-
|
|
8908
|
-
|
|
8978
|
+
const tfSourceDir = join32(targetDir, "terraform");
|
|
8979
|
+
if (existsSync30(tfSourceDir)) {
|
|
8980
|
+
mkdirSync11(modulesDir, { recursive: true });
|
|
8981
|
+
cpSync5(tfSourceDir, modulesDir, { recursive: true });
|
|
8909
8982
|
stagePaths.push(`modules/plugins/${pluginName}`);
|
|
8910
8983
|
log.success(`Copied Terraform module to modules/plugins/${pluginName}/`);
|
|
8911
8984
|
const wiring = syncPluginTerraform(options.cwd);
|
|
@@ -8944,6 +9017,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8944
9017
|
} else {
|
|
8945
9018
|
log.info(`${pluginName} declares no tables \u2014 nothing to migrate.`);
|
|
8946
9019
|
}
|
|
9020
|
+
const seedResult = vendorPluginSeed(targetDir, manifest, options.cwd);
|
|
9021
|
+
if (seedResult.vendored) {
|
|
9022
|
+
stagePaths.push(seedResult.stagedPath);
|
|
9023
|
+
}
|
|
8947
9024
|
const commitMessage = `feat(plugins): install ${pluginName}@${source.version}`;
|
|
8948
9025
|
await deps.git.add(options.cwd, stagePaths);
|
|
8949
9026
|
await deps.git.commit(options.cwd, commitMessage);
|
|
@@ -8992,13 +9069,18 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
|
|
|
8992
9069
|
` Would generate a migration for ${source.manifest.tables.length} table(s) into services/api/migrations/versions/`
|
|
8993
9070
|
);
|
|
8994
9071
|
}
|
|
9072
|
+
if (source && source.manifest.seed) {
|
|
9073
|
+
console.log(
|
|
9074
|
+
` Would vendor seed DDL into: ${pluginSeedImportDir(name)}/ (baseline_tables: ${source.manifest.seed.baseline_tables.join(", ") || "none declared"})`
|
|
9075
|
+
);
|
|
9076
|
+
}
|
|
8995
9077
|
console.log(` Would commit: feat(plugins): install ${name}@${version}
|
|
8996
9078
|
`);
|
|
8997
9079
|
}
|
|
8998
9080
|
|
|
8999
9081
|
// src/commands/plugin-list.ts
|
|
9000
|
-
import { existsSync as
|
|
9001
|
-
import { join as
|
|
9082
|
+
import { existsSync as existsSync31, readFileSync as readFileSync23 } from "fs";
|
|
9083
|
+
import { join as join33, resolve as resolve13 } from "path";
|
|
9002
9084
|
import chalk16 from "chalk";
|
|
9003
9085
|
import { Command as Command16 } from "commander";
|
|
9004
9086
|
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) => {
|
|
@@ -9011,8 +9093,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
|
|
|
9011
9093
|
}
|
|
9012
9094
|
});
|
|
9013
9095
|
async function runPluginList(options) {
|
|
9014
|
-
const servicesDir =
|
|
9015
|
-
if (!
|
|
9096
|
+
const servicesDir = join33(options.cwd, "services");
|
|
9097
|
+
if (!existsSync31(servicesDir)) {
|
|
9016
9098
|
throw new Error(
|
|
9017
9099
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9018
9100
|
);
|
|
@@ -9061,14 +9143,14 @@ import { resolve as resolve14 } from "path";
|
|
|
9061
9143
|
import { Command as Command17 } from "commander";
|
|
9062
9144
|
|
|
9063
9145
|
// src/lib/plugin-staleness.ts
|
|
9064
|
-
import { existsSync as
|
|
9065
|
-
import { join as
|
|
9146
|
+
import { existsSync as existsSync32, readFileSync as readFileSync24, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
|
|
9147
|
+
import { join as join34, relative as relative4 } from "path";
|
|
9066
9148
|
function discoverVendoredPlugins(servicesDir) {
|
|
9067
|
-
if (!
|
|
9068
|
-
return
|
|
9149
|
+
if (!existsSync32(servicesDir)) return [];
|
|
9150
|
+
return readdirSync14(servicesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith("_") && e.name !== "api").map((e) => e.name).filter((name) => existsSync32(join34(servicesDir, name, "biffo.plugin.json"))).sort();
|
|
9069
9151
|
}
|
|
9070
9152
|
async function checkPluginStaleness(cwd, deps) {
|
|
9071
|
-
const servicesDir =
|
|
9153
|
+
const servicesDir = join34(cwd, "services");
|
|
9072
9154
|
const names = discoverVendoredPlugins(servicesDir);
|
|
9073
9155
|
let registryRepoByName = null;
|
|
9074
9156
|
const resolveRegistryRepo = async (name) => {
|
|
@@ -9084,7 +9166,7 @@ async function checkPluginStaleness(cwd, deps) {
|
|
|
9084
9166
|
};
|
|
9085
9167
|
const results = [];
|
|
9086
9168
|
for (const name of names) {
|
|
9087
|
-
results.push(await checkOnePlugin(
|
|
9169
|
+
results.push(await checkOnePlugin(join34(servicesDir, name), name, resolveRegistryRepo, deps.git));
|
|
9088
9170
|
}
|
|
9089
9171
|
return results;
|
|
9090
9172
|
}
|
|
@@ -9110,7 +9192,7 @@ async function checkOnePlugin(pluginDir2, name, resolveRegistryRepo, git) {
|
|
|
9110
9192
|
if (record?.sha && isFetchableUrl(record.origin)) {
|
|
9111
9193
|
return checkViaProvenance(name, record, record.origin, git);
|
|
9112
9194
|
}
|
|
9113
|
-
const localOrigin = record && !isFetchableUrl(record.origin) &&
|
|
9195
|
+
const localOrigin = record && !isFetchableUrl(record.origin) && existsSync32(record.origin) ? record.origin : null;
|
|
9114
9196
|
if (localOrigin) {
|
|
9115
9197
|
return checkViaContentDiff(name, pluginDir2, localOrigin, { isLocalDir: true }, git);
|
|
9116
9198
|
}
|
|
@@ -9244,8 +9326,8 @@ async function countDifferingFiles(sourceDir, pluginDir2) {
|
|
|
9244
9326
|
differing++;
|
|
9245
9327
|
continue;
|
|
9246
9328
|
}
|
|
9247
|
-
const a = readFileSync24(
|
|
9248
|
-
const b = readFileSync24(
|
|
9329
|
+
const a = readFileSync24(join34(sourceDir, relPath));
|
|
9330
|
+
const b = readFileSync24(join34(pluginDir2, relPath));
|
|
9249
9331
|
if (!a.equals(b)) differing++;
|
|
9250
9332
|
}
|
|
9251
9333
|
return differing;
|
|
@@ -9260,11 +9342,11 @@ function vendorFileList(dir) {
|
|
|
9260
9342
|
return new Set(walkExcluding(dir, dir, LOCAL_COPY_EXCLUDES));
|
|
9261
9343
|
}
|
|
9262
9344
|
function walkExcluding(root, dir, excludes) {
|
|
9263
|
-
if (!
|
|
9345
|
+
if (!existsSync32(dir)) return [];
|
|
9264
9346
|
const out = [];
|
|
9265
|
-
for (const entry of
|
|
9347
|
+
for (const entry of readdirSync14(dir)) {
|
|
9266
9348
|
if (excludes.has(entry) || entry === ".git") continue;
|
|
9267
|
-
const full =
|
|
9349
|
+
const full = join34(dir, entry);
|
|
9268
9350
|
const stat = statSync7(full);
|
|
9269
9351
|
if (stat.isDirectory()) {
|
|
9270
9352
|
out.push(...walkExcluding(root, full, excludes));
|
|
@@ -9316,8 +9398,8 @@ var pluginStalenessCommand = new Command17("staleness").description(
|
|
|
9316
9398
|
});
|
|
9317
9399
|
|
|
9318
9400
|
// src/commands/plugin-sync-migrations.ts
|
|
9319
|
-
import { existsSync as
|
|
9320
|
-
import { join as
|
|
9401
|
+
import { existsSync as existsSync33 } from "fs";
|
|
9402
|
+
import { join as join35, relative as relative5, resolve as resolve15 } from "path";
|
|
9321
9403
|
import chalk17 from "chalk";
|
|
9322
9404
|
import { Command as Command18 } from "commander";
|
|
9323
9405
|
var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
@@ -9338,11 +9420,11 @@ var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
|
9338
9420
|
}
|
|
9339
9421
|
);
|
|
9340
9422
|
async function runPluginSyncMigrations(name, options, deps) {
|
|
9341
|
-
const servicesDir =
|
|
9342
|
-
if (!
|
|
9423
|
+
const servicesDir = join35(options.cwd, "services");
|
|
9424
|
+
if (!existsSync33(servicesDir)) {
|
|
9343
9425
|
throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
|
|
9344
9426
|
}
|
|
9345
|
-
if (name && !
|
|
9427
|
+
if (name && !existsSync33(join35(servicesDir, name, "biffo.plugin.json"))) {
|
|
9346
9428
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
9347
9429
|
}
|
|
9348
9430
|
if (options.dryRun) {
|
|
@@ -9378,8 +9460,8 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
9378
9460
|
}
|
|
9379
9461
|
|
|
9380
9462
|
// src/commands/plugin-uninstall.ts
|
|
9381
|
-
import { existsSync as
|
|
9382
|
-
import { join as
|
|
9463
|
+
import { existsSync as existsSync34, readFileSync as readFileSync25, rmSync as rmSync9 } from "fs";
|
|
9464
|
+
import { join as join36, resolve as resolve16 } from "path";
|
|
9383
9465
|
import chalk18 from "chalk";
|
|
9384
9466
|
import { Command as Command19 } from "commander";
|
|
9385
9467
|
import inquirer6 from "inquirer";
|
|
@@ -9411,16 +9493,16 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9411
9493
|
if (!NAME_PATTERN2.test(name)) {
|
|
9412
9494
|
throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
|
|
9413
9495
|
}
|
|
9414
|
-
const servicesDir =
|
|
9415
|
-
if (!
|
|
9496
|
+
const servicesDir = join36(options.cwd, "services");
|
|
9497
|
+
if (!existsSync34(servicesDir)) {
|
|
9416
9498
|
throw new Error(
|
|
9417
9499
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9418
9500
|
);
|
|
9419
9501
|
}
|
|
9420
|
-
const targetDir =
|
|
9421
|
-
if (!
|
|
9422
|
-
const firstParty =
|
|
9423
|
-
if (
|
|
9502
|
+
const targetDir = join36(servicesDir, name);
|
|
9503
|
+
if (!existsSync34(targetDir)) {
|
|
9504
|
+
const firstParty = join36(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
|
|
9505
|
+
if (existsSync34(firstParty)) {
|
|
9424
9506
|
throw new Error(
|
|
9425
9507
|
`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.`
|
|
9426
9508
|
);
|
|
@@ -9428,9 +9510,9 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9428
9510
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
9429
9511
|
}
|
|
9430
9512
|
const version = readInstalledVersion(targetDir);
|
|
9431
|
-
const modulesDir =
|
|
9513
|
+
const modulesDir = join36(options.cwd, "modules", "plugins", name);
|
|
9432
9514
|
const stagePaths = [`services/${name}`];
|
|
9433
|
-
if (
|
|
9515
|
+
if (existsSync34(modulesDir)) {
|
|
9434
9516
|
stagePaths.push(`modules/plugins/${name}`);
|
|
9435
9517
|
}
|
|
9436
9518
|
if (options.dryRun) {
|
|
@@ -9450,10 +9532,10 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9450
9532
|
`${options.cwd} is not a git repository \u2014 biffo plugin uninstall must be run from a Biffo project checkout.`
|
|
9451
9533
|
);
|
|
9452
9534
|
}
|
|
9453
|
-
|
|
9535
|
+
rmSync9(targetDir, { recursive: true, force: true });
|
|
9454
9536
|
log.success(`Removed services/${name}/`);
|
|
9455
|
-
if (
|
|
9456
|
-
|
|
9537
|
+
if (existsSync34(modulesDir)) {
|
|
9538
|
+
rmSync9(modulesDir, { recursive: true, force: true });
|
|
9457
9539
|
log.success(`Removed modules/plugins/${name}/`);
|
|
9458
9540
|
const wiring = syncPluginTerraform(options.cwd);
|
|
9459
9541
|
stagePaths.push(...wiring.changedPaths);
|
|
@@ -9487,10 +9569,15 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9487
9569
|
"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."
|
|
9488
9570
|
);
|
|
9489
9571
|
}
|
|
9572
|
+
if (existsSync34(join36(options.cwd, pluginSeedImportDir(name)))) {
|
|
9573
|
+
log.warn(
|
|
9574
|
+
`${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.`
|
|
9575
|
+
);
|
|
9576
|
+
}
|
|
9490
9577
|
}
|
|
9491
9578
|
function readInstalledVersion(targetDir) {
|
|
9492
|
-
const manifestPath =
|
|
9493
|
-
if (!
|
|
9579
|
+
const manifestPath = join36(targetDir, "biffo.plugin.json");
|
|
9580
|
+
if (!existsSync34(manifestPath)) return void 0;
|
|
9494
9581
|
try {
|
|
9495
9582
|
return validateManifest(JSON.parse(readFileSync25(manifestPath, "utf8"))).version;
|
|
9496
9583
|
} catch {
|
|
@@ -9525,8 +9612,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
|
|
|
9525
9612
|
}
|
|
9526
9613
|
|
|
9527
9614
|
// src/commands/plugin-upgrade.ts
|
|
9528
|
-
import { cpSync as
|
|
9529
|
-
import { join as
|
|
9615
|
+
import { cpSync as cpSync6, existsSync as existsSync35, mkdirSync as mkdirSync12, readFileSync as readFileSync26, rmSync as rmSync10 } from "fs";
|
|
9616
|
+
import { join as join37, relative as relative6, resolve as resolve17 } from "path";
|
|
9530
9617
|
import chalk19 from "chalk";
|
|
9531
9618
|
import { Command as Command20 } from "commander";
|
|
9532
9619
|
import inquirer7 from "inquirer";
|
|
@@ -9573,8 +9660,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9573
9660
|
`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\`).`
|
|
9574
9661
|
);
|
|
9575
9662
|
}
|
|
9576
|
-
const servicesDir =
|
|
9577
|
-
if (!
|
|
9663
|
+
const servicesDir = join37(options.cwd, "services");
|
|
9664
|
+
if (!existsSync35(servicesDir)) {
|
|
9578
9665
|
throw new Error(
|
|
9579
9666
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9580
9667
|
);
|
|
@@ -9583,8 +9670,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9583
9670
|
return runLocalPluginRefresh(options.local, options, deps);
|
|
9584
9671
|
}
|
|
9585
9672
|
const { name, minor } = parsePluginTarget(target);
|
|
9586
|
-
const targetDir =
|
|
9587
|
-
if (!
|
|
9673
|
+
const targetDir = join37(servicesDir, name);
|
|
9674
|
+
if (!existsSync35(targetDir)) {
|
|
9588
9675
|
throw new Error(
|
|
9589
9676
|
`Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
|
|
9590
9677
|
);
|
|
@@ -9598,7 +9685,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9598
9685
|
`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.`
|
|
9599
9686
|
);
|
|
9600
9687
|
}
|
|
9601
|
-
const modulesDir =
|
|
9688
|
+
const modulesDir = join37(options.cwd, "modules", "plugins", entry.name);
|
|
9602
9689
|
if (options.dryRun) {
|
|
9603
9690
|
printDryRun6(entry, currentVersion);
|
|
9604
9691
|
return;
|
|
@@ -9627,9 +9714,9 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9627
9714
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
9628
9715
|
);
|
|
9629
9716
|
const previousProvenance = readProvenance(targetDir);
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9717
|
+
rmSync10(targetDir, { recursive: true, force: true });
|
|
9718
|
+
mkdirSync12(targetDir, { recursive: true });
|
|
9719
|
+
cpSync6(tmpDir, targetDir, { recursive: true });
|
|
9633
9720
|
log.success(`Upgraded plugin source at services/${entry.name}/`);
|
|
9634
9721
|
const nextProvenance = resolveRegistryProvenance(
|
|
9635
9722
|
entry.repo,
|
|
@@ -9638,13 +9725,13 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9638
9725
|
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
9639
9726
|
applyWorkspaceSources(targetDir, options.cwd, `services/${entry.name}`);
|
|
9640
9727
|
const stagePaths = [`services/${entry.name}`];
|
|
9641
|
-
if (
|
|
9642
|
-
|
|
9728
|
+
if (existsSync35(modulesDir)) {
|
|
9729
|
+
rmSync10(modulesDir, { recursive: true, force: true });
|
|
9643
9730
|
}
|
|
9644
|
-
const tfSourceDir =
|
|
9645
|
-
if (
|
|
9646
|
-
|
|
9647
|
-
|
|
9731
|
+
const tfSourceDir = join37(targetDir, "terraform");
|
|
9732
|
+
if (existsSync35(tfSourceDir)) {
|
|
9733
|
+
mkdirSync12(modulesDir, { recursive: true });
|
|
9734
|
+
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
9648
9735
|
stagePaths.push(`modules/plugins/${entry.name}`);
|
|
9649
9736
|
log.success(`Copied Terraform module to modules/plugins/${entry.name}/`);
|
|
9650
9737
|
}
|
|
@@ -9664,6 +9751,10 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9664
9751
|
);
|
|
9665
9752
|
}
|
|
9666
9753
|
}
|
|
9754
|
+
const seedResult = vendorPluginSeed(targetDir, manifest, options.cwd);
|
|
9755
|
+
if (seedResult.vendored) {
|
|
9756
|
+
stagePaths.push(seedResult.stagedPath);
|
|
9757
|
+
}
|
|
9667
9758
|
const label = currentVersion ? `${entry.name} ${currentVersion} -> ${entry.version}` : `${entry.name} to ${entry.version}`;
|
|
9668
9759
|
const commitMessage = `feat(plugins): upgrade ${label}`;
|
|
9669
9760
|
await deps.git.add(options.cwd, stagePaths);
|
|
@@ -9682,16 +9773,16 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9682
9773
|
async function runLocalPluginRefresh(localPath, options, deps) {
|
|
9683
9774
|
const source = resolveLocalPlugin(localPath);
|
|
9684
9775
|
log.success(`Resolved ${source.name}@${source.version} from ${source.origin}`);
|
|
9685
|
-
const servicesDir =
|
|
9686
|
-
const targetDir =
|
|
9687
|
-
if (!
|
|
9776
|
+
const servicesDir = join37(options.cwd, "services");
|
|
9777
|
+
const targetDir = join37(servicesDir, source.name);
|
|
9778
|
+
if (!existsSync35(targetDir)) {
|
|
9688
9779
|
throw new Error(
|
|
9689
9780
|
`Plugin '${source.name}' is not installed at services/${source.name}/. Use 'biffo plugin install --local ${localPath}' instead.`
|
|
9690
9781
|
);
|
|
9691
9782
|
}
|
|
9692
9783
|
const inTreeSource = resolve17(source.sourceDir) === resolve17(targetDir);
|
|
9693
9784
|
const currentVersion = readInstalledVersion2(targetDir);
|
|
9694
|
-
const modulesDir =
|
|
9785
|
+
const modulesDir = join37(options.cwd, "modules", "plugins", source.name);
|
|
9695
9786
|
if (options.dryRun) {
|
|
9696
9787
|
printLocalDryRun(source, currentVersion, inTreeSource);
|
|
9697
9788
|
return;
|
|
@@ -9720,8 +9811,8 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9720
9811
|
`services/${source.name}/ is already the local checkout \u2014 nothing to copy; re-syncing its Terraform module and checking for a migration.`
|
|
9721
9812
|
);
|
|
9722
9813
|
} else {
|
|
9723
|
-
|
|
9724
|
-
|
|
9814
|
+
rmSync10(targetDir, { recursive: true, force: true });
|
|
9815
|
+
mkdirSync12(targetDir, { recursive: true });
|
|
9725
9816
|
await copyPluginSource(source.sourceDir, targetDir);
|
|
9726
9817
|
log.success(`Refreshed plugin source at services/${source.name}/ from ${source.origin}`);
|
|
9727
9818
|
}
|
|
@@ -9729,13 +9820,13 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9729
9820
|
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
9730
9821
|
applyWorkspaceSources(targetDir, options.cwd, `services/${source.name}`);
|
|
9731
9822
|
const stagePaths = [`services/${source.name}`];
|
|
9732
|
-
if (
|
|
9733
|
-
|
|
9823
|
+
if (existsSync35(modulesDir)) {
|
|
9824
|
+
rmSync10(modulesDir, { recursive: true, force: true });
|
|
9734
9825
|
}
|
|
9735
|
-
const tfSourceDir =
|
|
9736
|
-
if (
|
|
9737
|
-
|
|
9738
|
-
|
|
9826
|
+
const tfSourceDir = join37(targetDir, "terraform");
|
|
9827
|
+
if (existsSync35(tfSourceDir)) {
|
|
9828
|
+
mkdirSync12(modulesDir, { recursive: true });
|
|
9829
|
+
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
9739
9830
|
stagePaths.push(`modules/plugins/${source.name}`);
|
|
9740
9831
|
log.success(`Refreshed Terraform module at modules/plugins/${source.name}/`);
|
|
9741
9832
|
}
|
|
@@ -9757,6 +9848,10 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9757
9848
|
} else {
|
|
9758
9849
|
log.info(`${source.name} declares no tables \u2014 nothing to migrate.`);
|
|
9759
9850
|
}
|
|
9851
|
+
const seedResult = vendorPluginSeed(targetDir, manifest, options.cwd);
|
|
9852
|
+
if (seedResult.vendored) {
|
|
9853
|
+
stagePaths.push(seedResult.stagedPath);
|
|
9854
|
+
}
|
|
9760
9855
|
await deps.git.add(options.cwd, stagePaths);
|
|
9761
9856
|
if (!await deps.git.hasUncommittedChanges(options.cwd)) {
|
|
9762
9857
|
log.warn(`services/${source.name}/ already matches ${source.origin} \u2014 nothing to commit.`);
|
|
@@ -9776,8 +9871,8 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9776
9871
|
}
|
|
9777
9872
|
}
|
|
9778
9873
|
function readInstalledVersion2(targetDir) {
|
|
9779
|
-
const manifestPath =
|
|
9780
|
-
if (!
|
|
9874
|
+
const manifestPath = join37(targetDir, "biffo.plugin.json");
|
|
9875
|
+
if (!existsSync35(manifestPath)) return void 0;
|
|
9781
9876
|
try {
|
|
9782
9877
|
return validateManifest(JSON.parse(readFileSync26(manifestPath, "utf8"))).version;
|
|
9783
9878
|
} catch {
|
|
@@ -9814,6 +9909,11 @@ function printDryRun6(entry, currentVersion) {
|
|
|
9814
9909
|
` Would replace Terraform module at: modules/plugins/${entry.name}/ (if the repo has one)`
|
|
9815
9910
|
);
|
|
9816
9911
|
}
|
|
9912
|
+
if (entry.baseline_tables && entry.baseline_tables.length > 0) {
|
|
9913
|
+
console.log(
|
|
9914
|
+
` Would re-vendor seed DDL into: ${pluginSeedImportDir(entry.name)}/ (baseline_tables: ${entry.baseline_tables.join(", ")})`
|
|
9915
|
+
);
|
|
9916
|
+
}
|
|
9817
9917
|
console.log(` Would commit: feat(plugins): upgrade ${entry.name} to ${entry.version}
|
|
9818
9918
|
`);
|
|
9819
9919
|
}
|
|
@@ -9833,6 +9933,11 @@ function printLocalDryRun(source, currentVersion, inTreeSource) {
|
|
|
9833
9933
|
` Would check for a migration for ${source.manifest.tables.length} table(s) (generated for a new table or an added column on an already-migrated table; a removed/retyped/nullability-changed column stops the refresh instead \u2014 #1539)`
|
|
9834
9934
|
);
|
|
9835
9935
|
}
|
|
9936
|
+
if (source.manifest.seed) {
|
|
9937
|
+
console.log(
|
|
9938
|
+
` Would re-vendor seed DDL into: ${pluginSeedImportDir(source.name)}/ (baseline_tables: ${source.manifest.seed.baseline_tables.join(", ") || "none declared"})`
|
|
9939
|
+
);
|
|
9940
|
+
}
|
|
9836
9941
|
console.log(` Would commit: chore(plugins): refresh ${source.name} from local checkout
|
|
9837
9942
|
`);
|
|
9838
9943
|
}
|
|
@@ -9852,7 +9957,7 @@ pluginCommand.addCommand(pluginStalenessCommand);
|
|
|
9852
9957
|
import { Command as Command23 } from "commander";
|
|
9853
9958
|
|
|
9854
9959
|
// src/commands/sibling-check-identity.ts
|
|
9855
|
-
import { existsSync as
|
|
9960
|
+
import { existsSync as existsSync36, readFileSync as readFileSync27 } from "fs";
|
|
9856
9961
|
import { resolve as resolve18 } from "path";
|
|
9857
9962
|
import chalk20 from "chalk";
|
|
9858
9963
|
import { Command as Command22 } from "commander";
|
|
@@ -10066,7 +10171,7 @@ async function resolveConfig4(options) {
|
|
|
10066
10171
|
return cfg;
|
|
10067
10172
|
}
|
|
10068
10173
|
const localConfigPath = resolve18(process.cwd(), "biffo.config.json");
|
|
10069
|
-
if (
|
|
10174
|
+
if (existsSync36(localConfigPath)) {
|
|
10070
10175
|
const raw = JSON.parse(readFileSync27(localConfigPath, "utf8"));
|
|
10071
10176
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
10072
10177
|
if (result.success) return result.data;
|
|
@@ -10113,19 +10218,19 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
|
10113
10218
|
import { Command as Command24 } from "commander";
|
|
10114
10219
|
|
|
10115
10220
|
// src/scripts/check-adr-numbering.ts
|
|
10116
|
-
import { existsSync as
|
|
10117
|
-
import { join as
|
|
10221
|
+
import { existsSync as existsSync38 } from "fs";
|
|
10222
|
+
import { join as join39 } from "path";
|
|
10118
10223
|
import { execa as execa7 } from "execa";
|
|
10119
10224
|
|
|
10120
10225
|
// src/lib/adr-numbering-guard.ts
|
|
10121
|
-
import { existsSync as
|
|
10122
|
-
import { join as
|
|
10226
|
+
import { existsSync as existsSync37, readdirSync as readdirSync15, readFileSync as readFileSync28 } from "fs";
|
|
10227
|
+
import { join as join38 } from "path";
|
|
10123
10228
|
var ADR_FILENAME = /^(\d{4})-.+\.md$/;
|
|
10124
10229
|
var ALLOWLIST_FILENAME = ".numbering-allowlist";
|
|
10125
10230
|
var TEMPLATE_ADR_RESERVED_UPTO = "0099";
|
|
10126
10231
|
function readAdrNumberingAllowlist(adrDir) {
|
|
10127
|
-
const path =
|
|
10128
|
-
if (!
|
|
10232
|
+
const path = join38(adrDir, ALLOWLIST_FILENAME);
|
|
10233
|
+
if (!existsSync37(path)) return /* @__PURE__ */ new Set();
|
|
10129
10234
|
const numbers = /* @__PURE__ */ new Set();
|
|
10130
10235
|
for (const rawLine of readFileSync28(path, "utf8").split("\n")) {
|
|
10131
10236
|
const line = rawLine.split("#")[0].trim();
|
|
@@ -10135,8 +10240,8 @@ function readAdrNumberingAllowlist(adrDir) {
|
|
|
10135
10240
|
}
|
|
10136
10241
|
function adrNumbersIn(adrDir) {
|
|
10137
10242
|
const claims = /* @__PURE__ */ new Map();
|
|
10138
|
-
if (!
|
|
10139
|
-
for (const entry of
|
|
10243
|
+
if (!existsSync37(adrDir)) return claims;
|
|
10244
|
+
for (const entry of readdirSync15(adrDir).sort()) {
|
|
10140
10245
|
const match = ADR_FILENAME.exec(entry);
|
|
10141
10246
|
if (!match) continue;
|
|
10142
10247
|
const number = match[1];
|
|
@@ -10190,8 +10295,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
|
|
|
10190
10295
|
// src/scripts/check-adr-numbering.ts
|
|
10191
10296
|
async function runAdrNumberingCheck() {
|
|
10192
10297
|
const root = (await execa7("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10193
|
-
const adrDir =
|
|
10194
|
-
if (!
|
|
10298
|
+
const adrDir = join39(root, "docs", "ADR");
|
|
10299
|
+
if (!existsSync38(adrDir)) {
|
|
10195
10300
|
console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
|
|
10196
10301
|
return;
|
|
10197
10302
|
}
|
|
@@ -10489,13 +10594,13 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
|
|
|
10489
10594
|
}
|
|
10490
10595
|
|
|
10491
10596
|
// src/scripts/check-codeql-suppression.ts
|
|
10492
|
-
import { existsSync as
|
|
10493
|
-
import { join as
|
|
10597
|
+
import { existsSync as existsSync39 } from "fs";
|
|
10598
|
+
import { join as join41, relative as relative7 } from "path";
|
|
10494
10599
|
import { execa as execa9 } from "execa";
|
|
10495
10600
|
|
|
10496
10601
|
// src/lib/codeql-suppression-guard.ts
|
|
10497
|
-
import { readdirSync as
|
|
10498
|
-
import { join as
|
|
10602
|
+
import { readdirSync as readdirSync16, readFileSync as readFileSync29, statSync as statSync8 } from "fs";
|
|
10603
|
+
import { join as join40 } from "path";
|
|
10499
10604
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10500
10605
|
".git",
|
|
10501
10606
|
".worktrees",
|
|
@@ -10521,12 +10626,12 @@ function walkSourceFiles(root) {
|
|
|
10521
10626
|
const walk2 = (dir) => {
|
|
10522
10627
|
let entries;
|
|
10523
10628
|
try {
|
|
10524
|
-
entries =
|
|
10629
|
+
entries = readdirSync16(dir);
|
|
10525
10630
|
} catch {
|
|
10526
10631
|
return;
|
|
10527
10632
|
}
|
|
10528
10633
|
for (const entry of entries) {
|
|
10529
|
-
const p =
|
|
10634
|
+
const p = join40(dir, entry);
|
|
10530
10635
|
let st;
|
|
10531
10636
|
try {
|
|
10532
10637
|
st = statSync8(p);
|
|
@@ -10563,8 +10668,8 @@ function sweepCodeqlSuppressionComments(root) {
|
|
|
10563
10668
|
// src/scripts/check-codeql-suppression.ts
|
|
10564
10669
|
async function runCodeqlSuppressionCheck() {
|
|
10565
10670
|
const root = (await execa9("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10566
|
-
const scanRoot =
|
|
10567
|
-
if (!
|
|
10671
|
+
const scanRoot = join41(root, "cli", "src");
|
|
10672
|
+
if (!existsSync39(scanRoot)) {
|
|
10568
10673
|
console.log(
|
|
10569
10674
|
"\u2014 codeql-suppression guard: skipped \u2014 no cli/src in this repo, so there is no CLI source to scan."
|
|
10570
10675
|
);
|
|
@@ -10591,8 +10696,8 @@ async function runCodeqlSuppressionCheck() {
|
|
|
10591
10696
|
import { execa as execa10 } from "execa";
|
|
10592
10697
|
|
|
10593
10698
|
// src/lib/cognito-invite-template-guard.ts
|
|
10594
|
-
import { readdirSync as
|
|
10595
|
-
import { join as
|
|
10699
|
+
import { readdirSync as readdirSync17, readFileSync as readFileSync30, statSync as statSync9 } from "fs";
|
|
10700
|
+
import { join as join42 } from "path";
|
|
10596
10701
|
var REQUIRED_INVITE_MEMBERS = ["email_subject", "email_message", "sms_message"];
|
|
10597
10702
|
var REQUIRED_INVITE_PLACEHOLDERS = ["{username}", "{####}"];
|
|
10598
10703
|
var PLACEHOLDER_MEMBERS = ["email_message", "sms_message"];
|
|
@@ -10669,13 +10774,13 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
10669
10774
|
const walk2 = (dir, relative10) => {
|
|
10670
10775
|
let entries;
|
|
10671
10776
|
try {
|
|
10672
|
-
entries =
|
|
10777
|
+
entries = readdirSync17(dir);
|
|
10673
10778
|
} catch {
|
|
10674
10779
|
return;
|
|
10675
10780
|
}
|
|
10676
10781
|
for (const entry of entries) {
|
|
10677
10782
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees") continue;
|
|
10678
|
-
const full =
|
|
10783
|
+
const full = join42(dir, entry);
|
|
10679
10784
|
const rel = `${relative10}/${entry}`;
|
|
10680
10785
|
if (statSync9(full).isDirectory()) {
|
|
10681
10786
|
walk2(full, rel);
|
|
@@ -10684,12 +10789,12 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
10684
10789
|
}
|
|
10685
10790
|
}
|
|
10686
10791
|
};
|
|
10687
|
-
walk2(
|
|
10792
|
+
walk2(join42(repoRoot, "modules"), "modules");
|
|
10688
10793
|
return found.sort();
|
|
10689
10794
|
}
|
|
10690
10795
|
function checkCognitoInviteTemplates(repoRoot) {
|
|
10691
10796
|
return findModuleTerraformFiles(repoRoot).flatMap(
|
|
10692
|
-
(file) => checkInviteTemplateSource(file, readFileSync30(
|
|
10797
|
+
(file) => checkInviteTemplateSource(file, readFileSync30(join42(repoRoot, file), "utf8"))
|
|
10693
10798
|
);
|
|
10694
10799
|
}
|
|
10695
10800
|
|
|
@@ -10717,12 +10822,12 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
10717
10822
|
}
|
|
10718
10823
|
|
|
10719
10824
|
// src/scripts/check-core-direct-paths.ts
|
|
10720
|
-
import { join as
|
|
10825
|
+
import { join as join44 } from "path";
|
|
10721
10826
|
import { execa as execa11 } from "execa";
|
|
10722
10827
|
|
|
10723
10828
|
// src/lib/core-direct-paths-audit.ts
|
|
10724
|
-
import { existsSync as
|
|
10725
|
-
import { join as
|
|
10829
|
+
import { existsSync as existsSync40, readFileSync as readFileSync31, readdirSync as readdirSync18, statSync as statSync10 } from "fs";
|
|
10830
|
+
import { join as join43 } from "path";
|
|
10726
10831
|
var EXTERNAL_BASE_IDENTIFIERS = ["CORE_API_URL"];
|
|
10727
10832
|
var API_ROUTE_PREFIX = "/api/v1";
|
|
10728
10833
|
var TEST_FILE_SUFFIXES = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
|
|
@@ -10881,12 +10986,12 @@ function walkFiles(root, accept, skipDir) {
|
|
|
10881
10986
|
const walk2 = (dir) => {
|
|
10882
10987
|
let entries;
|
|
10883
10988
|
try {
|
|
10884
|
-
entries =
|
|
10989
|
+
entries = readdirSync18(dir);
|
|
10885
10990
|
} catch {
|
|
10886
10991
|
return;
|
|
10887
10992
|
}
|
|
10888
10993
|
for (const entry of entries) {
|
|
10889
|
-
const p =
|
|
10994
|
+
const p = join43(dir, entry);
|
|
10890
10995
|
let st;
|
|
10891
10996
|
try {
|
|
10892
10997
|
st = statSync10(p);
|
|
@@ -10981,7 +11086,7 @@ function pathMatchesAnyCorePrefix(normalized, corePrefixes, apiRoutePrefix = API
|
|
|
10981
11086
|
}
|
|
10982
11087
|
function resolveSiblingCoreSrc(params) {
|
|
10983
11088
|
const { estateDir, sibling } = params;
|
|
10984
|
-
const configPath =
|
|
11089
|
+
const configPath = join43(estateDir, sibling, "biffo.sibling.json");
|
|
10985
11090
|
let raw;
|
|
10986
11091
|
try {
|
|
10987
11092
|
raw = readFileSync31(configPath, "utf8");
|
|
@@ -11004,8 +11109,8 @@ function resolveSiblingCoreSrc(params) {
|
|
|
11004
11109
|
`cannot resolve ${sibling}'s core: ${configPath} has no non-empty "core_project" field.`
|
|
11005
11110
|
);
|
|
11006
11111
|
}
|
|
11007
|
-
const coreApiSrcDir =
|
|
11008
|
-
if (!
|
|
11112
|
+
const coreApiSrcDir = join43(estateDir, coreProject, "services", "api", "src");
|
|
11113
|
+
if (!existsSync40(coreApiSrcDir)) {
|
|
11009
11114
|
throw new Error(
|
|
11010
11115
|
`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.`
|
|
11011
11116
|
);
|
|
@@ -11048,7 +11153,7 @@ function auditSiblingCoreDirectPaths(params) {
|
|
|
11048
11153
|
async function runCoreDirectPathsCheck(opts = {}) {
|
|
11049
11154
|
const root = (await execa11("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11050
11155
|
const sibling = opts.sibling ?? "sibling-template (self-check)";
|
|
11051
|
-
const frontendSrcDir = opts.frontendSrc ??
|
|
11156
|
+
const frontendSrcDir = opts.frontendSrc ?? join44(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
|
|
11052
11157
|
let coreApiSrcDir;
|
|
11053
11158
|
let coreProject = null;
|
|
11054
11159
|
if (opts.coreSrc) {
|
|
@@ -11064,7 +11169,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
11064
11169
|
coreApiSrcDir = resolution.coreApiSrcDir;
|
|
11065
11170
|
coreProject = resolution.coreProject;
|
|
11066
11171
|
} else {
|
|
11067
|
-
coreApiSrcDir =
|
|
11172
|
+
coreApiSrcDir = join44(root, "services", "api", "src");
|
|
11068
11173
|
}
|
|
11069
11174
|
const report = auditSiblingCoreDirectPaths({ sibling, frontendSrcDir, coreApiSrcDir });
|
|
11070
11175
|
console.log(
|
|
@@ -11130,8 +11235,8 @@ async function runOwnershipCheck(argv) {
|
|
|
11130
11235
|
const { stdout } = await execa12("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
11131
11236
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
11132
11237
|
if (messageFile) {
|
|
11133
|
-
const { readFileSync: readFileSync41, existsSync:
|
|
11134
|
-
if (
|
|
11238
|
+
const { readFileSync: readFileSync41, existsSync: existsSync49 } = await import("fs");
|
|
11239
|
+
if (existsSync49(messageFile)) commitMessage = readFileSync41(messageFile, "utf8");
|
|
11135
11240
|
}
|
|
11136
11241
|
} else {
|
|
11137
11242
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -11235,8 +11340,8 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
11235
11340
|
import { execa as execa13 } from "execa";
|
|
11236
11341
|
|
|
11237
11342
|
// src/lib/eventbridge-log-permission-guard.ts
|
|
11238
|
-
import { readFileSync as readFileSync32, readdirSync as
|
|
11239
|
-
import { join as
|
|
11343
|
+
import { readFileSync as readFileSync32, readdirSync as readdirSync19, statSync as statSync11 } from "fs";
|
|
11344
|
+
import { join as join45 } from "path";
|
|
11240
11345
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
11241
11346
|
var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
|
|
11242
11347
|
var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
|
|
@@ -11308,12 +11413,12 @@ function walkTerraformFiles(root) {
|
|
|
11308
11413
|
const walk2 = (dir) => {
|
|
11309
11414
|
let entries;
|
|
11310
11415
|
try {
|
|
11311
|
-
entries =
|
|
11416
|
+
entries = readdirSync19(dir);
|
|
11312
11417
|
} catch {
|
|
11313
11418
|
return;
|
|
11314
11419
|
}
|
|
11315
11420
|
for (const entry of entries) {
|
|
11316
|
-
const p =
|
|
11421
|
+
const p = join45(dir, entry);
|
|
11317
11422
|
let st;
|
|
11318
11423
|
try {
|
|
11319
11424
|
st = statSync11(p);
|
|
@@ -11450,11 +11555,11 @@ import { execa as execa14 } from "execa";
|
|
|
11450
11555
|
|
|
11451
11556
|
// src/lib/lambda-output-guard.ts
|
|
11452
11557
|
import { readFileSync as readFileSync34 } from "fs";
|
|
11453
|
-
import { join as
|
|
11558
|
+
import { join as join47 } from "path";
|
|
11454
11559
|
|
|
11455
11560
|
// src/lib/terraform-input-guard.ts
|
|
11456
|
-
import { readdirSync as
|
|
11457
|
-
import { join as
|
|
11561
|
+
import { readdirSync as readdirSync20, readFileSync as readFileSync33, statSync as statSync12 } from "fs";
|
|
11562
|
+
import { join as join46 } from "path";
|
|
11458
11563
|
var GUARDED_SUBCOMMANDS = [
|
|
11459
11564
|
"init",
|
|
11460
11565
|
"plan",
|
|
@@ -11472,13 +11577,13 @@ function findWorkflowFiles(repoRoot) {
|
|
|
11472
11577
|
const walk2 = (dir, relative10) => {
|
|
11473
11578
|
let entries;
|
|
11474
11579
|
try {
|
|
11475
|
-
entries =
|
|
11580
|
+
entries = readdirSync20(dir);
|
|
11476
11581
|
} catch {
|
|
11477
11582
|
return;
|
|
11478
11583
|
}
|
|
11479
11584
|
for (const entry of entries) {
|
|
11480
11585
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees") continue;
|
|
11481
|
-
const full =
|
|
11586
|
+
const full = join46(dir, entry);
|
|
11482
11587
|
const rel = relative10 ? `${relative10}/${entry}` : entry;
|
|
11483
11588
|
if (statSync12(full).isDirectory()) {
|
|
11484
11589
|
walk2(full, rel);
|
|
@@ -11522,7 +11627,7 @@ function checkWorkflowSource(file, rawSource) {
|
|
|
11522
11627
|
}
|
|
11523
11628
|
function checkTerraformInput(repoRoot) {
|
|
11524
11629
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
11525
|
-
(file) => checkWorkflowSource(file, readFileSync33(
|
|
11630
|
+
(file) => checkWorkflowSource(file, readFileSync33(join46(repoRoot, file), "utf8"))
|
|
11526
11631
|
);
|
|
11527
11632
|
}
|
|
11528
11633
|
|
|
@@ -11580,7 +11685,7 @@ function checkWorkflowSource2(file, rawSource) {
|
|
|
11580
11685
|
}
|
|
11581
11686
|
function checkLambdaOutput(repoRoot) {
|
|
11582
11687
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
11583
|
-
(file) => checkWorkflowSource2(file, readFileSync34(
|
|
11688
|
+
(file) => checkWorkflowSource2(file, readFileSync34(join47(repoRoot, file), "utf8"))
|
|
11584
11689
|
);
|
|
11585
11690
|
}
|
|
11586
11691
|
|
|
@@ -11608,8 +11713,8 @@ async function runLambdaOutputCheck() {
|
|
|
11608
11713
|
}
|
|
11609
11714
|
|
|
11610
11715
|
// src/scripts/check-pipe-trap.ts
|
|
11611
|
-
import { readFileSync as readFileSync35, readdirSync as
|
|
11612
|
-
import { join as
|
|
11716
|
+
import { readFileSync as readFileSync35, readdirSync as readdirSync21 } from "fs";
|
|
11717
|
+
import { join as join48, relative as relative8 } from "path";
|
|
11613
11718
|
import { execa as execa15 } from "execa";
|
|
11614
11719
|
|
|
11615
11720
|
// src/lib/pipe-trap-guard.ts
|
|
@@ -11706,17 +11811,17 @@ function findPipeTraps(source) {
|
|
|
11706
11811
|
function shellFiles(root) {
|
|
11707
11812
|
const out = [];
|
|
11708
11813
|
for (const dir of ["scripts", ".githooks"]) {
|
|
11709
|
-
const full =
|
|
11814
|
+
const full = join48(root, dir);
|
|
11710
11815
|
let entries;
|
|
11711
11816
|
try {
|
|
11712
|
-
entries =
|
|
11817
|
+
entries = readdirSync21(full, { withFileTypes: true });
|
|
11713
11818
|
} catch {
|
|
11714
11819
|
continue;
|
|
11715
11820
|
}
|
|
11716
11821
|
for (const entry of entries) {
|
|
11717
11822
|
if (!entry.isFile()) continue;
|
|
11718
11823
|
if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
|
|
11719
|
-
out.push(
|
|
11824
|
+
out.push(join48(full, entry.name));
|
|
11720
11825
|
}
|
|
11721
11826
|
}
|
|
11722
11827
|
return out;
|
|
@@ -11753,7 +11858,7 @@ import { execa as execa16 } from "execa";
|
|
|
11753
11858
|
|
|
11754
11859
|
// src/lib/plugin-allowlist-convention.ts
|
|
11755
11860
|
import { readFileSync as readFileSync36 } from "fs";
|
|
11756
|
-
import { join as
|
|
11861
|
+
import { join as join49 } from "path";
|
|
11757
11862
|
var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
|
|
11758
11863
|
var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
|
|
11759
11864
|
var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
|
|
@@ -11764,7 +11869,7 @@ var PLUGIN = "<plugin>";
|
|
|
11764
11869
|
var ACCOUNT = "<account>";
|
|
11765
11870
|
function read(repoRoot, relative10) {
|
|
11766
11871
|
try {
|
|
11767
|
-
return readFileSync36(
|
|
11872
|
+
return readFileSync36(join49(repoRoot, relative10), "utf8");
|
|
11768
11873
|
} catch {
|
|
11769
11874
|
throw new Error(`plugin-allowlist drift guard: cannot read ${relative10}`);
|
|
11770
11875
|
}
|
|
@@ -11885,34 +11990,34 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
11885
11990
|
}
|
|
11886
11991
|
|
|
11887
11992
|
// src/scripts/check-plugin-collisions.ts
|
|
11888
|
-
import { existsSync as
|
|
11889
|
-
import { join as
|
|
11993
|
+
import { existsSync as existsSync42 } from "fs";
|
|
11994
|
+
import { join as join51 } from "path";
|
|
11890
11995
|
import { execa as execa17 } from "execa";
|
|
11891
11996
|
|
|
11892
11997
|
// src/lib/plugin-collision-guard.ts
|
|
11893
|
-
import { existsSync as
|
|
11894
|
-
import { join as
|
|
11998
|
+
import { existsSync as existsSync41, readdirSync as readdirSync22, statSync as statSync13 } from "fs";
|
|
11999
|
+
import { join as join50 } from "path";
|
|
11895
12000
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
11896
12001
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
11897
12002
|
function subdirectories(dir) {
|
|
11898
|
-
if (!
|
|
11899
|
-
return
|
|
12003
|
+
if (!existsSync41(dir)) return [];
|
|
12004
|
+
return readdirSync22(dir).filter((entry) => {
|
|
11900
12005
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
11901
12006
|
try {
|
|
11902
|
-
return statSync13(
|
|
12007
|
+
return statSync13(join50(dir, entry)).isDirectory();
|
|
11903
12008
|
} catch {
|
|
11904
12009
|
return false;
|
|
11905
12010
|
}
|
|
11906
12011
|
});
|
|
11907
12012
|
}
|
|
11908
12013
|
function regularPackagesOf(pluginDir2) {
|
|
11909
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
12014
|
+
return subdirectories(pluginDir2).filter((name) => existsSync41(join50(pluginDir2, name, "__init__.py"))).sort();
|
|
11910
12015
|
}
|
|
11911
12016
|
function bareTestModulesOf(pluginDir2) {
|
|
11912
|
-
const testsDir =
|
|
11913
|
-
if (!
|
|
11914
|
-
if (
|
|
11915
|
-
return
|
|
12017
|
+
const testsDir = join50(pluginDir2, "tests");
|
|
12018
|
+
if (!existsSync41(testsDir)) return [];
|
|
12019
|
+
if (existsSync41(join50(testsDir, "__init__.py"))) return [];
|
|
12020
|
+
return readdirSync22(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
11916
12021
|
}
|
|
11917
12022
|
function findCollisions(servicesDir, pluginDirs) {
|
|
11918
12023
|
const plugins = (pluginDirs ?? subdirectories(servicesDir)).filter((name) => !name.startsWith("_")).filter((name) => name !== "api").sort();
|
|
@@ -11920,7 +12025,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
11920
12025
|
const gather = (kind, namesOf) => {
|
|
11921
12026
|
const claims = /* @__PURE__ */ new Map();
|
|
11922
12027
|
for (const plugin of plugins) {
|
|
11923
|
-
for (const name of namesOf(
|
|
12028
|
+
for (const name of namesOf(join50(servicesDir, plugin))) {
|
|
11924
12029
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
11925
12030
|
}
|
|
11926
12031
|
}
|
|
@@ -11958,8 +12063,8 @@ function formatCollisions(collisions) {
|
|
|
11958
12063
|
// src/scripts/check-plugin-collisions.ts
|
|
11959
12064
|
async function runPluginCollisionCheck() {
|
|
11960
12065
|
const root = (await execa17("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11961
|
-
const servicesDir =
|
|
11962
|
-
if (!
|
|
12066
|
+
const servicesDir = join51(root, "services");
|
|
12067
|
+
if (!existsSync42(servicesDir)) {
|
|
11963
12068
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
11964
12069
|
return;
|
|
11965
12070
|
}
|
|
@@ -11979,8 +12084,8 @@ async function runPluginCollisionCheck() {
|
|
|
11979
12084
|
import { execa as execa18 } from "execa";
|
|
11980
12085
|
|
|
11981
12086
|
// src/lib/plugin-terraform-guard.ts
|
|
11982
|
-
import { existsSync as
|
|
11983
|
-
import { dirname as dirname10, join as
|
|
12087
|
+
import { existsSync as existsSync43, readFileSync as readFileSync37, readdirSync as readdirSync23 } from "fs";
|
|
12088
|
+
import { dirname as dirname10, join as join52, relative as relative9, sep as sep3 } from "path";
|
|
11984
12089
|
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
11985
12090
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
11986
12091
|
function findPluginManifests(root) {
|
|
@@ -11988,16 +12093,16 @@ function findPluginManifests(root) {
|
|
|
11988
12093
|
const walk2 = (dir) => {
|
|
11989
12094
|
let entries;
|
|
11990
12095
|
try {
|
|
11991
|
-
entries =
|
|
12096
|
+
entries = readdirSync23(dir, { withFileTypes: true });
|
|
11992
12097
|
} catch {
|
|
11993
12098
|
return;
|
|
11994
12099
|
}
|
|
11995
12100
|
for (const entry of entries) {
|
|
11996
12101
|
if (entry.isDirectory()) {
|
|
11997
12102
|
if (SKIP_DIRS3.has(entry.name)) continue;
|
|
11998
|
-
walk2(
|
|
12103
|
+
walk2(join52(dir, entry.name));
|
|
11999
12104
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
12000
|
-
found.push(relative9(root,
|
|
12105
|
+
found.push(relative9(root, join52(dir, entry.name)).split(sep3).join("/"));
|
|
12001
12106
|
}
|
|
12002
12107
|
}
|
|
12003
12108
|
};
|
|
@@ -12022,14 +12127,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
12022
12127
|
}
|
|
12023
12128
|
function checkPluginTerraform(root) {
|
|
12024
12129
|
const violations = [];
|
|
12025
|
-
const coreManifest =
|
|
12130
|
+
const coreManifest = existsSync43(join52(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
12026
12131
|
for (const manifest of findPluginManifests(root)) {
|
|
12027
12132
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
12028
|
-
const absManifest =
|
|
12133
|
+
const absManifest = join52(root, manifest);
|
|
12029
12134
|
const subscriptions = readSubscriptions(absManifest);
|
|
12030
12135
|
if (subscriptions === null) continue;
|
|
12031
12136
|
const pluginDir2 = dirname10(absManifest);
|
|
12032
|
-
if (
|
|
12137
|
+
if (existsSync43(join52(pluginDir2, "terraform"))) continue;
|
|
12033
12138
|
const relPluginDir = relative9(root, pluginDir2).split(sep3).join("/");
|
|
12034
12139
|
violations.push({
|
|
12035
12140
|
manifest,
|
|
@@ -12060,13 +12165,13 @@ async function runPluginTerraformCheck() {
|
|
|
12060
12165
|
}
|
|
12061
12166
|
|
|
12062
12167
|
// src/scripts/check-plugin-tool-supply.ts
|
|
12063
|
-
import { existsSync as
|
|
12064
|
-
import { join as
|
|
12168
|
+
import { existsSync as existsSync45 } from "fs";
|
|
12169
|
+
import { join as join54 } from "path";
|
|
12065
12170
|
import { execa as execa19 } from "execa";
|
|
12066
12171
|
|
|
12067
12172
|
// src/lib/plugin-tool-supply-audit.ts
|
|
12068
|
-
import { existsSync as
|
|
12069
|
-
import { join as
|
|
12173
|
+
import { existsSync as existsSync44, readFileSync as readFileSync38, readdirSync as readdirSync24, statSync as statSync14 } from "fs";
|
|
12174
|
+
import { join as join53 } from "path";
|
|
12070
12175
|
|
|
12071
12176
|
// src/lib/openrouter-model-snapshot.ts
|
|
12072
12177
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -12477,13 +12582,13 @@ var OPENROUTER_MODEL_IDS = [
|
|
|
12477
12582
|
function listDirs(root) {
|
|
12478
12583
|
let entries;
|
|
12479
12584
|
try {
|
|
12480
|
-
entries =
|
|
12585
|
+
entries = readdirSync24(root);
|
|
12481
12586
|
} catch {
|
|
12482
12587
|
return [];
|
|
12483
12588
|
}
|
|
12484
12589
|
return entries.filter((e) => {
|
|
12485
12590
|
try {
|
|
12486
|
-
return statSync14(
|
|
12591
|
+
return statSync14(join53(root, e)).isDirectory();
|
|
12487
12592
|
} catch {
|
|
12488
12593
|
return false;
|
|
12489
12594
|
}
|
|
@@ -12494,12 +12599,12 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
12494
12599
|
const walk2 = (dir) => {
|
|
12495
12600
|
let entries;
|
|
12496
12601
|
try {
|
|
12497
|
-
entries =
|
|
12602
|
+
entries = readdirSync24(dir);
|
|
12498
12603
|
} catch {
|
|
12499
12604
|
return;
|
|
12500
12605
|
}
|
|
12501
12606
|
for (const entry of entries) {
|
|
12502
|
-
const p =
|
|
12607
|
+
const p = join53(dir, entry);
|
|
12503
12608
|
let st;
|
|
12504
12609
|
try {
|
|
12505
12610
|
st = statSync14(p);
|
|
@@ -12525,14 +12630,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
12525
12630
|
);
|
|
12526
12631
|
}
|
|
12527
12632
|
function pluginTerraformFiles(pluginDir2) {
|
|
12528
|
-
const tfDir =
|
|
12633
|
+
const tfDir = join53(pluginDir2, "terraform");
|
|
12529
12634
|
let entries;
|
|
12530
12635
|
try {
|
|
12531
|
-
entries =
|
|
12636
|
+
entries = readdirSync24(tfDir);
|
|
12532
12637
|
} catch {
|
|
12533
12638
|
return [];
|
|
12534
12639
|
}
|
|
12535
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
12640
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join53(tfDir, e)).sort();
|
|
12536
12641
|
}
|
|
12537
12642
|
function extractManifestTools(manifestText) {
|
|
12538
12643
|
let parsed;
|
|
@@ -12784,8 +12889,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
12784
12889
|
function normalizeModelId(id) {
|
|
12785
12890
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
12786
12891
|
}
|
|
12787
|
-
var CONFIG_PY_PATH =
|
|
12788
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
12892
|
+
var CONFIG_PY_PATH = join53("services", "api", "src", "api", "config.py");
|
|
12893
|
+
var ORCHESTRATION_SCHEMA_PATH = join53(
|
|
12789
12894
|
"services",
|
|
12790
12895
|
"api",
|
|
12791
12896
|
"src",
|
|
@@ -12797,10 +12902,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
12797
12902
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
12798
12903
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
12799
12904
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
12800
|
-
const configPath =
|
|
12801
|
-
const orchestrationPath =
|
|
12802
|
-
const configMissing = !
|
|
12803
|
-
const orchestrationSchemaMissing = !
|
|
12905
|
+
const configPath = join53(repoRoot, CONFIG_PY_PATH);
|
|
12906
|
+
const orchestrationPath = join53(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
12907
|
+
const configMissing = !existsSync44(configPath);
|
|
12908
|
+
const orchestrationSchemaMissing = !existsSync44(orchestrationPath);
|
|
12804
12909
|
const knownSet = new Set(knownModelIds);
|
|
12805
12910
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
12806
12911
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -12871,7 +12976,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
12871
12976
|
function discoverPluginDirs(pluginsRoot) {
|
|
12872
12977
|
return listDirs(pluginsRoot).filter((name) => {
|
|
12873
12978
|
try {
|
|
12874
|
-
return statSync14(
|
|
12979
|
+
return statSync14(join53(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
12875
12980
|
} catch {
|
|
12876
12981
|
return false;
|
|
12877
12982
|
}
|
|
@@ -12884,8 +12989,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
12884
12989
|
let terraformBlind = false;
|
|
12885
12990
|
let totalDeclaredTools = 0;
|
|
12886
12991
|
for (const name of pluginNames) {
|
|
12887
|
-
const pluginDir2 =
|
|
12888
|
-
const manifestText = readFileSync38(
|
|
12992
|
+
const pluginDir2 = join53(pluginsRoot, name);
|
|
12993
|
+
const manifestText = readFileSync38(join53(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
12889
12994
|
const manifest = extractManifestTools(manifestText);
|
|
12890
12995
|
if (manifest.parseError) {
|
|
12891
12996
|
findings.push({
|
|
@@ -12983,7 +13088,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
12983
13088
|
requiredEnvVars: envResult.envVars,
|
|
12984
13089
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
12985
13090
|
status: anyWired ? "ok" : "missing-env",
|
|
12986
|
-
detail: anyWired ? `${entry.predicate}() is satisfiable: at least one of ${JSON.stringify(envResult.envVars)} is wired in Terraform` : `${entry.predicate}() reads ${JSON.stringify(envResult.envVars)} \u2014 NONE of these are wired by any environment_variables block under ${
|
|
13091
|
+
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 ${join53(pluginDir2, "terraform")}, so this deployment can never supply it`
|
|
12987
13092
|
});
|
|
12988
13093
|
}
|
|
12989
13094
|
}
|
|
@@ -13016,8 +13121,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
13016
13121
|
async function runPluginToolSupplyCheck() {
|
|
13017
13122
|
const root = (await execa19("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13018
13123
|
let allOk = true;
|
|
13019
|
-
const pluginsRoot =
|
|
13020
|
-
if (!
|
|
13124
|
+
const pluginsRoot = join54(root, "services", "_plugins");
|
|
13125
|
+
if (!existsSync45(pluginsRoot)) {
|
|
13021
13126
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
13022
13127
|
} else {
|
|
13023
13128
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -13047,8 +13152,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
13047
13152
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
13048
13153
|
}
|
|
13049
13154
|
}
|
|
13050
|
-
const servicesApiRoot =
|
|
13051
|
-
if (!
|
|
13155
|
+
const servicesApiRoot = join54(root, "services", "api");
|
|
13156
|
+
if (!existsSync45(servicesApiRoot)) {
|
|
13052
13157
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
13053
13158
|
} else {
|
|
13054
13159
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -13225,13 +13330,13 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
13225
13330
|
}
|
|
13226
13331
|
|
|
13227
13332
|
// src/scripts/check-skeleton-drift.ts
|
|
13228
|
-
import { existsSync as
|
|
13229
|
-
import { join as
|
|
13333
|
+
import { existsSync as existsSync46, readdirSync as readdirSync26 } from "fs";
|
|
13334
|
+
import { join as join56 } from "path";
|
|
13230
13335
|
import { execa as execa21 } from "execa";
|
|
13231
13336
|
|
|
13232
13337
|
// src/lib/skeleton-drift-guard.ts
|
|
13233
|
-
import { readFileSync as readFileSync39, readdirSync as
|
|
13234
|
-
import { join as
|
|
13338
|
+
import { readFileSync as readFileSync39, readdirSync as readdirSync25, statSync as statSync15 } from "fs";
|
|
13339
|
+
import { join as join55 } from "path";
|
|
13235
13340
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
13236
13341
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
13237
13342
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -13289,13 +13394,13 @@ function walk(dir, base = dir) {
|
|
|
13289
13394
|
const out = [];
|
|
13290
13395
|
let entries;
|
|
13291
13396
|
try {
|
|
13292
|
-
entries =
|
|
13397
|
+
entries = readdirSync25(dir);
|
|
13293
13398
|
} catch {
|
|
13294
13399
|
return out;
|
|
13295
13400
|
}
|
|
13296
13401
|
for (const entry of entries) {
|
|
13297
13402
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
13298
|
-
const abs =
|
|
13403
|
+
const abs = join55(dir, entry);
|
|
13299
13404
|
let isDir;
|
|
13300
13405
|
try {
|
|
13301
13406
|
isDir = statSync15(abs).isDirectory();
|
|
@@ -13317,7 +13422,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
13317
13422
|
if (!rule.appliesTo(rel)) continue;
|
|
13318
13423
|
let contents;
|
|
13319
13424
|
try {
|
|
13320
|
-
contents = readFileSync39(
|
|
13425
|
+
contents = readFileSync39(join55(skeletonRoot, rel), "utf8");
|
|
13321
13426
|
} catch {
|
|
13322
13427
|
continue;
|
|
13323
13428
|
}
|
|
@@ -13346,23 +13451,23 @@ function formatViolations2(violations) {
|
|
|
13346
13451
|
|
|
13347
13452
|
// src/scripts/check-skeleton-drift.ts
|
|
13348
13453
|
function discoverSkeletons(root) {
|
|
13349
|
-
const skeletonsDir =
|
|
13454
|
+
const skeletonsDir = join56(root, "_skeletons");
|
|
13350
13455
|
let entries;
|
|
13351
13456
|
try {
|
|
13352
|
-
entries =
|
|
13457
|
+
entries = readdirSync26(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
13353
13458
|
} catch {
|
|
13354
13459
|
return [];
|
|
13355
13460
|
}
|
|
13356
|
-
return entries.filter((name) =>
|
|
13461
|
+
return entries.filter((name) => existsSync46(join56(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
13357
13462
|
}
|
|
13358
13463
|
async function runSkeletonDriftCheck() {
|
|
13359
13464
|
const root = (await execa21("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13360
13465
|
const skeletons = discoverSkeletons(root);
|
|
13361
13466
|
let filesConsidered = 0;
|
|
13362
13467
|
for (const name of skeletons) {
|
|
13363
|
-
const skeletonRoot =
|
|
13468
|
+
const skeletonRoot = join56(root, "_skeletons", name);
|
|
13364
13469
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
13365
|
-
if (
|
|
13470
|
+
if (existsSync46(join56(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
13366
13471
|
filesConsidered += 1;
|
|
13367
13472
|
}
|
|
13368
13473
|
}
|
|
@@ -13376,7 +13481,7 @@ async function runSkeletonDriftCheck() {
|
|
|
13376
13481
|
process.exit(1);
|
|
13377
13482
|
}
|
|
13378
13483
|
const violations = skeletons.flatMap(
|
|
13379
|
-
(name) => auditSkeleton(
|
|
13484
|
+
(name) => auditSkeleton(join56(root, "_skeletons", name), name)
|
|
13380
13485
|
);
|
|
13381
13486
|
if (violations.length > 0) {
|
|
13382
13487
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -13514,8 +13619,8 @@ function rawArgsAfter(subcommand) {
|
|
|
13514
13619
|
}
|
|
13515
13620
|
|
|
13516
13621
|
// src/commands/doctor.ts
|
|
13517
|
-
import { existsSync as
|
|
13518
|
-
import { join as
|
|
13622
|
+
import { existsSync as existsSync47, readFileSync as readFileSync40 } from "fs";
|
|
13623
|
+
import { join as join57, resolve as resolve20 } from "path";
|
|
13519
13624
|
import chalk21 from "chalk";
|
|
13520
13625
|
import { Command as Command25 } from "commander";
|
|
13521
13626
|
|
|
@@ -13694,8 +13799,8 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
|
|
|
13694
13799
|
return runDoctorChecks(facts);
|
|
13695
13800
|
}
|
|
13696
13801
|
function readLocalCoreVersion(cwd) {
|
|
13697
|
-
const path =
|
|
13698
|
-
if (!
|
|
13802
|
+
const path = join57(cwd, INSTANCE_CORE_FILE);
|
|
13803
|
+
if (!existsSync47(path)) return null;
|
|
13699
13804
|
try {
|
|
13700
13805
|
return extractVersionField(readFileSync40(path, "utf8"));
|
|
13701
13806
|
} catch {
|
|
@@ -13717,8 +13822,8 @@ function extractVersionField(contents) {
|
|
|
13717
13822
|
return match?.[1] ?? null;
|
|
13718
13823
|
}
|
|
13719
13824
|
function readFossil(cwd) {
|
|
13720
|
-
const path =
|
|
13721
|
-
if (!
|
|
13825
|
+
const path = join57(cwd, CORE_VERSION_FILE);
|
|
13826
|
+
if (!existsSync47(path)) return null;
|
|
13722
13827
|
try {
|
|
13723
13828
|
const value = readFileSync40(path, "utf8").trim();
|
|
13724
13829
|
return value === "" ? null : value;
|
|
@@ -14169,13 +14274,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
14169
14274
|
import { Command as Command27 } from "commander";
|
|
14170
14275
|
|
|
14171
14276
|
// src/lib/packaged-scripts.ts
|
|
14172
|
-
import { existsSync as
|
|
14173
|
-
import { dirname as dirname11, join as
|
|
14277
|
+
import { existsSync as existsSync48 } from "fs";
|
|
14278
|
+
import { dirname as dirname11, join as join58 } from "path";
|
|
14174
14279
|
function findPackagedScript(startDir, relativePath) {
|
|
14175
14280
|
let dir = startDir;
|
|
14176
14281
|
for (; ; ) {
|
|
14177
|
-
const candidate =
|
|
14178
|
-
if (
|
|
14282
|
+
const candidate = join58(dir, relativePath);
|
|
14283
|
+
if (existsSync48(candidate)) return candidate;
|
|
14179
14284
|
const parent = dirname11(dir);
|
|
14180
14285
|
if (parent === dir) return null;
|
|
14181
14286
|
dir = parent;
|