@biffo/cli 0.281.0 → 0.282.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 +328 -228
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7756,6 +7756,13 @@ var ToolDeclarationSchema = z7.object({
|
|
|
7756
7756
|
description: z7.string(),
|
|
7757
7757
|
parameters: z7.record(z7.string(), z7.unknown()).default({})
|
|
7758
7758
|
});
|
|
7759
|
+
var SeedDeclarationSchema = z7.object({
|
|
7760
|
+
dir: z7.string().regex(
|
|
7761
|
+
REL_DIR,
|
|
7762
|
+
"must be a plugin-relative path with no leading slash or traversal, e.g. db/seed"
|
|
7763
|
+
),
|
|
7764
|
+
baseline_tables: z7.array(z7.string()).default([])
|
|
7765
|
+
}).strict();
|
|
7759
7766
|
var ChatAgentDeclarationSchema = z7.object({
|
|
7760
7767
|
key: z7.string().regex(/^[a-z][a-z0-9-]*$/, "must be a lowercase kebab-case slug"),
|
|
7761
7768
|
agent_name: z7.string().optional(),
|
|
@@ -7794,7 +7801,11 @@ var PluginManifestSchema = z7.object({
|
|
|
7794
7801
|
tools: z7.array(ToolDeclarationSchema).default([]),
|
|
7795
7802
|
// Chat agents the plugin registers with Core (ADR-0017). Default empty — an
|
|
7796
7803
|
// ordinary plugin declares none.
|
|
7797
|
-
chat_agents: z7.array(ChatAgentDeclarationSchema).default([])
|
|
7804
|
+
chat_agents: z7.array(ChatAgentDeclarationSchema).default([]),
|
|
7805
|
+
// The plugin's tenant-scoped baseline-row seed (ADR-0005, biffo-template#1554).
|
|
7806
|
+
// Optional — a plugin with no baseline data omits this entirely, and
|
|
7807
|
+
// `biffo plugin install`/`upgrade` vendor nothing for it.
|
|
7808
|
+
seed: SeedDeclarationSchema.optional()
|
|
7798
7809
|
}).superRefine((manifest, ctx) => {
|
|
7799
7810
|
const tableNames = new Set(manifest.tables.map((t) => t.name));
|
|
7800
7811
|
for (const route of manifest.api_routes) {
|
|
@@ -7805,6 +7816,16 @@ var PluginManifestSchema = z7.object({
|
|
|
7805
7816
|
});
|
|
7806
7817
|
}
|
|
7807
7818
|
}
|
|
7819
|
+
if (manifest.seed) {
|
|
7820
|
+
for (const table of manifest.seed.baseline_tables) {
|
|
7821
|
+
if (!tableNames.has(table)) {
|
|
7822
|
+
ctx.addIssue({
|
|
7823
|
+
code: z7.ZodIssueCode.custom,
|
|
7824
|
+
message: `seed.baseline_tables references table '${table}', which is not declared in this manifest's 'tables' (${[...tableNames].sort().join(", ") || "none"})`
|
|
7825
|
+
});
|
|
7826
|
+
}
|
|
7827
|
+
}
|
|
7828
|
+
}
|
|
7808
7829
|
});
|
|
7809
7830
|
function validateManifest(raw) {
|
|
7810
7831
|
const result = PluginManifestSchema.safeParse(raw);
|
|
@@ -8319,6 +8340,14 @@ var RegistryPluginEntrySchema = z8.object({
|
|
|
8319
8340
|
description: z8.string().optional(),
|
|
8320
8341
|
author: z8.string().optional(),
|
|
8321
8342
|
tags: z8.array(z8.string()).optional(),
|
|
8343
|
+
// Summary-form mirror of the manifest's `seed.baseline_tables` (see
|
|
8344
|
+
// ../../lib/plugin-manifest.ts's SeedDeclarationSchema and
|
|
8345
|
+
// _skeletons/registry/registry-schema.json's `seed`, biffo-template#1554).
|
|
8346
|
+
// The registry entry only ever needs to know WHICH tables a plugin promises
|
|
8347
|
+
// baseline rows for, never the seed `dir` itself — that only matters to the
|
|
8348
|
+
// install/upgrade vendoring step, which reads it from the plugin's own
|
|
8349
|
+
// biffo.plugin.json after cloning, not from this summary.
|
|
8350
|
+
baseline_tables: z8.array(z8.string()).optional(),
|
|
8322
8351
|
required_core_version: z8.string().optional(),
|
|
8323
8352
|
infra_modules: z8.array(z8.string()).optional(),
|
|
8324
8353
|
api_routes: z8.array(z8.string()).optional(),
|
|
@@ -8445,8 +8474,8 @@ function printEntry(entry) {
|
|
|
8445
8474
|
}
|
|
8446
8475
|
|
|
8447
8476
|
// src/commands/plugin-install.ts
|
|
8448
|
-
import { cpSync as
|
|
8449
|
-
import { join as
|
|
8477
|
+
import { cpSync as cpSync5, existsSync as existsSync30, mkdirSync as mkdirSync11, readFileSync as readFileSync22, statSync as statSync6 } from "fs";
|
|
8478
|
+
import { join as join32, relative as relative3, resolve as resolve12 } from "path";
|
|
8450
8479
|
import chalk15 from "chalk";
|
|
8451
8480
|
import { Command as Command15 } from "commander";
|
|
8452
8481
|
|
|
@@ -8572,9 +8601,48 @@ async function tryGit(cwd, args) {
|
|
|
8572
8601
|
}
|
|
8573
8602
|
}
|
|
8574
8603
|
|
|
8604
|
+
// src/lib/plugin-seed-vendor.ts
|
|
8605
|
+
import { cpSync as cpSync3, existsSync as existsSync28, mkdirSync as mkdirSync9, readdirSync as readdirSync12, rmSync as rmSync8 } from "fs";
|
|
8606
|
+
import { join as join29 } from "path";
|
|
8607
|
+
var VENDOR_PREFIX = "_plugin-";
|
|
8608
|
+
function pluginSeedImportDir(pluginName) {
|
|
8609
|
+
return `db/imports/${VENDOR_PREFIX}${pluginName}`;
|
|
8610
|
+
}
|
|
8611
|
+
function vendorPluginSeed(pluginSourceDir, manifest, cwd) {
|
|
8612
|
+
if (!manifest.seed) {
|
|
8613
|
+
return { vendored: false };
|
|
8614
|
+
}
|
|
8615
|
+
const sourceSeedDir = join29(pluginSourceDir, manifest.seed.dir);
|
|
8616
|
+
if (!existsSync28(sourceSeedDir)) {
|
|
8617
|
+
throw new Error(
|
|
8618
|
+
`${manifest.name}'s manifest declares seed.dir '${manifest.seed.dir}', but ${sourceSeedDir} does not exist in the plugin's source.`
|
|
8619
|
+
);
|
|
8620
|
+
}
|
|
8621
|
+
const sqlFiles = readdirSync12(sourceSeedDir).filter((f) => f.endsWith(".sql"));
|
|
8622
|
+
if (sqlFiles.length === 0) {
|
|
8623
|
+
throw new Error(
|
|
8624
|
+
`${manifest.name}'s manifest declares seed.dir '${manifest.seed.dir}', but ${sourceSeedDir} contains no *.sql files.`
|
|
8625
|
+
);
|
|
8626
|
+
}
|
|
8627
|
+
const relTargetDir = pluginSeedImportDir(manifest.name);
|
|
8628
|
+
const targetDir = join29(cwd, relTargetDir);
|
|
8629
|
+
rmSync8(targetDir, { recursive: true, force: true });
|
|
8630
|
+
mkdirSync9(targetDir, { recursive: true });
|
|
8631
|
+
for (const file of sqlFiles) {
|
|
8632
|
+
cpSync3(join29(sourceSeedDir, file), join29(targetDir, file));
|
|
8633
|
+
}
|
|
8634
|
+
log.success(
|
|
8635
|
+
`Vendored ${sqlFiles.length} seed file(s) to ${relTargetDir}/ (baseline_tables: ${manifest.seed.baseline_tables.join(", ") || "none declared"})`
|
|
8636
|
+
);
|
|
8637
|
+
log.info(
|
|
8638
|
+
`${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.`
|
|
8639
|
+
);
|
|
8640
|
+
return { vendored: true, stagedPath: relTargetDir };
|
|
8641
|
+
}
|
|
8642
|
+
|
|
8575
8643
|
// src/lib/plugin-source-copy.ts
|
|
8576
|
-
import { copyFileSync as copyFileSync2, cpSync as
|
|
8577
|
-
import { basename, dirname as dirname9, join as
|
|
8644
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync4, mkdirSync as mkdirSync10 } from "fs";
|
|
8645
|
+
import { basename, dirname as dirname9, join as join30 } from "path";
|
|
8578
8646
|
import { execa as execa6 } from "execa";
|
|
8579
8647
|
var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
|
|
8580
8648
|
".git",
|
|
@@ -8591,17 +8659,17 @@ async function copyPluginSource(sourceDir, targetDir) {
|
|
|
8591
8659
|
if (await isGitWorkingTree2(sourceDir)) {
|
|
8592
8660
|
const files = await listGitFiles(sourceDir);
|
|
8593
8661
|
for (const relPath of files) {
|
|
8594
|
-
const destPath =
|
|
8595
|
-
|
|
8596
|
-
copyFileSync2(
|
|
8662
|
+
const destPath = join30(targetDir, relPath);
|
|
8663
|
+
mkdirSync10(dirname9(destPath), { recursive: true });
|
|
8664
|
+
copyFileSync2(join30(sourceDir, relPath), destPath);
|
|
8597
8665
|
}
|
|
8598
8666
|
return { usedGitIgnoreRules: true };
|
|
8599
8667
|
}
|
|
8600
8668
|
log.warn(
|
|
8601
8669
|
`${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
8670
|
);
|
|
8603
|
-
|
|
8604
|
-
|
|
8671
|
+
mkdirSync10(targetDir, { recursive: true });
|
|
8672
|
+
cpSync4(sourceDir, targetDir, {
|
|
8605
8673
|
recursive: true,
|
|
8606
8674
|
filter: (src) => !LOCAL_COPY_EXCLUDES.has(basename(src))
|
|
8607
8675
|
});
|
|
@@ -8625,8 +8693,8 @@ async function listGitFiles(dir) {
|
|
|
8625
8693
|
}
|
|
8626
8694
|
|
|
8627
8695
|
// src/lib/plugin-workspace-sources.ts
|
|
8628
|
-
import { existsSync as
|
|
8629
|
-
import { join as
|
|
8696
|
+
import { existsSync as existsSync29, readdirSync as readdirSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync11 } from "fs";
|
|
8697
|
+
import { join as join31 } from "path";
|
|
8630
8698
|
function readTomlStringArray(text, key) {
|
|
8631
8699
|
const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
|
|
8632
8700
|
if (!open) return [];
|
|
@@ -8670,8 +8738,8 @@ function readDependencyNames(text) {
|
|
|
8670
8738
|
return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
|
|
8671
8739
|
}
|
|
8672
8740
|
function workspaceMemberNames(instanceRoot) {
|
|
8673
|
-
const rootPyproject =
|
|
8674
|
-
if (!
|
|
8741
|
+
const rootPyproject = join31(instanceRoot, "pyproject.toml");
|
|
8742
|
+
if (!existsSync29(rootPyproject)) return /* @__PURE__ */ new Set();
|
|
8675
8743
|
const text = readFileSync21(rootPyproject, "utf8");
|
|
8676
8744
|
const members = readTomlStringArray(text, "members");
|
|
8677
8745
|
const excluded = new Set(readTomlStringArray(text, "exclude"));
|
|
@@ -8681,7 +8749,7 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8681
8749
|
const base = member.slice(0, -2);
|
|
8682
8750
|
let entries;
|
|
8683
8751
|
try {
|
|
8684
|
-
entries =
|
|
8752
|
+
entries = readdirSync13(join31(instanceRoot, base), { withFileTypes: true });
|
|
8685
8753
|
} catch {
|
|
8686
8754
|
continue;
|
|
8687
8755
|
}
|
|
@@ -8695,8 +8763,8 @@ function workspaceMemberNames(instanceRoot) {
|
|
|
8695
8763
|
}
|
|
8696
8764
|
const names = /* @__PURE__ */ new Set();
|
|
8697
8765
|
for (const dir of dirs) {
|
|
8698
|
-
const pp =
|
|
8699
|
-
if (!
|
|
8766
|
+
const pp = join31(instanceRoot, dir, "pyproject.toml");
|
|
8767
|
+
if (!existsSync29(pp)) continue;
|
|
8700
8768
|
const name = readProjectName(readFileSync21(pp, "utf8"));
|
|
8701
8769
|
if (name) names.add(name);
|
|
8702
8770
|
}
|
|
@@ -8708,7 +8776,7 @@ function existingWorkspaceSources(text) {
|
|
|
8708
8776
|
);
|
|
8709
8777
|
}
|
|
8710
8778
|
function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
|
|
8711
|
-
if (!
|
|
8779
|
+
if (!existsSync29(pluginPyprojectPath) || memberNames.size === 0) return [];
|
|
8712
8780
|
const text = readFileSync21(pluginPyprojectPath, "utf8");
|
|
8713
8781
|
const already = existingWorkspaceSources(text);
|
|
8714
8782
|
const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
|
|
@@ -8733,8 +8801,8 @@ ${lines.join("\n")}
|
|
|
8733
8801
|
return toAdd;
|
|
8734
8802
|
}
|
|
8735
8803
|
function applyWorkspaceSources(targetDir, cwd, relTargetDir) {
|
|
8736
|
-
const pluginPyproject =
|
|
8737
|
-
if (!
|
|
8804
|
+
const pluginPyproject = join31(targetDir, "pyproject.toml");
|
|
8805
|
+
if (!existsSync29(pluginPyproject)) return;
|
|
8738
8806
|
const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(cwd));
|
|
8739
8807
|
if (sourced.length > 0) {
|
|
8740
8808
|
log.info(
|
|
@@ -8774,14 +8842,14 @@ var pluginInstallCommand = new Command15("install").description(
|
|
|
8774
8842
|
}
|
|
8775
8843
|
);
|
|
8776
8844
|
function resolveLocalPlugin(localPath) {
|
|
8777
|
-
if (!
|
|
8845
|
+
if (!existsSync30(localPath)) {
|
|
8778
8846
|
throw new Error(`--local path does not exist: ${localPath}`);
|
|
8779
8847
|
}
|
|
8780
8848
|
if (!statSync6(localPath).isDirectory()) {
|
|
8781
8849
|
throw new Error(`--local path is not a directory: ${localPath}`);
|
|
8782
8850
|
}
|
|
8783
|
-
const manifestPath =
|
|
8784
|
-
if (!
|
|
8851
|
+
const manifestPath = join32(localPath, "biffo.plugin.json");
|
|
8852
|
+
if (!existsSync30(manifestPath)) {
|
|
8785
8853
|
throw new Error(
|
|
8786
8854
|
`${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
8855
|
);
|
|
@@ -8807,8 +8875,8 @@ function parsePluginTarget(target) {
|
|
|
8807
8875
|
async function cloneAndValidatePlugin(entry, git) {
|
|
8808
8876
|
const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
|
|
8809
8877
|
try {
|
|
8810
|
-
const manifestPath =
|
|
8811
|
-
if (!
|
|
8878
|
+
const manifestPath = join32(tmpDir, "biffo.plugin.json");
|
|
8879
|
+
if (!existsSync30(manifestPath)) {
|
|
8812
8880
|
throw new Error(
|
|
8813
8881
|
`Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
|
|
8814
8882
|
);
|
|
@@ -8836,8 +8904,8 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8836
8904
|
`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
8905
|
);
|
|
8838
8906
|
}
|
|
8839
|
-
const servicesDir =
|
|
8840
|
-
if (!
|
|
8907
|
+
const servicesDir = join32(options.cwd, "services");
|
|
8908
|
+
if (!existsSync30(servicesDir)) {
|
|
8841
8909
|
throw new Error(
|
|
8842
8910
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
8843
8911
|
);
|
|
@@ -8855,10 +8923,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8855
8923
|
}
|
|
8856
8924
|
const pluginName = entry ? entry.name : source.name;
|
|
8857
8925
|
const relTargetDir = pluginDir(pluginName, "third-party");
|
|
8858
|
-
const targetDir =
|
|
8859
|
-
const modulesDir =
|
|
8926
|
+
const targetDir = join32(options.cwd, relTargetDir);
|
|
8927
|
+
const modulesDir = join32(options.cwd, "modules", "plugins", pluginName);
|
|
8860
8928
|
const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
|
|
8861
|
-
if (
|
|
8929
|
+
if (existsSync30(targetDir) && !inTreeSource) {
|
|
8862
8930
|
throw new Error(
|
|
8863
8931
|
`Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
|
|
8864
8932
|
);
|
|
@@ -8893,7 +8961,7 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8893
8961
|
if (inTreeSource) {
|
|
8894
8962
|
log.info(`${relTargetDir}/ is already in this checkout \u2014 installing in place.`);
|
|
8895
8963
|
} else {
|
|
8896
|
-
|
|
8964
|
+
mkdirSync11(targetDir, { recursive: true });
|
|
8897
8965
|
await copyPluginSource(source.sourceDir, targetDir);
|
|
8898
8966
|
log.success(`Installed plugin source at ${relTargetDir}/`);
|
|
8899
8967
|
}
|
|
@@ -8902,10 +8970,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8902
8970
|
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
8903
8971
|
applyWorkspaceSources(targetDir, options.cwd, relTargetDir);
|
|
8904
8972
|
const stagePaths = [relTargetDir];
|
|
8905
|
-
const tfSourceDir =
|
|
8906
|
-
if (
|
|
8907
|
-
|
|
8908
|
-
|
|
8973
|
+
const tfSourceDir = join32(targetDir, "terraform");
|
|
8974
|
+
if (existsSync30(tfSourceDir)) {
|
|
8975
|
+
mkdirSync11(modulesDir, { recursive: true });
|
|
8976
|
+
cpSync5(tfSourceDir, modulesDir, { recursive: true });
|
|
8909
8977
|
stagePaths.push(`modules/plugins/${pluginName}`);
|
|
8910
8978
|
log.success(`Copied Terraform module to modules/plugins/${pluginName}/`);
|
|
8911
8979
|
const wiring = syncPluginTerraform(options.cwd);
|
|
@@ -8944,6 +9012,10 @@ async function runPluginInstall(target, options, deps) {
|
|
|
8944
9012
|
} else {
|
|
8945
9013
|
log.info(`${pluginName} declares no tables \u2014 nothing to migrate.`);
|
|
8946
9014
|
}
|
|
9015
|
+
const seedResult = vendorPluginSeed(targetDir, manifest, options.cwd);
|
|
9016
|
+
if (seedResult.vendored) {
|
|
9017
|
+
stagePaths.push(seedResult.stagedPath);
|
|
9018
|
+
}
|
|
8947
9019
|
const commitMessage = `feat(plugins): install ${pluginName}@${source.version}`;
|
|
8948
9020
|
await deps.git.add(options.cwd, stagePaths);
|
|
8949
9021
|
await deps.git.commit(options.cwd, commitMessage);
|
|
@@ -8992,13 +9064,18 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
|
|
|
8992
9064
|
` Would generate a migration for ${source.manifest.tables.length} table(s) into services/api/migrations/versions/`
|
|
8993
9065
|
);
|
|
8994
9066
|
}
|
|
9067
|
+
if (source && source.manifest.seed) {
|
|
9068
|
+
console.log(
|
|
9069
|
+
` Would vendor seed DDL into: ${pluginSeedImportDir(name)}/ (baseline_tables: ${source.manifest.seed.baseline_tables.join(", ") || "none declared"})`
|
|
9070
|
+
);
|
|
9071
|
+
}
|
|
8995
9072
|
console.log(` Would commit: feat(plugins): install ${name}@${version}
|
|
8996
9073
|
`);
|
|
8997
9074
|
}
|
|
8998
9075
|
|
|
8999
9076
|
// src/commands/plugin-list.ts
|
|
9000
|
-
import { existsSync as
|
|
9001
|
-
import { join as
|
|
9077
|
+
import { existsSync as existsSync31, readFileSync as readFileSync23 } from "fs";
|
|
9078
|
+
import { join as join33, resolve as resolve13 } from "path";
|
|
9002
9079
|
import chalk16 from "chalk";
|
|
9003
9080
|
import { Command as Command16 } from "commander";
|
|
9004
9081
|
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 +9088,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
|
|
|
9011
9088
|
}
|
|
9012
9089
|
});
|
|
9013
9090
|
async function runPluginList(options) {
|
|
9014
|
-
const servicesDir =
|
|
9015
|
-
if (!
|
|
9091
|
+
const servicesDir = join33(options.cwd, "services");
|
|
9092
|
+
if (!existsSync31(servicesDir)) {
|
|
9016
9093
|
throw new Error(
|
|
9017
9094
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9018
9095
|
);
|
|
@@ -9061,14 +9138,14 @@ import { resolve as resolve14 } from "path";
|
|
|
9061
9138
|
import { Command as Command17 } from "commander";
|
|
9062
9139
|
|
|
9063
9140
|
// src/lib/plugin-staleness.ts
|
|
9064
|
-
import { existsSync as
|
|
9065
|
-
import { join as
|
|
9141
|
+
import { existsSync as existsSync32, readFileSync as readFileSync24, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
|
|
9142
|
+
import { join as join34, relative as relative4 } from "path";
|
|
9066
9143
|
function discoverVendoredPlugins(servicesDir) {
|
|
9067
|
-
if (!
|
|
9068
|
-
return
|
|
9144
|
+
if (!existsSync32(servicesDir)) return [];
|
|
9145
|
+
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
9146
|
}
|
|
9070
9147
|
async function checkPluginStaleness(cwd, deps) {
|
|
9071
|
-
const servicesDir =
|
|
9148
|
+
const servicesDir = join34(cwd, "services");
|
|
9072
9149
|
const names = discoverVendoredPlugins(servicesDir);
|
|
9073
9150
|
let registryRepoByName = null;
|
|
9074
9151
|
const resolveRegistryRepo = async (name) => {
|
|
@@ -9084,7 +9161,7 @@ async function checkPluginStaleness(cwd, deps) {
|
|
|
9084
9161
|
};
|
|
9085
9162
|
const results = [];
|
|
9086
9163
|
for (const name of names) {
|
|
9087
|
-
results.push(await checkOnePlugin(
|
|
9164
|
+
results.push(await checkOnePlugin(join34(servicesDir, name), name, resolveRegistryRepo, deps.git));
|
|
9088
9165
|
}
|
|
9089
9166
|
return results;
|
|
9090
9167
|
}
|
|
@@ -9110,7 +9187,7 @@ async function checkOnePlugin(pluginDir2, name, resolveRegistryRepo, git) {
|
|
|
9110
9187
|
if (record?.sha && isFetchableUrl(record.origin)) {
|
|
9111
9188
|
return checkViaProvenance(name, record, record.origin, git);
|
|
9112
9189
|
}
|
|
9113
|
-
const localOrigin = record && !isFetchableUrl(record.origin) &&
|
|
9190
|
+
const localOrigin = record && !isFetchableUrl(record.origin) && existsSync32(record.origin) ? record.origin : null;
|
|
9114
9191
|
if (localOrigin) {
|
|
9115
9192
|
return checkViaContentDiff(name, pluginDir2, localOrigin, { isLocalDir: true }, git);
|
|
9116
9193
|
}
|
|
@@ -9244,8 +9321,8 @@ async function countDifferingFiles(sourceDir, pluginDir2) {
|
|
|
9244
9321
|
differing++;
|
|
9245
9322
|
continue;
|
|
9246
9323
|
}
|
|
9247
|
-
const a = readFileSync24(
|
|
9248
|
-
const b = readFileSync24(
|
|
9324
|
+
const a = readFileSync24(join34(sourceDir, relPath));
|
|
9325
|
+
const b = readFileSync24(join34(pluginDir2, relPath));
|
|
9249
9326
|
if (!a.equals(b)) differing++;
|
|
9250
9327
|
}
|
|
9251
9328
|
return differing;
|
|
@@ -9260,11 +9337,11 @@ function vendorFileList(dir) {
|
|
|
9260
9337
|
return new Set(walkExcluding(dir, dir, LOCAL_COPY_EXCLUDES));
|
|
9261
9338
|
}
|
|
9262
9339
|
function walkExcluding(root, dir, excludes) {
|
|
9263
|
-
if (!
|
|
9340
|
+
if (!existsSync32(dir)) return [];
|
|
9264
9341
|
const out = [];
|
|
9265
|
-
for (const entry of
|
|
9342
|
+
for (const entry of readdirSync14(dir)) {
|
|
9266
9343
|
if (excludes.has(entry) || entry === ".git") continue;
|
|
9267
|
-
const full =
|
|
9344
|
+
const full = join34(dir, entry);
|
|
9268
9345
|
const stat = statSync7(full);
|
|
9269
9346
|
if (stat.isDirectory()) {
|
|
9270
9347
|
out.push(...walkExcluding(root, full, excludes));
|
|
@@ -9316,8 +9393,8 @@ var pluginStalenessCommand = new Command17("staleness").description(
|
|
|
9316
9393
|
});
|
|
9317
9394
|
|
|
9318
9395
|
// src/commands/plugin-sync-migrations.ts
|
|
9319
|
-
import { existsSync as
|
|
9320
|
-
import { join as
|
|
9396
|
+
import { existsSync as existsSync33 } from "fs";
|
|
9397
|
+
import { join as join35, relative as relative5, resolve as resolve15 } from "path";
|
|
9321
9398
|
import chalk17 from "chalk";
|
|
9322
9399
|
import { Command as Command18 } from "commander";
|
|
9323
9400
|
var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
@@ -9338,11 +9415,11 @@ var pluginSyncMigrationsCommand = new Command18("sync-migrations").description(
|
|
|
9338
9415
|
}
|
|
9339
9416
|
);
|
|
9340
9417
|
async function runPluginSyncMigrations(name, options, deps) {
|
|
9341
|
-
const servicesDir =
|
|
9342
|
-
if (!
|
|
9418
|
+
const servicesDir = join35(options.cwd, "services");
|
|
9419
|
+
if (!existsSync33(servicesDir)) {
|
|
9343
9420
|
throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
|
|
9344
9421
|
}
|
|
9345
|
-
if (name && !
|
|
9422
|
+
if (name && !existsSync33(join35(servicesDir, name, "biffo.plugin.json"))) {
|
|
9346
9423
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
9347
9424
|
}
|
|
9348
9425
|
if (options.dryRun) {
|
|
@@ -9378,8 +9455,8 @@ async function runPluginSyncMigrations(name, options, deps) {
|
|
|
9378
9455
|
}
|
|
9379
9456
|
|
|
9380
9457
|
// src/commands/plugin-uninstall.ts
|
|
9381
|
-
import { existsSync as
|
|
9382
|
-
import { join as
|
|
9458
|
+
import { existsSync as existsSync34, readFileSync as readFileSync25, rmSync as rmSync9 } from "fs";
|
|
9459
|
+
import { join as join36, resolve as resolve16 } from "path";
|
|
9383
9460
|
import chalk18 from "chalk";
|
|
9384
9461
|
import { Command as Command19 } from "commander";
|
|
9385
9462
|
import inquirer6 from "inquirer";
|
|
@@ -9411,16 +9488,16 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9411
9488
|
if (!NAME_PATTERN2.test(name)) {
|
|
9412
9489
|
throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
|
|
9413
9490
|
}
|
|
9414
|
-
const servicesDir =
|
|
9415
|
-
if (!
|
|
9491
|
+
const servicesDir = join36(options.cwd, "services");
|
|
9492
|
+
if (!existsSync34(servicesDir)) {
|
|
9416
9493
|
throw new Error(
|
|
9417
9494
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9418
9495
|
);
|
|
9419
9496
|
}
|
|
9420
|
-
const targetDir =
|
|
9421
|
-
if (!
|
|
9422
|
-
const firstParty =
|
|
9423
|
-
if (
|
|
9497
|
+
const targetDir = join36(servicesDir, name);
|
|
9498
|
+
if (!existsSync34(targetDir)) {
|
|
9499
|
+
const firstParty = join36(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
|
|
9500
|
+
if (existsSync34(firstParty)) {
|
|
9424
9501
|
throw new Error(
|
|
9425
9502
|
`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
9503
|
);
|
|
@@ -9428,9 +9505,9 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9428
9505
|
throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
|
|
9429
9506
|
}
|
|
9430
9507
|
const version = readInstalledVersion(targetDir);
|
|
9431
|
-
const modulesDir =
|
|
9508
|
+
const modulesDir = join36(options.cwd, "modules", "plugins", name);
|
|
9432
9509
|
const stagePaths = [`services/${name}`];
|
|
9433
|
-
if (
|
|
9510
|
+
if (existsSync34(modulesDir)) {
|
|
9434
9511
|
stagePaths.push(`modules/plugins/${name}`);
|
|
9435
9512
|
}
|
|
9436
9513
|
if (options.dryRun) {
|
|
@@ -9450,10 +9527,10 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9450
9527
|
`${options.cwd} is not a git repository \u2014 biffo plugin uninstall must be run from a Biffo project checkout.`
|
|
9451
9528
|
);
|
|
9452
9529
|
}
|
|
9453
|
-
|
|
9530
|
+
rmSync9(targetDir, { recursive: true, force: true });
|
|
9454
9531
|
log.success(`Removed services/${name}/`);
|
|
9455
|
-
if (
|
|
9456
|
-
|
|
9532
|
+
if (existsSync34(modulesDir)) {
|
|
9533
|
+
rmSync9(modulesDir, { recursive: true, force: true });
|
|
9457
9534
|
log.success(`Removed modules/plugins/${name}/`);
|
|
9458
9535
|
const wiring = syncPluginTerraform(options.cwd);
|
|
9459
9536
|
stagePaths.push(...wiring.changedPaths);
|
|
@@ -9487,10 +9564,15 @@ async function runPluginUninstall(name, options, deps) {
|
|
|
9487
9564
|
"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
9565
|
);
|
|
9489
9566
|
}
|
|
9567
|
+
if (existsSync34(join36(options.cwd, pluginSeedImportDir(name)))) {
|
|
9568
|
+
log.warn(
|
|
9569
|
+
`${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.`
|
|
9570
|
+
);
|
|
9571
|
+
}
|
|
9490
9572
|
}
|
|
9491
9573
|
function readInstalledVersion(targetDir) {
|
|
9492
|
-
const manifestPath =
|
|
9493
|
-
if (!
|
|
9574
|
+
const manifestPath = join36(targetDir, "biffo.plugin.json");
|
|
9575
|
+
if (!existsSync34(manifestPath)) return void 0;
|
|
9494
9576
|
try {
|
|
9495
9577
|
return validateManifest(JSON.parse(readFileSync25(manifestPath, "utf8"))).version;
|
|
9496
9578
|
} catch {
|
|
@@ -9525,8 +9607,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
|
|
|
9525
9607
|
}
|
|
9526
9608
|
|
|
9527
9609
|
// src/commands/plugin-upgrade.ts
|
|
9528
|
-
import { cpSync as
|
|
9529
|
-
import { join as
|
|
9610
|
+
import { cpSync as cpSync6, existsSync as existsSync35, mkdirSync as mkdirSync12, readFileSync as readFileSync26, rmSync as rmSync10 } from "fs";
|
|
9611
|
+
import { join as join37, relative as relative6, resolve as resolve17 } from "path";
|
|
9530
9612
|
import chalk19 from "chalk";
|
|
9531
9613
|
import { Command as Command20 } from "commander";
|
|
9532
9614
|
import inquirer7 from "inquirer";
|
|
@@ -9573,8 +9655,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9573
9655
|
`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
9656
|
);
|
|
9575
9657
|
}
|
|
9576
|
-
const servicesDir =
|
|
9577
|
-
if (!
|
|
9658
|
+
const servicesDir = join37(options.cwd, "services");
|
|
9659
|
+
if (!existsSync35(servicesDir)) {
|
|
9578
9660
|
throw new Error(
|
|
9579
9661
|
`${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
|
|
9580
9662
|
);
|
|
@@ -9583,8 +9665,8 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9583
9665
|
return runLocalPluginRefresh(options.local, options, deps);
|
|
9584
9666
|
}
|
|
9585
9667
|
const { name, minor } = parsePluginTarget(target);
|
|
9586
|
-
const targetDir =
|
|
9587
|
-
if (!
|
|
9668
|
+
const targetDir = join37(servicesDir, name);
|
|
9669
|
+
if (!existsSync35(targetDir)) {
|
|
9588
9670
|
throw new Error(
|
|
9589
9671
|
`Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
|
|
9590
9672
|
);
|
|
@@ -9598,7 +9680,7 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9598
9680
|
`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
9681
|
);
|
|
9600
9682
|
}
|
|
9601
|
-
const modulesDir =
|
|
9683
|
+
const modulesDir = join37(options.cwd, "modules", "plugins", entry.name);
|
|
9602
9684
|
if (options.dryRun) {
|
|
9603
9685
|
printDryRun6(entry, currentVersion);
|
|
9604
9686
|
return;
|
|
@@ -9627,9 +9709,9 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9627
9709
|
`Manifest valid \u2014 ${manifest.tables.length} table(s), ${manifest.api_routes.length} route(s)`
|
|
9628
9710
|
);
|
|
9629
9711
|
const previousProvenance = readProvenance(targetDir);
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9712
|
+
rmSync10(targetDir, { recursive: true, force: true });
|
|
9713
|
+
mkdirSync12(targetDir, { recursive: true });
|
|
9714
|
+
cpSync6(tmpDir, targetDir, { recursive: true });
|
|
9633
9715
|
log.success(`Upgraded plugin source at services/${entry.name}/`);
|
|
9634
9716
|
const nextProvenance = resolveRegistryProvenance(
|
|
9635
9717
|
entry.repo,
|
|
@@ -9638,13 +9720,13 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9638
9720
|
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
9639
9721
|
applyWorkspaceSources(targetDir, options.cwd, `services/${entry.name}`);
|
|
9640
9722
|
const stagePaths = [`services/${entry.name}`];
|
|
9641
|
-
if (
|
|
9642
|
-
|
|
9723
|
+
if (existsSync35(modulesDir)) {
|
|
9724
|
+
rmSync10(modulesDir, { recursive: true, force: true });
|
|
9643
9725
|
}
|
|
9644
|
-
const tfSourceDir =
|
|
9645
|
-
if (
|
|
9646
|
-
|
|
9647
|
-
|
|
9726
|
+
const tfSourceDir = join37(targetDir, "terraform");
|
|
9727
|
+
if (existsSync35(tfSourceDir)) {
|
|
9728
|
+
mkdirSync12(modulesDir, { recursive: true });
|
|
9729
|
+
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
9648
9730
|
stagePaths.push(`modules/plugins/${entry.name}`);
|
|
9649
9731
|
log.success(`Copied Terraform module to modules/plugins/${entry.name}/`);
|
|
9650
9732
|
}
|
|
@@ -9664,6 +9746,10 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9664
9746
|
);
|
|
9665
9747
|
}
|
|
9666
9748
|
}
|
|
9749
|
+
const seedResult = vendorPluginSeed(targetDir, manifest, options.cwd);
|
|
9750
|
+
if (seedResult.vendored) {
|
|
9751
|
+
stagePaths.push(seedResult.stagedPath);
|
|
9752
|
+
}
|
|
9667
9753
|
const label = currentVersion ? `${entry.name} ${currentVersion} -> ${entry.version}` : `${entry.name} to ${entry.version}`;
|
|
9668
9754
|
const commitMessage = `feat(plugins): upgrade ${label}`;
|
|
9669
9755
|
await deps.git.add(options.cwd, stagePaths);
|
|
@@ -9682,16 +9768,16 @@ async function runPluginUpgrade(target, options, deps) {
|
|
|
9682
9768
|
async function runLocalPluginRefresh(localPath, options, deps) {
|
|
9683
9769
|
const source = resolveLocalPlugin(localPath);
|
|
9684
9770
|
log.success(`Resolved ${source.name}@${source.version} from ${source.origin}`);
|
|
9685
|
-
const servicesDir =
|
|
9686
|
-
const targetDir =
|
|
9687
|
-
if (!
|
|
9771
|
+
const servicesDir = join37(options.cwd, "services");
|
|
9772
|
+
const targetDir = join37(servicesDir, source.name);
|
|
9773
|
+
if (!existsSync35(targetDir)) {
|
|
9688
9774
|
throw new Error(
|
|
9689
9775
|
`Plugin '${source.name}' is not installed at services/${source.name}/. Use 'biffo plugin install --local ${localPath}' instead.`
|
|
9690
9776
|
);
|
|
9691
9777
|
}
|
|
9692
9778
|
const inTreeSource = resolve17(source.sourceDir) === resolve17(targetDir);
|
|
9693
9779
|
const currentVersion = readInstalledVersion2(targetDir);
|
|
9694
|
-
const modulesDir =
|
|
9780
|
+
const modulesDir = join37(options.cwd, "modules", "plugins", source.name);
|
|
9695
9781
|
if (options.dryRun) {
|
|
9696
9782
|
printLocalDryRun(source, currentVersion, inTreeSource);
|
|
9697
9783
|
return;
|
|
@@ -9720,8 +9806,8 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9720
9806
|
`services/${source.name}/ is already the local checkout \u2014 nothing to copy; re-syncing its Terraform module and checking for a migration.`
|
|
9721
9807
|
);
|
|
9722
9808
|
} else {
|
|
9723
|
-
|
|
9724
|
-
|
|
9809
|
+
rmSync10(targetDir, { recursive: true, force: true });
|
|
9810
|
+
mkdirSync12(targetDir, { recursive: true });
|
|
9725
9811
|
await copyPluginSource(source.sourceDir, targetDir);
|
|
9726
9812
|
log.success(`Refreshed plugin source at services/${source.name}/ from ${source.origin}`);
|
|
9727
9813
|
}
|
|
@@ -9729,13 +9815,13 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9729
9815
|
writePluginProvenance(targetDir, reconcileProvenance(previousProvenance, nextProvenance));
|
|
9730
9816
|
applyWorkspaceSources(targetDir, options.cwd, `services/${source.name}`);
|
|
9731
9817
|
const stagePaths = [`services/${source.name}`];
|
|
9732
|
-
if (
|
|
9733
|
-
|
|
9818
|
+
if (existsSync35(modulesDir)) {
|
|
9819
|
+
rmSync10(modulesDir, { recursive: true, force: true });
|
|
9734
9820
|
}
|
|
9735
|
-
const tfSourceDir =
|
|
9736
|
-
if (
|
|
9737
|
-
|
|
9738
|
-
|
|
9821
|
+
const tfSourceDir = join37(targetDir, "terraform");
|
|
9822
|
+
if (existsSync35(tfSourceDir)) {
|
|
9823
|
+
mkdirSync12(modulesDir, { recursive: true });
|
|
9824
|
+
cpSync6(tfSourceDir, modulesDir, { recursive: true });
|
|
9739
9825
|
stagePaths.push(`modules/plugins/${source.name}`);
|
|
9740
9826
|
log.success(`Refreshed Terraform module at modules/plugins/${source.name}/`);
|
|
9741
9827
|
}
|
|
@@ -9757,6 +9843,10 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9757
9843
|
} else {
|
|
9758
9844
|
log.info(`${source.name} declares no tables \u2014 nothing to migrate.`);
|
|
9759
9845
|
}
|
|
9846
|
+
const seedResult = vendorPluginSeed(targetDir, manifest, options.cwd);
|
|
9847
|
+
if (seedResult.vendored) {
|
|
9848
|
+
stagePaths.push(seedResult.stagedPath);
|
|
9849
|
+
}
|
|
9760
9850
|
await deps.git.add(options.cwd, stagePaths);
|
|
9761
9851
|
if (!await deps.git.hasUncommittedChanges(options.cwd)) {
|
|
9762
9852
|
log.warn(`services/${source.name}/ already matches ${source.origin} \u2014 nothing to commit.`);
|
|
@@ -9776,8 +9866,8 @@ async function runLocalPluginRefresh(localPath, options, deps) {
|
|
|
9776
9866
|
}
|
|
9777
9867
|
}
|
|
9778
9868
|
function readInstalledVersion2(targetDir) {
|
|
9779
|
-
const manifestPath =
|
|
9780
|
-
if (!
|
|
9869
|
+
const manifestPath = join37(targetDir, "biffo.plugin.json");
|
|
9870
|
+
if (!existsSync35(manifestPath)) return void 0;
|
|
9781
9871
|
try {
|
|
9782
9872
|
return validateManifest(JSON.parse(readFileSync26(manifestPath, "utf8"))).version;
|
|
9783
9873
|
} catch {
|
|
@@ -9814,6 +9904,11 @@ function printDryRun6(entry, currentVersion) {
|
|
|
9814
9904
|
` Would replace Terraform module at: modules/plugins/${entry.name}/ (if the repo has one)`
|
|
9815
9905
|
);
|
|
9816
9906
|
}
|
|
9907
|
+
if (entry.baseline_tables && entry.baseline_tables.length > 0) {
|
|
9908
|
+
console.log(
|
|
9909
|
+
` Would re-vendor seed DDL into: ${pluginSeedImportDir(entry.name)}/ (baseline_tables: ${entry.baseline_tables.join(", ")})`
|
|
9910
|
+
);
|
|
9911
|
+
}
|
|
9817
9912
|
console.log(` Would commit: feat(plugins): upgrade ${entry.name} to ${entry.version}
|
|
9818
9913
|
`);
|
|
9819
9914
|
}
|
|
@@ -9833,6 +9928,11 @@ function printLocalDryRun(source, currentVersion, inTreeSource) {
|
|
|
9833
9928
|
` 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
9929
|
);
|
|
9835
9930
|
}
|
|
9931
|
+
if (source.manifest.seed) {
|
|
9932
|
+
console.log(
|
|
9933
|
+
` Would re-vendor seed DDL into: ${pluginSeedImportDir(source.name)}/ (baseline_tables: ${source.manifest.seed.baseline_tables.join(", ") || "none declared"})`
|
|
9934
|
+
);
|
|
9935
|
+
}
|
|
9836
9936
|
console.log(` Would commit: chore(plugins): refresh ${source.name} from local checkout
|
|
9837
9937
|
`);
|
|
9838
9938
|
}
|
|
@@ -9852,7 +9952,7 @@ pluginCommand.addCommand(pluginStalenessCommand);
|
|
|
9852
9952
|
import { Command as Command23 } from "commander";
|
|
9853
9953
|
|
|
9854
9954
|
// src/commands/sibling-check-identity.ts
|
|
9855
|
-
import { existsSync as
|
|
9955
|
+
import { existsSync as existsSync36, readFileSync as readFileSync27 } from "fs";
|
|
9856
9956
|
import { resolve as resolve18 } from "path";
|
|
9857
9957
|
import chalk20 from "chalk";
|
|
9858
9958
|
import { Command as Command22 } from "commander";
|
|
@@ -10066,7 +10166,7 @@ async function resolveConfig4(options) {
|
|
|
10066
10166
|
return cfg;
|
|
10067
10167
|
}
|
|
10068
10168
|
const localConfigPath = resolve18(process.cwd(), "biffo.config.json");
|
|
10069
|
-
if (
|
|
10169
|
+
if (existsSync36(localConfigPath)) {
|
|
10070
10170
|
const raw = JSON.parse(readFileSync27(localConfigPath, "utf8"));
|
|
10071
10171
|
const result = BiffoConfigSchema.safeParse(raw);
|
|
10072
10172
|
if (result.success) return result.data;
|
|
@@ -10113,19 +10213,19 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
|
|
|
10113
10213
|
import { Command as Command24 } from "commander";
|
|
10114
10214
|
|
|
10115
10215
|
// src/scripts/check-adr-numbering.ts
|
|
10116
|
-
import { existsSync as
|
|
10117
|
-
import { join as
|
|
10216
|
+
import { existsSync as existsSync38 } from "fs";
|
|
10217
|
+
import { join as join39 } from "path";
|
|
10118
10218
|
import { execa as execa7 } from "execa";
|
|
10119
10219
|
|
|
10120
10220
|
// src/lib/adr-numbering-guard.ts
|
|
10121
|
-
import { existsSync as
|
|
10122
|
-
import { join as
|
|
10221
|
+
import { existsSync as existsSync37, readdirSync as readdirSync15, readFileSync as readFileSync28 } from "fs";
|
|
10222
|
+
import { join as join38 } from "path";
|
|
10123
10223
|
var ADR_FILENAME = /^(\d{4})-.+\.md$/;
|
|
10124
10224
|
var ALLOWLIST_FILENAME = ".numbering-allowlist";
|
|
10125
10225
|
var TEMPLATE_ADR_RESERVED_UPTO = "0099";
|
|
10126
10226
|
function readAdrNumberingAllowlist(adrDir) {
|
|
10127
|
-
const path =
|
|
10128
|
-
if (!
|
|
10227
|
+
const path = join38(adrDir, ALLOWLIST_FILENAME);
|
|
10228
|
+
if (!existsSync37(path)) return /* @__PURE__ */ new Set();
|
|
10129
10229
|
const numbers = /* @__PURE__ */ new Set();
|
|
10130
10230
|
for (const rawLine of readFileSync28(path, "utf8").split("\n")) {
|
|
10131
10231
|
const line = rawLine.split("#")[0].trim();
|
|
@@ -10135,8 +10235,8 @@ function readAdrNumberingAllowlist(adrDir) {
|
|
|
10135
10235
|
}
|
|
10136
10236
|
function adrNumbersIn(adrDir) {
|
|
10137
10237
|
const claims = /* @__PURE__ */ new Map();
|
|
10138
|
-
if (!
|
|
10139
|
-
for (const entry of
|
|
10238
|
+
if (!existsSync37(adrDir)) return claims;
|
|
10239
|
+
for (const entry of readdirSync15(adrDir).sort()) {
|
|
10140
10240
|
const match = ADR_FILENAME.exec(entry);
|
|
10141
10241
|
if (!match) continue;
|
|
10142
10242
|
const number = match[1];
|
|
@@ -10190,8 +10290,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
|
|
|
10190
10290
|
// src/scripts/check-adr-numbering.ts
|
|
10191
10291
|
async function runAdrNumberingCheck() {
|
|
10192
10292
|
const root = (await execa7("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10193
|
-
const adrDir =
|
|
10194
|
-
if (!
|
|
10293
|
+
const adrDir = join39(root, "docs", "ADR");
|
|
10294
|
+
if (!existsSync38(adrDir)) {
|
|
10195
10295
|
console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
|
|
10196
10296
|
return;
|
|
10197
10297
|
}
|
|
@@ -10489,13 +10589,13 @@ async function runBranchProtectionCheck(explicitRepo, options = {}) {
|
|
|
10489
10589
|
}
|
|
10490
10590
|
|
|
10491
10591
|
// src/scripts/check-codeql-suppression.ts
|
|
10492
|
-
import { existsSync as
|
|
10493
|
-
import { join as
|
|
10592
|
+
import { existsSync as existsSync39 } from "fs";
|
|
10593
|
+
import { join as join41, relative as relative7 } from "path";
|
|
10494
10594
|
import { execa as execa9 } from "execa";
|
|
10495
10595
|
|
|
10496
10596
|
// src/lib/codeql-suppression-guard.ts
|
|
10497
|
-
import { readdirSync as
|
|
10498
|
-
import { join as
|
|
10597
|
+
import { readdirSync as readdirSync16, readFileSync as readFileSync29, statSync as statSync8 } from "fs";
|
|
10598
|
+
import { join as join40 } from "path";
|
|
10499
10599
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
10500
10600
|
".git",
|
|
10501
10601
|
".worktrees",
|
|
@@ -10521,12 +10621,12 @@ function walkSourceFiles(root) {
|
|
|
10521
10621
|
const walk2 = (dir) => {
|
|
10522
10622
|
let entries;
|
|
10523
10623
|
try {
|
|
10524
|
-
entries =
|
|
10624
|
+
entries = readdirSync16(dir);
|
|
10525
10625
|
} catch {
|
|
10526
10626
|
return;
|
|
10527
10627
|
}
|
|
10528
10628
|
for (const entry of entries) {
|
|
10529
|
-
const p =
|
|
10629
|
+
const p = join40(dir, entry);
|
|
10530
10630
|
let st;
|
|
10531
10631
|
try {
|
|
10532
10632
|
st = statSync8(p);
|
|
@@ -10563,8 +10663,8 @@ function sweepCodeqlSuppressionComments(root) {
|
|
|
10563
10663
|
// src/scripts/check-codeql-suppression.ts
|
|
10564
10664
|
async function runCodeqlSuppressionCheck() {
|
|
10565
10665
|
const root = (await execa9("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
10566
|
-
const scanRoot =
|
|
10567
|
-
if (!
|
|
10666
|
+
const scanRoot = join41(root, "cli", "src");
|
|
10667
|
+
if (!existsSync39(scanRoot)) {
|
|
10568
10668
|
console.log(
|
|
10569
10669
|
"\u2014 codeql-suppression guard: skipped \u2014 no cli/src in this repo, so there is no CLI source to scan."
|
|
10570
10670
|
);
|
|
@@ -10591,8 +10691,8 @@ async function runCodeqlSuppressionCheck() {
|
|
|
10591
10691
|
import { execa as execa10 } from "execa";
|
|
10592
10692
|
|
|
10593
10693
|
// src/lib/cognito-invite-template-guard.ts
|
|
10594
|
-
import { readdirSync as
|
|
10595
|
-
import { join as
|
|
10694
|
+
import { readdirSync as readdirSync17, readFileSync as readFileSync30, statSync as statSync9 } from "fs";
|
|
10695
|
+
import { join as join42 } from "path";
|
|
10596
10696
|
var REQUIRED_INVITE_MEMBERS = ["email_subject", "email_message", "sms_message"];
|
|
10597
10697
|
var REQUIRED_INVITE_PLACEHOLDERS = ["{username}", "{####}"];
|
|
10598
10698
|
var PLACEHOLDER_MEMBERS = ["email_message", "sms_message"];
|
|
@@ -10669,13 +10769,13 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
10669
10769
|
const walk2 = (dir, relative10) => {
|
|
10670
10770
|
let entries;
|
|
10671
10771
|
try {
|
|
10672
|
-
entries =
|
|
10772
|
+
entries = readdirSync17(dir);
|
|
10673
10773
|
} catch {
|
|
10674
10774
|
return;
|
|
10675
10775
|
}
|
|
10676
10776
|
for (const entry of entries) {
|
|
10677
10777
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees") continue;
|
|
10678
|
-
const full =
|
|
10778
|
+
const full = join42(dir, entry);
|
|
10679
10779
|
const rel = `${relative10}/${entry}`;
|
|
10680
10780
|
if (statSync9(full).isDirectory()) {
|
|
10681
10781
|
walk2(full, rel);
|
|
@@ -10684,12 +10784,12 @@ function findModuleTerraformFiles(repoRoot) {
|
|
|
10684
10784
|
}
|
|
10685
10785
|
}
|
|
10686
10786
|
};
|
|
10687
|
-
walk2(
|
|
10787
|
+
walk2(join42(repoRoot, "modules"), "modules");
|
|
10688
10788
|
return found.sort();
|
|
10689
10789
|
}
|
|
10690
10790
|
function checkCognitoInviteTemplates(repoRoot) {
|
|
10691
10791
|
return findModuleTerraformFiles(repoRoot).flatMap(
|
|
10692
|
-
(file) => checkInviteTemplateSource(file, readFileSync30(
|
|
10792
|
+
(file) => checkInviteTemplateSource(file, readFileSync30(join42(repoRoot, file), "utf8"))
|
|
10693
10793
|
);
|
|
10694
10794
|
}
|
|
10695
10795
|
|
|
@@ -10717,12 +10817,12 @@ async function runCognitoInviteTemplateCheck() {
|
|
|
10717
10817
|
}
|
|
10718
10818
|
|
|
10719
10819
|
// src/scripts/check-core-direct-paths.ts
|
|
10720
|
-
import { join as
|
|
10820
|
+
import { join as join44 } from "path";
|
|
10721
10821
|
import { execa as execa11 } from "execa";
|
|
10722
10822
|
|
|
10723
10823
|
// src/lib/core-direct-paths-audit.ts
|
|
10724
|
-
import { existsSync as
|
|
10725
|
-
import { join as
|
|
10824
|
+
import { existsSync as existsSync40, readFileSync as readFileSync31, readdirSync as readdirSync18, statSync as statSync10 } from "fs";
|
|
10825
|
+
import { join as join43 } from "path";
|
|
10726
10826
|
var EXTERNAL_BASE_IDENTIFIERS = ["CORE_API_URL"];
|
|
10727
10827
|
var API_ROUTE_PREFIX = "/api/v1";
|
|
10728
10828
|
var TEST_FILE_SUFFIXES = [".test.ts", ".test.tsx", ".spec.ts", ".spec.tsx"];
|
|
@@ -10881,12 +10981,12 @@ function walkFiles(root, accept, skipDir) {
|
|
|
10881
10981
|
const walk2 = (dir) => {
|
|
10882
10982
|
let entries;
|
|
10883
10983
|
try {
|
|
10884
|
-
entries =
|
|
10984
|
+
entries = readdirSync18(dir);
|
|
10885
10985
|
} catch {
|
|
10886
10986
|
return;
|
|
10887
10987
|
}
|
|
10888
10988
|
for (const entry of entries) {
|
|
10889
|
-
const p =
|
|
10989
|
+
const p = join43(dir, entry);
|
|
10890
10990
|
let st;
|
|
10891
10991
|
try {
|
|
10892
10992
|
st = statSync10(p);
|
|
@@ -10981,7 +11081,7 @@ function pathMatchesAnyCorePrefix(normalized, corePrefixes, apiRoutePrefix = API
|
|
|
10981
11081
|
}
|
|
10982
11082
|
function resolveSiblingCoreSrc(params) {
|
|
10983
11083
|
const { estateDir, sibling } = params;
|
|
10984
|
-
const configPath =
|
|
11084
|
+
const configPath = join43(estateDir, sibling, "biffo.sibling.json");
|
|
10985
11085
|
let raw;
|
|
10986
11086
|
try {
|
|
10987
11087
|
raw = readFileSync31(configPath, "utf8");
|
|
@@ -11004,8 +11104,8 @@ function resolveSiblingCoreSrc(params) {
|
|
|
11004
11104
|
`cannot resolve ${sibling}'s core: ${configPath} has no non-empty "core_project" field.`
|
|
11005
11105
|
);
|
|
11006
11106
|
}
|
|
11007
|
-
const coreApiSrcDir =
|
|
11008
|
-
if (!
|
|
11107
|
+
const coreApiSrcDir = join43(estateDir, coreProject, "services", "api", "src");
|
|
11108
|
+
if (!existsSync40(coreApiSrcDir)) {
|
|
11009
11109
|
throw new Error(
|
|
11010
11110
|
`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
11111
|
);
|
|
@@ -11048,7 +11148,7 @@ function auditSiblingCoreDirectPaths(params) {
|
|
|
11048
11148
|
async function runCoreDirectPathsCheck(opts = {}) {
|
|
11049
11149
|
const root = (await execa11("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11050
11150
|
const sibling = opts.sibling ?? "sibling-template (self-check)";
|
|
11051
|
-
const frontendSrcDir = opts.frontendSrc ??
|
|
11151
|
+
const frontendSrcDir = opts.frontendSrc ?? join44(root, "_skeletons", "sibling-template", "apps", "frontend", "src");
|
|
11052
11152
|
let coreApiSrcDir;
|
|
11053
11153
|
let coreProject = null;
|
|
11054
11154
|
if (opts.coreSrc) {
|
|
@@ -11064,7 +11164,7 @@ async function runCoreDirectPathsCheck(opts = {}) {
|
|
|
11064
11164
|
coreApiSrcDir = resolution.coreApiSrcDir;
|
|
11065
11165
|
coreProject = resolution.coreProject;
|
|
11066
11166
|
} else {
|
|
11067
|
-
coreApiSrcDir =
|
|
11167
|
+
coreApiSrcDir = join44(root, "services", "api", "src");
|
|
11068
11168
|
}
|
|
11069
11169
|
const report = auditSiblingCoreDirectPaths({ sibling, frontendSrcDir, coreApiSrcDir });
|
|
11070
11170
|
console.log(
|
|
@@ -11130,8 +11230,8 @@ async function runOwnershipCheck(argv) {
|
|
|
11130
11230
|
const { stdout } = await execa12("git", ["diff", "--cached", "--name-status"], { cwd: root });
|
|
11131
11231
|
({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
|
|
11132
11232
|
if (messageFile) {
|
|
11133
|
-
const { readFileSync: readFileSync41, existsSync:
|
|
11134
|
-
if (
|
|
11233
|
+
const { readFileSync: readFileSync41, existsSync: existsSync49 } = await import("fs");
|
|
11234
|
+
if (existsSync49(messageFile)) commitMessage = readFileSync41(messageFile, "utf8");
|
|
11135
11235
|
}
|
|
11136
11236
|
} else {
|
|
11137
11237
|
const base = process.env["GITHUB_BASE_REF"] ?? args[0];
|
|
@@ -11235,8 +11335,8 @@ ${BOLD}If the divergence is deliberate${OFF}
|
|
|
11235
11335
|
import { execa as execa13 } from "execa";
|
|
11236
11336
|
|
|
11237
11337
|
// src/lib/eventbridge-log-permission-guard.ts
|
|
11238
|
-
import { readFileSync as readFileSync32, readdirSync as
|
|
11239
|
-
import { join as
|
|
11338
|
+
import { readFileSync as readFileSync32, readdirSync as readdirSync19, statSync as statSync11 } from "fs";
|
|
11339
|
+
import { join as join45 } from "path";
|
|
11240
11340
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", ".terraform", ".worktrees", "dist"]);
|
|
11241
11341
|
var EVENT_TARGET_TYPE = "aws_cloudwatch_event_target";
|
|
11242
11342
|
var LOG_RESOURCE_POLICY_TYPE = "aws_cloudwatch_log_resource_policy";
|
|
@@ -11308,12 +11408,12 @@ function walkTerraformFiles(root) {
|
|
|
11308
11408
|
const walk2 = (dir) => {
|
|
11309
11409
|
let entries;
|
|
11310
11410
|
try {
|
|
11311
|
-
entries =
|
|
11411
|
+
entries = readdirSync19(dir);
|
|
11312
11412
|
} catch {
|
|
11313
11413
|
return;
|
|
11314
11414
|
}
|
|
11315
11415
|
for (const entry of entries) {
|
|
11316
|
-
const p =
|
|
11416
|
+
const p = join45(dir, entry);
|
|
11317
11417
|
let st;
|
|
11318
11418
|
try {
|
|
11319
11419
|
st = statSync11(p);
|
|
@@ -11450,11 +11550,11 @@ import { execa as execa14 } from "execa";
|
|
|
11450
11550
|
|
|
11451
11551
|
// src/lib/lambda-output-guard.ts
|
|
11452
11552
|
import { readFileSync as readFileSync34 } from "fs";
|
|
11453
|
-
import { join as
|
|
11553
|
+
import { join as join47 } from "path";
|
|
11454
11554
|
|
|
11455
11555
|
// src/lib/terraform-input-guard.ts
|
|
11456
|
-
import { readdirSync as
|
|
11457
|
-
import { join as
|
|
11556
|
+
import { readdirSync as readdirSync20, readFileSync as readFileSync33, statSync as statSync12 } from "fs";
|
|
11557
|
+
import { join as join46 } from "path";
|
|
11458
11558
|
var GUARDED_SUBCOMMANDS = [
|
|
11459
11559
|
"init",
|
|
11460
11560
|
"plan",
|
|
@@ -11472,13 +11572,13 @@ function findWorkflowFiles(repoRoot) {
|
|
|
11472
11572
|
const walk2 = (dir, relative10) => {
|
|
11473
11573
|
let entries;
|
|
11474
11574
|
try {
|
|
11475
|
-
entries =
|
|
11575
|
+
entries = readdirSync20(dir);
|
|
11476
11576
|
} catch {
|
|
11477
11577
|
return;
|
|
11478
11578
|
}
|
|
11479
11579
|
for (const entry of entries) {
|
|
11480
11580
|
if (entry === "node_modules" || entry === ".git" || entry === ".worktrees") continue;
|
|
11481
|
-
const full =
|
|
11581
|
+
const full = join46(dir, entry);
|
|
11482
11582
|
const rel = relative10 ? `${relative10}/${entry}` : entry;
|
|
11483
11583
|
if (statSync12(full).isDirectory()) {
|
|
11484
11584
|
walk2(full, rel);
|
|
@@ -11522,7 +11622,7 @@ function checkWorkflowSource(file, rawSource) {
|
|
|
11522
11622
|
}
|
|
11523
11623
|
function checkTerraformInput(repoRoot) {
|
|
11524
11624
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
11525
|
-
(file) => checkWorkflowSource(file, readFileSync33(
|
|
11625
|
+
(file) => checkWorkflowSource(file, readFileSync33(join46(repoRoot, file), "utf8"))
|
|
11526
11626
|
);
|
|
11527
11627
|
}
|
|
11528
11628
|
|
|
@@ -11580,7 +11680,7 @@ function checkWorkflowSource2(file, rawSource) {
|
|
|
11580
11680
|
}
|
|
11581
11681
|
function checkLambdaOutput(repoRoot) {
|
|
11582
11682
|
return findWorkflowFiles(repoRoot).flatMap(
|
|
11583
|
-
(file) => checkWorkflowSource2(file, readFileSync34(
|
|
11683
|
+
(file) => checkWorkflowSource2(file, readFileSync34(join47(repoRoot, file), "utf8"))
|
|
11584
11684
|
);
|
|
11585
11685
|
}
|
|
11586
11686
|
|
|
@@ -11608,8 +11708,8 @@ async function runLambdaOutputCheck() {
|
|
|
11608
11708
|
}
|
|
11609
11709
|
|
|
11610
11710
|
// src/scripts/check-pipe-trap.ts
|
|
11611
|
-
import { readFileSync as readFileSync35, readdirSync as
|
|
11612
|
-
import { join as
|
|
11711
|
+
import { readFileSync as readFileSync35, readdirSync as readdirSync21 } from "fs";
|
|
11712
|
+
import { join as join48, relative as relative8 } from "path";
|
|
11613
11713
|
import { execa as execa15 } from "execa";
|
|
11614
11714
|
|
|
11615
11715
|
// src/lib/pipe-trap-guard.ts
|
|
@@ -11706,17 +11806,17 @@ function findPipeTraps(source) {
|
|
|
11706
11806
|
function shellFiles(root) {
|
|
11707
11807
|
const out = [];
|
|
11708
11808
|
for (const dir of ["scripts", ".githooks"]) {
|
|
11709
|
-
const full =
|
|
11809
|
+
const full = join48(root, dir);
|
|
11710
11810
|
let entries;
|
|
11711
11811
|
try {
|
|
11712
|
-
entries =
|
|
11812
|
+
entries = readdirSync21(full, { withFileTypes: true });
|
|
11713
11813
|
} catch {
|
|
11714
11814
|
continue;
|
|
11715
11815
|
}
|
|
11716
11816
|
for (const entry of entries) {
|
|
11717
11817
|
if (!entry.isFile()) continue;
|
|
11718
11818
|
if (dir === "scripts" && !entry.name.endsWith(".sh")) continue;
|
|
11719
|
-
out.push(
|
|
11819
|
+
out.push(join48(full, entry.name));
|
|
11720
11820
|
}
|
|
11721
11821
|
}
|
|
11722
11822
|
return out;
|
|
@@ -11753,7 +11853,7 @@ import { execa as execa16 } from "execa";
|
|
|
11753
11853
|
|
|
11754
11854
|
// src/lib/plugin-allowlist-convention.ts
|
|
11755
11855
|
import { readFileSync as readFileSync36 } from "fs";
|
|
11756
|
-
import { join as
|
|
11856
|
+
import { join as join49 } from "path";
|
|
11757
11857
|
var COMPUTE_MAIN_TF = "modules/cloud/aws/compute/main.tf";
|
|
11758
11858
|
var PLUGIN_TEMPLATE_MAIN_TF = "modules/plugins/_template/main.tf";
|
|
11759
11859
|
var ALLOWLIST_MAIN_TF = "modules/cloud/aws/plugin-allowlist/main.tf";
|
|
@@ -11764,7 +11864,7 @@ var PLUGIN = "<plugin>";
|
|
|
11764
11864
|
var ACCOUNT = "<account>";
|
|
11765
11865
|
function read(repoRoot, relative10) {
|
|
11766
11866
|
try {
|
|
11767
|
-
return readFileSync36(
|
|
11867
|
+
return readFileSync36(join49(repoRoot, relative10), "utf8");
|
|
11768
11868
|
} catch {
|
|
11769
11869
|
throw new Error(`plugin-allowlist drift guard: cannot read ${relative10}`);
|
|
11770
11870
|
}
|
|
@@ -11885,34 +11985,34 @@ async function runPluginAllowlistConventionCheck() {
|
|
|
11885
11985
|
}
|
|
11886
11986
|
|
|
11887
11987
|
// src/scripts/check-plugin-collisions.ts
|
|
11888
|
-
import { existsSync as
|
|
11889
|
-
import { join as
|
|
11988
|
+
import { existsSync as existsSync42 } from "fs";
|
|
11989
|
+
import { join as join51 } from "path";
|
|
11890
11990
|
import { execa as execa17 } from "execa";
|
|
11891
11991
|
|
|
11892
11992
|
// src/lib/plugin-collision-guard.ts
|
|
11893
|
-
import { existsSync as
|
|
11894
|
-
import { join as
|
|
11993
|
+
import { existsSync as existsSync41, readdirSync as readdirSync22, statSync as statSync13 } from "fs";
|
|
11994
|
+
import { join as join50 } from "path";
|
|
11895
11995
|
var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
|
|
11896
11996
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
|
|
11897
11997
|
function subdirectories(dir) {
|
|
11898
|
-
if (!
|
|
11899
|
-
return
|
|
11998
|
+
if (!existsSync41(dir)) return [];
|
|
11999
|
+
return readdirSync22(dir).filter((entry) => {
|
|
11900
12000
|
if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
|
|
11901
12001
|
try {
|
|
11902
|
-
return statSync13(
|
|
12002
|
+
return statSync13(join50(dir, entry)).isDirectory();
|
|
11903
12003
|
} catch {
|
|
11904
12004
|
return false;
|
|
11905
12005
|
}
|
|
11906
12006
|
});
|
|
11907
12007
|
}
|
|
11908
12008
|
function regularPackagesOf(pluginDir2) {
|
|
11909
|
-
return subdirectories(pluginDir2).filter((name) =>
|
|
12009
|
+
return subdirectories(pluginDir2).filter((name) => existsSync41(join50(pluginDir2, name, "__init__.py"))).sort();
|
|
11910
12010
|
}
|
|
11911
12011
|
function bareTestModulesOf(pluginDir2) {
|
|
11912
|
-
const testsDir =
|
|
11913
|
-
if (!
|
|
11914
|
-
if (
|
|
11915
|
-
return
|
|
12012
|
+
const testsDir = join50(pluginDir2, "tests");
|
|
12013
|
+
if (!existsSync41(testsDir)) return [];
|
|
12014
|
+
if (existsSync41(join50(testsDir, "__init__.py"))) return [];
|
|
12015
|
+
return readdirSync22(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
|
|
11916
12016
|
}
|
|
11917
12017
|
function findCollisions(servicesDir, pluginDirs) {
|
|
11918
12018
|
const plugins = (pluginDirs ?? subdirectories(servicesDir)).filter((name) => !name.startsWith("_")).filter((name) => name !== "api").sort();
|
|
@@ -11920,7 +12020,7 @@ function findCollisions(servicesDir, pluginDirs) {
|
|
|
11920
12020
|
const gather = (kind, namesOf) => {
|
|
11921
12021
|
const claims = /* @__PURE__ */ new Map();
|
|
11922
12022
|
for (const plugin of plugins) {
|
|
11923
|
-
for (const name of namesOf(
|
|
12023
|
+
for (const name of namesOf(join50(servicesDir, plugin))) {
|
|
11924
12024
|
claims.set(name, [...claims.get(name) ?? [], plugin]);
|
|
11925
12025
|
}
|
|
11926
12026
|
}
|
|
@@ -11958,8 +12058,8 @@ function formatCollisions(collisions) {
|
|
|
11958
12058
|
// src/scripts/check-plugin-collisions.ts
|
|
11959
12059
|
async function runPluginCollisionCheck() {
|
|
11960
12060
|
const root = (await execa17("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
11961
|
-
const servicesDir =
|
|
11962
|
-
if (!
|
|
12061
|
+
const servicesDir = join51(root, "services");
|
|
12062
|
+
if (!existsSync42(servicesDir)) {
|
|
11963
12063
|
console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
|
|
11964
12064
|
return;
|
|
11965
12065
|
}
|
|
@@ -11979,8 +12079,8 @@ async function runPluginCollisionCheck() {
|
|
|
11979
12079
|
import { execa as execa18 } from "execa";
|
|
11980
12080
|
|
|
11981
12081
|
// src/lib/plugin-terraform-guard.ts
|
|
11982
|
-
import { existsSync as
|
|
11983
|
-
import { dirname as dirname10, join as
|
|
12082
|
+
import { existsSync as existsSync43, readFileSync as readFileSync37, readdirSync as readdirSync23 } from "fs";
|
|
12083
|
+
import { dirname as dirname10, join as join52, relative as relative9, sep as sep3 } from "path";
|
|
11984
12084
|
var SKIP_DIRS3 = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
|
|
11985
12085
|
var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
|
|
11986
12086
|
function findPluginManifests(root) {
|
|
@@ -11988,16 +12088,16 @@ function findPluginManifests(root) {
|
|
|
11988
12088
|
const walk2 = (dir) => {
|
|
11989
12089
|
let entries;
|
|
11990
12090
|
try {
|
|
11991
|
-
entries =
|
|
12091
|
+
entries = readdirSync23(dir, { withFileTypes: true });
|
|
11992
12092
|
} catch {
|
|
11993
12093
|
return;
|
|
11994
12094
|
}
|
|
11995
12095
|
for (const entry of entries) {
|
|
11996
12096
|
if (entry.isDirectory()) {
|
|
11997
12097
|
if (SKIP_DIRS3.has(entry.name)) continue;
|
|
11998
|
-
walk2(
|
|
12098
|
+
walk2(join52(dir, entry.name));
|
|
11999
12099
|
} else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
|
|
12000
|
-
found.push(relative9(root,
|
|
12100
|
+
found.push(relative9(root, join52(dir, entry.name)).split(sep3).join("/"));
|
|
12001
12101
|
}
|
|
12002
12102
|
}
|
|
12003
12103
|
};
|
|
@@ -12022,14 +12122,14 @@ function readSubscriptions(absManifestPath) {
|
|
|
12022
12122
|
}
|
|
12023
12123
|
function checkPluginTerraform(root) {
|
|
12024
12124
|
const violations = [];
|
|
12025
|
-
const coreManifest =
|
|
12125
|
+
const coreManifest = existsSync43(join52(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
|
|
12026
12126
|
for (const manifest of findPluginManifests(root)) {
|
|
12027
12127
|
if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
|
|
12028
|
-
const absManifest =
|
|
12128
|
+
const absManifest = join52(root, manifest);
|
|
12029
12129
|
const subscriptions = readSubscriptions(absManifest);
|
|
12030
12130
|
if (subscriptions === null) continue;
|
|
12031
12131
|
const pluginDir2 = dirname10(absManifest);
|
|
12032
|
-
if (
|
|
12132
|
+
if (existsSync43(join52(pluginDir2, "terraform"))) continue;
|
|
12033
12133
|
const relPluginDir = relative9(root, pluginDir2).split(sep3).join("/");
|
|
12034
12134
|
violations.push({
|
|
12035
12135
|
manifest,
|
|
@@ -12060,13 +12160,13 @@ async function runPluginTerraformCheck() {
|
|
|
12060
12160
|
}
|
|
12061
12161
|
|
|
12062
12162
|
// src/scripts/check-plugin-tool-supply.ts
|
|
12063
|
-
import { existsSync as
|
|
12064
|
-
import { join as
|
|
12163
|
+
import { existsSync as existsSync45 } from "fs";
|
|
12164
|
+
import { join as join54 } from "path";
|
|
12065
12165
|
import { execa as execa19 } from "execa";
|
|
12066
12166
|
|
|
12067
12167
|
// src/lib/plugin-tool-supply-audit.ts
|
|
12068
|
-
import { existsSync as
|
|
12069
|
-
import { join as
|
|
12168
|
+
import { existsSync as existsSync44, readFileSync as readFileSync38, readdirSync as readdirSync24, statSync as statSync14 } from "fs";
|
|
12169
|
+
import { join as join53 } from "path";
|
|
12070
12170
|
|
|
12071
12171
|
// src/lib/openrouter-model-snapshot.ts
|
|
12072
12172
|
var OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT = "2026-08-10T06:39:01Z";
|
|
@@ -12477,13 +12577,13 @@ var OPENROUTER_MODEL_IDS = [
|
|
|
12477
12577
|
function listDirs(root) {
|
|
12478
12578
|
let entries;
|
|
12479
12579
|
try {
|
|
12480
|
-
entries =
|
|
12580
|
+
entries = readdirSync24(root);
|
|
12481
12581
|
} catch {
|
|
12482
12582
|
return [];
|
|
12483
12583
|
}
|
|
12484
12584
|
return entries.filter((e) => {
|
|
12485
12585
|
try {
|
|
12486
|
-
return statSync14(
|
|
12586
|
+
return statSync14(join53(root, e)).isDirectory();
|
|
12487
12587
|
} catch {
|
|
12488
12588
|
return false;
|
|
12489
12589
|
}
|
|
@@ -12494,12 +12594,12 @@ function walkFiles2(root, accept, skipDir) {
|
|
|
12494
12594
|
const walk2 = (dir) => {
|
|
12495
12595
|
let entries;
|
|
12496
12596
|
try {
|
|
12497
|
-
entries =
|
|
12597
|
+
entries = readdirSync24(dir);
|
|
12498
12598
|
} catch {
|
|
12499
12599
|
return;
|
|
12500
12600
|
}
|
|
12501
12601
|
for (const entry of entries) {
|
|
12502
|
-
const p =
|
|
12602
|
+
const p = join53(dir, entry);
|
|
12503
12603
|
let st;
|
|
12504
12604
|
try {
|
|
12505
12605
|
st = statSync14(p);
|
|
@@ -12525,14 +12625,14 @@ function pluginPythonFiles(pluginDir2) {
|
|
|
12525
12625
|
);
|
|
12526
12626
|
}
|
|
12527
12627
|
function pluginTerraformFiles(pluginDir2) {
|
|
12528
|
-
const tfDir =
|
|
12628
|
+
const tfDir = join53(pluginDir2, "terraform");
|
|
12529
12629
|
let entries;
|
|
12530
12630
|
try {
|
|
12531
|
-
entries =
|
|
12631
|
+
entries = readdirSync24(tfDir);
|
|
12532
12632
|
} catch {
|
|
12533
12633
|
return [];
|
|
12534
12634
|
}
|
|
12535
|
-
return entries.filter((e) => e.endsWith(".tf")).map((e) =>
|
|
12635
|
+
return entries.filter((e) => e.endsWith(".tf")).map((e) => join53(tfDir, e)).sort();
|
|
12536
12636
|
}
|
|
12537
12637
|
function extractManifestTools(manifestText) {
|
|
12538
12638
|
let parsed;
|
|
@@ -12784,8 +12884,8 @@ function isSnapshotStale(fetchedAt, now) {
|
|
|
12784
12884
|
function normalizeModelId(id) {
|
|
12785
12885
|
return id.endsWith(":online") ? id.slice(0, -":online".length) : id;
|
|
12786
12886
|
}
|
|
12787
|
-
var CONFIG_PY_PATH =
|
|
12788
|
-
var ORCHESTRATION_SCHEMA_PATH =
|
|
12887
|
+
var CONFIG_PY_PATH = join53("services", "api", "src", "api", "config.py");
|
|
12888
|
+
var ORCHESTRATION_SCHEMA_PATH = join53(
|
|
12789
12889
|
"services",
|
|
12790
12890
|
"api",
|
|
12791
12891
|
"src",
|
|
@@ -12797,10 +12897,10 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
12797
12897
|
const knownModelIds = options.knownModelIds ?? OPENROUTER_MODEL_IDS;
|
|
12798
12898
|
const snapshotFetchedAt = options.snapshotFetchedAt ?? OPENROUTER_MODEL_SNAPSHOT_FETCHED_AT;
|
|
12799
12899
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
12800
|
-
const configPath =
|
|
12801
|
-
const orchestrationPath =
|
|
12802
|
-
const configMissing = !
|
|
12803
|
-
const orchestrationSchemaMissing = !
|
|
12900
|
+
const configPath = join53(repoRoot, CONFIG_PY_PATH);
|
|
12901
|
+
const orchestrationPath = join53(repoRoot, ORCHESTRATION_SCHEMA_PATH);
|
|
12902
|
+
const configMissing = !existsSync44(configPath);
|
|
12903
|
+
const orchestrationSchemaMissing = !existsSync44(orchestrationPath);
|
|
12804
12904
|
const knownSet = new Set(knownModelIds);
|
|
12805
12905
|
const snapshotEmpty = knownModelIds.length === 0;
|
|
12806
12906
|
const snapshotStale = isSnapshotStale(snapshotFetchedAt, now);
|
|
@@ -12871,7 +12971,7 @@ function auditDeclaredModelIds(repoRoot, options = {}) {
|
|
|
12871
12971
|
function discoverPluginDirs(pluginsRoot) {
|
|
12872
12972
|
return listDirs(pluginsRoot).filter((name) => {
|
|
12873
12973
|
try {
|
|
12874
|
-
return statSync14(
|
|
12974
|
+
return statSync14(join53(pluginsRoot, name, "biffo.plugin.json")).isFile();
|
|
12875
12975
|
} catch {
|
|
12876
12976
|
return false;
|
|
12877
12977
|
}
|
|
@@ -12884,8 +12984,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
12884
12984
|
let terraformBlind = false;
|
|
12885
12985
|
let totalDeclaredTools = 0;
|
|
12886
12986
|
for (const name of pluginNames) {
|
|
12887
|
-
const pluginDir2 =
|
|
12888
|
-
const manifestText = readFileSync38(
|
|
12987
|
+
const pluginDir2 = join53(pluginsRoot, name);
|
|
12988
|
+
const manifestText = readFileSync38(join53(pluginDir2, "biffo.plugin.json"), "utf8");
|
|
12889
12989
|
const manifest = extractManifestTools(manifestText);
|
|
12890
12990
|
if (manifest.parseError) {
|
|
12891
12991
|
findings.push({
|
|
@@ -12983,7 +13083,7 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
12983
13083
|
requiredEnvVars: envResult.envVars,
|
|
12984
13084
|
missingEnvVars: anyWired ? [] : envResult.envVars,
|
|
12985
13085
|
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 ${
|
|
13086
|
+
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
13087
|
});
|
|
12988
13088
|
}
|
|
12989
13089
|
}
|
|
@@ -13016,8 +13116,8 @@ function auditPluginToolSupply(pluginsRoot) {
|
|
|
13016
13116
|
async function runPluginToolSupplyCheck() {
|
|
13017
13117
|
const root = (await execa19("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13018
13118
|
let allOk = true;
|
|
13019
|
-
const pluginsRoot =
|
|
13020
|
-
if (!
|
|
13119
|
+
const pluginsRoot = join54(root, "services", "_plugins");
|
|
13120
|
+
if (!existsSync45(pluginsRoot)) {
|
|
13021
13121
|
console.log("\u2713 plugin tool-supply guard: no services/_plugins/ \u2014 nothing to audit");
|
|
13022
13122
|
} else {
|
|
13023
13123
|
const report = auditPluginToolSupply(pluginsRoot);
|
|
@@ -13047,8 +13147,8 @@ async function runPluginToolSupplyCheck() {
|
|
|
13047
13147
|
console.log(`\u2713 plugin tool-supply guard: ${report.summary}`);
|
|
13048
13148
|
}
|
|
13049
13149
|
}
|
|
13050
|
-
const servicesApiRoot =
|
|
13051
|
-
if (!
|
|
13150
|
+
const servicesApiRoot = join54(root, "services", "api");
|
|
13151
|
+
if (!existsSync45(servicesApiRoot)) {
|
|
13052
13152
|
console.log("\u2713 plugin model-id guard: no services/api/ \u2014 nothing to audit");
|
|
13053
13153
|
} else {
|
|
13054
13154
|
const modelReport = auditDeclaredModelIds(root);
|
|
@@ -13225,13 +13325,13 @@ async function runReleaseSubjectCheck(argv) {
|
|
|
13225
13325
|
}
|
|
13226
13326
|
|
|
13227
13327
|
// src/scripts/check-skeleton-drift.ts
|
|
13228
|
-
import { existsSync as
|
|
13229
|
-
import { join as
|
|
13328
|
+
import { existsSync as existsSync46, readdirSync as readdirSync26 } from "fs";
|
|
13329
|
+
import { join as join56 } from "path";
|
|
13230
13330
|
import { execa as execa21 } from "execa";
|
|
13231
13331
|
|
|
13232
13332
|
// src/lib/skeleton-drift-guard.ts
|
|
13233
|
-
import { readFileSync as readFileSync39, readdirSync as
|
|
13234
|
-
import { join as
|
|
13333
|
+
import { readFileSync as readFileSync39, readdirSync as readdirSync25, statSync as statSync15 } from "fs";
|
|
13334
|
+
import { join as join55 } from "path";
|
|
13235
13335
|
var isWorkflow = (rel) => rel.startsWith(".github/workflows/") && (rel.endsWith(".yml") || rel.endsWith(".yaml"));
|
|
13236
13336
|
var isRootLayout = (rel) => rel.endsWith("src/app/layout.tsx");
|
|
13237
13337
|
var uncommented = (contents) => contents.split("\n").filter((line) => !/^\s*(\/\/|\/\*|\*)/.test(line)).join("\n");
|
|
@@ -13289,13 +13389,13 @@ function walk(dir, base = dir) {
|
|
|
13289
13389
|
const out = [];
|
|
13290
13390
|
let entries;
|
|
13291
13391
|
try {
|
|
13292
|
-
entries =
|
|
13392
|
+
entries = readdirSync25(dir);
|
|
13293
13393
|
} catch {
|
|
13294
13394
|
return out;
|
|
13295
13395
|
}
|
|
13296
13396
|
for (const entry of entries) {
|
|
13297
13397
|
if (entry === ".venv" || entry === "node_modules" || entry === ".git") continue;
|
|
13298
|
-
const abs =
|
|
13398
|
+
const abs = join55(dir, entry);
|
|
13299
13399
|
let isDir;
|
|
13300
13400
|
try {
|
|
13301
13401
|
isDir = statSync15(abs).isDirectory();
|
|
@@ -13317,7 +13417,7 @@ function auditSkeleton(skeletonRoot, name, rules = SKELETON_RULES) {
|
|
|
13317
13417
|
if (!rule.appliesTo(rel)) continue;
|
|
13318
13418
|
let contents;
|
|
13319
13419
|
try {
|
|
13320
|
-
contents = readFileSync39(
|
|
13420
|
+
contents = readFileSync39(join55(skeletonRoot, rel), "utf8");
|
|
13321
13421
|
} catch {
|
|
13322
13422
|
continue;
|
|
13323
13423
|
}
|
|
@@ -13346,23 +13446,23 @@ function formatViolations2(violations) {
|
|
|
13346
13446
|
|
|
13347
13447
|
// src/scripts/check-skeleton-drift.ts
|
|
13348
13448
|
function discoverSkeletons(root) {
|
|
13349
|
-
const skeletonsDir =
|
|
13449
|
+
const skeletonsDir = join56(root, "_skeletons");
|
|
13350
13450
|
let entries;
|
|
13351
13451
|
try {
|
|
13352
|
-
entries =
|
|
13452
|
+
entries = readdirSync26(skeletonsDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
13353
13453
|
} catch {
|
|
13354
13454
|
return [];
|
|
13355
13455
|
}
|
|
13356
|
-
return entries.filter((name) =>
|
|
13456
|
+
return entries.filter((name) => existsSync46(join56(skeletonsDir, name, ".github", "workflows", "ci.yml"))).sort();
|
|
13357
13457
|
}
|
|
13358
13458
|
async function runSkeletonDriftCheck() {
|
|
13359
13459
|
const root = (await execa21("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
13360
13460
|
const skeletons = discoverSkeletons(root);
|
|
13361
13461
|
let filesConsidered = 0;
|
|
13362
13462
|
for (const name of skeletons) {
|
|
13363
|
-
const skeletonRoot =
|
|
13463
|
+
const skeletonRoot = join56(root, "_skeletons", name);
|
|
13364
13464
|
filesConsidered += findWorkflowFiles(skeletonRoot).length;
|
|
13365
|
-
if (
|
|
13465
|
+
if (existsSync46(join56(skeletonRoot, "apps", "frontend", "src", "app", "layout.tsx"))) {
|
|
13366
13466
|
filesConsidered += 1;
|
|
13367
13467
|
}
|
|
13368
13468
|
}
|
|
@@ -13376,7 +13476,7 @@ async function runSkeletonDriftCheck() {
|
|
|
13376
13476
|
process.exit(1);
|
|
13377
13477
|
}
|
|
13378
13478
|
const violations = skeletons.flatMap(
|
|
13379
|
-
(name) => auditSkeleton(
|
|
13479
|
+
(name) => auditSkeleton(join56(root, "_skeletons", name), name)
|
|
13380
13480
|
);
|
|
13381
13481
|
if (violations.length > 0) {
|
|
13382
13482
|
console.error("\u2717 Skeleton-drift guard: drift found between this repo and its scaffolding\n");
|
|
@@ -13514,8 +13614,8 @@ function rawArgsAfter(subcommand) {
|
|
|
13514
13614
|
}
|
|
13515
13615
|
|
|
13516
13616
|
// src/commands/doctor.ts
|
|
13517
|
-
import { existsSync as
|
|
13518
|
-
import { join as
|
|
13617
|
+
import { existsSync as existsSync47, readFileSync as readFileSync40 } from "fs";
|
|
13618
|
+
import { join as join57, resolve as resolve20 } from "path";
|
|
13519
13619
|
import chalk21 from "chalk";
|
|
13520
13620
|
import { Command as Command25 } from "commander";
|
|
13521
13621
|
|
|
@@ -13694,8 +13794,8 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
|
|
|
13694
13794
|
return runDoctorChecks(facts);
|
|
13695
13795
|
}
|
|
13696
13796
|
function readLocalCoreVersion(cwd) {
|
|
13697
|
-
const path =
|
|
13698
|
-
if (!
|
|
13797
|
+
const path = join57(cwd, INSTANCE_CORE_FILE);
|
|
13798
|
+
if (!existsSync47(path)) return null;
|
|
13699
13799
|
try {
|
|
13700
13800
|
return extractVersionField(readFileSync40(path, "utf8"));
|
|
13701
13801
|
} catch {
|
|
@@ -13717,8 +13817,8 @@ function extractVersionField(contents) {
|
|
|
13717
13817
|
return match?.[1] ?? null;
|
|
13718
13818
|
}
|
|
13719
13819
|
function readFossil(cwd) {
|
|
13720
|
-
const path =
|
|
13721
|
-
if (!
|
|
13820
|
+
const path = join57(cwd, CORE_VERSION_FILE);
|
|
13821
|
+
if (!existsSync47(path)) return null;
|
|
13722
13822
|
try {
|
|
13723
13823
|
const value = readFileSync40(path, "utf8").trim();
|
|
13724
13824
|
return value === "" ? null : value;
|
|
@@ -14169,13 +14269,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
14169
14269
|
import { Command as Command27 } from "commander";
|
|
14170
14270
|
|
|
14171
14271
|
// src/lib/packaged-scripts.ts
|
|
14172
|
-
import { existsSync as
|
|
14173
|
-
import { dirname as dirname11, join as
|
|
14272
|
+
import { existsSync as existsSync48 } from "fs";
|
|
14273
|
+
import { dirname as dirname11, join as join58 } from "path";
|
|
14174
14274
|
function findPackagedScript(startDir, relativePath) {
|
|
14175
14275
|
let dir = startDir;
|
|
14176
14276
|
for (; ; ) {
|
|
14177
|
-
const candidate =
|
|
14178
|
-
if (
|
|
14277
|
+
const candidate = join58(dir, relativePath);
|
|
14278
|
+
if (existsSync48(candidate)) return candidate;
|
|
14179
14279
|
const parent = dirname11(dir);
|
|
14180
14280
|
if (parent === dir) return null;
|
|
14181
14281
|
dir = parent;
|