@biffo/cli 0.231.1 → 0.231.2

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.
Files changed (2) hide show
  1. package/dist/index.js +364 -286
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -570,8 +570,8 @@ async function runCoreStatus(options) {
570
570
 
571
571
  // src/commands/core-upgrade.ts
572
572
  import { execSync as execSync2 } from "child_process";
573
- import { existsSync as existsSync11, rmSync as rmSync5 } from "fs";
574
- import { join as join12, resolve as resolve3 } from "path";
573
+ import { existsSync as existsSync12, rmSync as rmSync5 } from "fs";
574
+ import { join as join13, resolve as resolve3 } from "path";
575
575
  import chalk4 from "chalk";
576
576
  import { execa as execa3 } from "execa";
577
577
  import { Command as Command3 } from "commander";
@@ -2774,9 +2774,48 @@ function materializeTemplateAtTag(repo, version, git = defaultGit2) {
2774
2774
  return { dir, cleanup: () => rmSync3(dir, { recursive: true, force: true }) };
2775
2775
  }
2776
2776
 
2777
- // src/lib/breaking-changes.ts
2777
+ // src/lib/instance-seams.ts
2778
2778
  import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
2779
2779
  import { join as join9 } from "path";
2780
+ var INSTANCE_SEAM_PREFIX = "@/instance-";
2781
+ function readSeams(templateDir, portalRelDir) {
2782
+ const seams = /* @__PURE__ */ new Map();
2783
+ const tsconfigPath = join9(templateDir, portalRelDir, "tsconfig.json");
2784
+ if (!existsSync8(tsconfigPath)) return seams;
2785
+ let parsed;
2786
+ try {
2787
+ parsed = JSON.parse(readFileSync6(tsconfigPath, "utf8"));
2788
+ } catch {
2789
+ return seams;
2790
+ }
2791
+ for (const [specifier, targets] of Object.entries(parsed.compilerOptions?.paths ?? {})) {
2792
+ if (!specifier.startsWith(INSTANCE_SEAM_PREFIX)) continue;
2793
+ const target = targets[0];
2794
+ if (target === void 0) continue;
2795
+ const name = specifier.slice("@/".length);
2796
+ seams.set(specifier, {
2797
+ specifier,
2798
+ instanceFile: `${portalRelDir}/src/${name}.ts`,
2799
+ defaultFile: `${portalRelDir}/${target.replace(/^\.\//, "")}`
2800
+ });
2801
+ }
2802
+ return seams;
2803
+ }
2804
+ function findNewUndeclaredSeams(baseDir, theirsDir, oursDir, portalRelDir = "apps/portal") {
2805
+ const baseSeams = readSeams(baseDir, portalRelDir);
2806
+ const theirsSeams = readSeams(theirsDir, portalRelDir);
2807
+ const undeclared = [];
2808
+ for (const [specifier, seam] of theirsSeams) {
2809
+ if (baseSeams.has(specifier)) continue;
2810
+ if (existsSync8(join9(oursDir, seam.instanceFile))) continue;
2811
+ undeclared.push(seam);
2812
+ }
2813
+ return undeclared.sort((a, b) => a.specifier.localeCompare(b.specifier));
2814
+ }
2815
+
2816
+ // src/lib/breaking-changes.ts
2817
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
2818
+ import { join as join10 } from "path";
2780
2819
  var UPGRADE_GUIDE_PATH = "docs/guides/core-upgrade.md";
2781
2820
  var SECTION_HEADING = "## Breaking changes by version";
2782
2821
  var ENTRY_HEADING = /^###\s+(\d+\.\d+\.\d+)\s*[—-]\s*(.+?)\s*$/;
@@ -2801,9 +2840,9 @@ function parseBreakingChanges(guide) {
2801
2840
  return entries;
2802
2841
  }
2803
2842
  function readBreakingChanges(templateRoot) {
2804
- const path = join9(templateRoot, UPGRADE_GUIDE_PATH);
2805
- if (!existsSync8(path)) return [];
2806
- return parseBreakingChanges(readFileSync6(path, "utf8"));
2843
+ const path = join10(templateRoot, UPGRADE_GUIDE_PATH);
2844
+ if (!existsSync9(path)) return [];
2845
+ return parseBreakingChanges(readFileSync7(path, "utf8"));
2807
2846
  }
2808
2847
  function breakingChangesBetween(from, to, entries) {
2809
2848
  parseCoreVersion(from);
@@ -2820,8 +2859,8 @@ var GLOBAL_DISPATCH_WORKFLOW_PATHS = [
2820
2859
  ];
2821
2860
 
2822
2861
  // src/lib/plugin-terraform-wiring.ts
2823
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
2824
- import { join as join10 } from "path";
2862
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
2863
+ import { join as join11 } from "path";
2825
2864
  var TEMPLATE_MODULE_DIR = "_template";
2826
2865
  var DEFAULT_PLUGIN_HANDLER = "src.lambda.main.handler";
2827
2866
  var GENERATED_TF_FILE = "plugins.generated.tf";
@@ -2846,7 +2885,7 @@ function standardArguments(pluginName, handler) {
2846
2885
  ];
2847
2886
  }
2848
2887
  function listPluginModules(cwd) {
2849
- const dir = join10(cwd, "modules", "plugins");
2888
+ const dir = join11(cwd, "modules", "plugins");
2850
2889
  let entries;
2851
2890
  try {
2852
2891
  entries = readdirSync3(dir, { withFileTypes: true });
@@ -2858,7 +2897,7 @@ function listPluginModules(cwd) {
2858
2897
  var FIRST_PARTY_TERRAFORM = (name) => `../../../services/_plugins/${name}/terraform`;
2859
2898
  var THIRD_PARTY_TERRAFORM = (name) => `../../../modules/plugins/${name}`;
2860
2899
  function isFirstPartyPlugin(cwd, name) {
2861
- return existsSync9(join10(cwd, "services", "_plugins", name, "terraform", "main.tf"));
2900
+ return existsSync10(join11(cwd, "services", "_plugins", name, "terraform", "main.tf"));
2862
2901
  }
2863
2902
  function pluginModuleSource(cwd, name) {
2864
2903
  return isFirstPartyPlugin(cwd, name) ? FIRST_PARTY_TERRAFORM(name) : THIRD_PARTY_TERRAFORM(name);
@@ -2867,7 +2906,7 @@ function listWireablePlugins(cwd) {
2867
2906
  return listPluginModules(cwd).filter((name) => !isFirstPartyPlugin(cwd, name)).sort();
2868
2907
  }
2869
2908
  function firstPartyPluginNames(cwd) {
2870
- const dir = join10(cwd, "services", "_plugins");
2909
+ const dir = join11(cwd, "services", "_plugins");
2871
2910
  let entries;
2872
2911
  try {
2873
2912
  entries = readdirSync3(dir, { withFileTypes: true });
@@ -2881,7 +2920,7 @@ function staleFirstPartyCopies(cwd) {
2881
2920
  return firstPartyPluginNames(cwd).filter((name) => copied.has(name));
2882
2921
  }
2883
2922
  function listEnvironments(cwd) {
2884
- const dir = join10(cwd, "infra", "environments");
2923
+ const dir = join11(cwd, "infra", "environments");
2885
2924
  let entries;
2886
2925
  try {
2887
2926
  entries = readdirSync3(dir, { withFileTypes: true });
@@ -2889,12 +2928,12 @@ function listEnvironments(cwd) {
2889
2928
  return [];
2890
2929
  }
2891
2930
  return entries.filter((e) => {
2892
- if (!e.isDirectory() || !existsSync9(join10(dir, e.name, "main.tf"))) return false;
2893
- return declaredVariables(join10(dir, e.name)).has("enabled_plugins");
2931
+ if (!e.isDirectory() || !existsSync10(join11(dir, e.name, "main.tf"))) return false;
2932
+ return declaredVariables(join11(dir, e.name)).has("enabled_plugins");
2894
2933
  }).map((e) => e.name).sort();
2895
2934
  }
2896
2935
  function listUnwirableEnvironments(cwd) {
2897
- const dir = join10(cwd, "infra", "environments");
2936
+ const dir = join11(cwd, "infra", "environments");
2898
2937
  let entries;
2899
2938
  try {
2900
2939
  entries = readdirSync3(dir, { withFileTypes: true });
@@ -2902,7 +2941,7 @@ function listUnwirableEnvironments(cwd) {
2902
2941
  return [];
2903
2942
  }
2904
2943
  return entries.filter(
2905
- (e) => e.isDirectory() && existsSync9(join10(dir, e.name, "main.tf")) && !declaredVariables(join10(dir, e.name)).has("enabled_plugins")
2944
+ (e) => e.isDirectory() && existsSync10(join11(dir, e.name, "main.tf")) && !declaredVariables(join11(dir, e.name)).has("enabled_plugins")
2906
2945
  ).map((e) => e.name).sort();
2907
2946
  }
2908
2947
  function declaredVariables(moduleDir) {
@@ -2917,7 +2956,7 @@ function declaredVariables(moduleDir) {
2917
2956
  if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
2918
2957
  let contents;
2919
2958
  try {
2920
- contents = readFileSync7(join10(moduleDir, entry.name), "utf8");
2959
+ contents = readFileSync8(join11(moduleDir, entry.name), "utf8");
2921
2960
  } catch {
2922
2961
  continue;
2923
2962
  }
@@ -2939,7 +2978,7 @@ function declaredOutputs(moduleDir) {
2939
2978
  if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
2940
2979
  let contents;
2941
2980
  try {
2942
- contents = readFileSync7(join10(moduleDir, entry.name), "utf8");
2981
+ contents = readFileSync8(join11(moduleDir, entry.name), "utf8");
2943
2982
  } catch {
2944
2983
  continue;
2945
2984
  }
@@ -3021,7 +3060,7 @@ function syncPluginTerraform(cwd) {
3021
3060
  const skippedEnvironments = listUnwirableEnvironments(cwd);
3022
3061
  const changedPaths = [];
3023
3062
  const rendered = plugins.map((name) => {
3024
- const moduleDir = join10(cwd, "modules", "plugins", name);
3063
+ const moduleDir = join11(cwd, "modules", "plugins", name);
3025
3064
  return {
3026
3065
  name,
3027
3066
  declaredVariables: declaredVariables(moduleDir),
@@ -3030,16 +3069,16 @@ function syncPluginTerraform(cwd) {
3030
3069
  };
3031
3070
  });
3032
3071
  for (const env of environments) {
3033
- const envDir = join10(cwd, "infra", "environments", env);
3034
- const tfPath = join10(envDir, GENERATED_TF_FILE);
3035
- const tfvarsPath = join10(envDir, GENERATED_TFVARS_FILE);
3072
+ const envDir = join11(cwd, "infra", "environments", env);
3073
+ const tfPath = join11(envDir, GENERATED_TF_FILE);
3074
+ const tfvarsPath = join11(envDir, GENERATED_TFVARS_FILE);
3036
3075
  const relBase = `infra/environments/${env}`;
3037
3076
  if (plugins.length === 0) {
3038
3077
  for (const [abs, rel] of [
3039
3078
  [tfPath, `${relBase}/${GENERATED_TF_FILE}`],
3040
3079
  [tfvarsPath, `${relBase}/${GENERATED_TFVARS_FILE}`]
3041
3080
  ]) {
3042
- if (existsSync9(abs)) {
3081
+ if (existsSync10(abs)) {
3043
3082
  rmSync4(abs);
3044
3083
  changedPaths.push(rel);
3045
3084
  }
@@ -3055,8 +3094,8 @@ function syncPluginTerraform(cwd) {
3055
3094
  }
3056
3095
 
3057
3096
  // src/lib/lockfile-refresh.ts
3058
- import { existsSync as existsSync10 } from "fs";
3059
- import { join as join11 } from "path";
3097
+ import { existsSync as existsSync11 } from "fs";
3098
+ import { join as join12 } from "path";
3060
3099
  var LOCKFILE_TRIGGERS = [
3061
3100
  {
3062
3101
  manifest: "package.json",
@@ -3079,7 +3118,7 @@ function lockfilesNeedingRefresh(changedPaths, instanceDir, triggers = LOCKFILE_
3079
3118
  const locked = changedPaths.filter((p) => !isForeignManifest(p));
3080
3119
  return triggers.filter((t) => {
3081
3120
  const touched = locked.some((p) => p === t.manifest || p.endsWith(`/${t.manifest}`));
3082
- return touched && existsSync10(join11(instanceDir, t.lockfile));
3121
+ return touched && existsSync11(join12(instanceDir, t.lockfile));
3083
3122
  });
3084
3123
  }
3085
3124
  async function refreshLockfiles(instanceDir, triggers, run) {
@@ -3288,6 +3327,7 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
3288
3327
  manifest
3289
3328
  });
3290
3329
  const orphanRatchet = checkOrphanRatchet(plan.orphaned.length, readOrphanBaseline(options.cwd));
3330
+ const newSeams = findNewUndeclaredSeams(baseDir, theirsDir, options.cwd);
3291
3331
  const heading = options.apply ? "Biffo core upgrade" : "Biffo core upgrade (dry run)";
3292
3332
  console.log(chalk4.bold(`
3293
3333
  ${heading}
@@ -3296,6 +3336,7 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
3296
3336
  console.log(` merge base: ${fromVersion}`);
3297
3337
  console.log(` target: ${toVersion}
3298
3338
  `);
3339
+ printNewInstanceSeams(newSeams);
3299
3340
  printOrphanReport(plan.orphaned, orphanRatchet);
3300
3341
  if (orphanRatchet.increased) {
3301
3342
  throw new Error(
@@ -3358,10 +3399,11 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
3358
3399
  breaking,
3359
3400
  theirsDir,
3360
3401
  coreVersionCleanup,
3361
- orphanRatchet
3402
+ orphanRatchet,
3403
+ newSeams
3362
3404
  );
3363
3405
  }
3364
- async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking, theirsDir, coreVersionCleanup, orphanRatchet) {
3406
+ async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking, theirsDir, coreVersionCleanup, orphanRatchet, newSeams) {
3365
3407
  if (breaking.length > 0 && !options.acknowledgeBreaking) {
3366
3408
  throw new Error(
3367
3409
  `This upgrade crosses ${breaking.length} documented breaking change(s): ${breaking.map((b) => b.version).join(", ")}. They are printed above and in ${UPGRADE_GUIDE_PATH}. Read what each one requires \u2014 some destroy data or need manual work after the deploy \u2014 then re-run with --acknowledge-breaking.`
@@ -3393,6 +3435,7 @@ async function applyAndOpenPr(options, deps, plan, migrations, fromVersion, toVe
3393
3435
  theirsDir,
3394
3436
  coreVersionCleanup,
3395
3437
  orphanRatchet,
3438
+ newSeams,
3396
3439
  branch,
3397
3440
  token
3398
3441
  );
@@ -3427,7 +3470,7 @@ async function restoreCallerBranch(git, cwd, callerBranch, upgradeBranch) {
3427
3470
  );
3428
3471
  }
3429
3472
  }
3430
- async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking, theirsDir, coreVersionCleanup, orphanRatchet, branch, token) {
3473
+ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion, toVersion, breaking, theirsDir, coreVersionCleanup, orphanRatchet, newSeams, branch, token) {
3431
3474
  const { git } = deps;
3432
3475
  const applied = applyUpgradePlan(options.cwd, plan, theirsDir);
3433
3476
  const carried = applyMigrationCarry(options.cwd, migrations);
@@ -3440,7 +3483,7 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
3440
3483
  );
3441
3484
  }
3442
3485
  const cleanedCoreVersion = coreVersionCleanup?.action === "delete";
3443
- if (cleanedCoreVersion && existsSync11(coreVersionCleanup.path)) {
3486
+ if (cleanedCoreVersion && existsSync12(coreVersionCleanup.path)) {
3444
3487
  rmSync5(coreVersionCleanup.path);
3445
3488
  log.info(
3446
3489
  coreVersionCleanup.stale ? `Deleted stale ${CORE_VERSION_FILE} (recorded ${coreVersionCleanup.found}, behind the version biffo.core.json records \u2014 this instance has moved past it) (#842).` : `Deleted orphaned ${CORE_VERSION_FILE} (inherited copy recording ${coreVersionCleanup.found}, superseded by biffo.core.json) \u2014 nothing reads it as an authority (#434).`
@@ -3479,7 +3522,8 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
3479
3522
  lockfiles,
3480
3523
  breaking,
3481
3524
  cleanedCoreVersion ? coreVersionCleanup : null,
3482
- carriedPrs
3525
+ carriedPrs,
3526
+ newSeams
3483
3527
  )
3484
3528
  });
3485
3529
  if (plan.conflicts.length > 0) {
@@ -3535,7 +3579,7 @@ function carriedPrNumbers(subjects) {
3535
3579
  }
3536
3580
  return [...new Set(numbers)].sort((a, b) => a - b);
3537
3581
  }
3538
- function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = [], breaking = [], coreVersionCleanup = null, carriedPrs = []) {
3582
+ function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, lockfiles = [], breaking = [], coreVersionCleanup = null, carriedPrs = [], newSeams = []) {
3539
3583
  const lines = [];
3540
3584
  if (breaking.length > 0) {
3541
3585
  lines.push(
@@ -3548,6 +3592,25 @@ function buildPrBody(from, to, plan, migrations, base = GLOBAL_DISPATCH_REF, loc
3548
3592
  ""
3549
3593
  );
3550
3594
  }
3595
+ if (newSeams.length > 0) {
3596
+ lines.push(
3597
+ `## \u26A0 ${newSeams.length} new instance seam(s) \u2014 no declaration yet (#1188)`,
3598
+ "",
3599
+ "This upgrade introduces a new optional `@/instance-*` module the portal can resolve to an instance-owned override. **This instance has not declared one**, so until it does, it silently gets the template-owned generic default listed below \u2014 not necessarily the behaviour this instance actually wants.",
3600
+ "",
3601
+ ...newSeams.flatMap((s) => [
3602
+ `### \`${s.specifier}\``,
3603
+ "",
3604
+ `- Add \`${s.instanceFile}\` to declare this instance's own behaviour.`,
3605
+ `- Until then, this instance uses the template default: \`${s.defaultFile}\`.`,
3606
+ ""
3607
+ ]),
3608
+ "The contract this module implements is part of THIS upgrade, so it does not exist yet anywhere the instance could have written the file in advance \u2014 add it as a follow-up commit on this PR, before merging, once the files above have landed on this branch.",
3609
+ "",
3610
+ "---",
3611
+ ""
3612
+ );
3613
+ }
3551
3614
  lines.push(
3552
3615
  "Automated core upgrade generated by `biffo core upgrade` (ADR-0006).",
3553
3616
  "",
@@ -3723,6 +3786,21 @@ function printCoreVersionCleanup(cleanup, applying) {
3723
3786
  const why = cleanup.reason === "repurposed" ? `does not match biffo.core.json \u2014 looks repurposed, keeping ${CORE_VERSION_FILE}` : `biffo.core.json absent or unparseable \u2014 no authority to check, keeping ${CORE_VERSION_FILE}`;
3724
3787
  console.log(` ${chalk4.dim("cleanup".padEnd(15))} ${chalk4.dim(`${cleanup.found}: ${why}`)}`);
3725
3788
  }
3789
+ function printNewInstanceSeams(seams) {
3790
+ if (seams.length === 0) return;
3791
+ console.log(
3792
+ chalk4.red.bold(
3793
+ ` \u26A0 ${String(seams.length)} new instance seam(s) introduced with no declaration (#1188):`
3794
+ )
3795
+ );
3796
+ for (const s of seams) {
3797
+ console.log(` ${chalk4.bold(s.specifier)} \u2192 add ${chalk4.yellow(s.instanceFile)}`);
3798
+ console.log(
3799
+ chalk4.dim(` Until then, this instance gets the template default: ${s.defaultFile}`)
3800
+ );
3801
+ }
3802
+ console.log();
3803
+ }
3726
3804
  function printOrphanReport(orphaned, ratchet) {
3727
3805
  if (orphaned.length === 0 && ratchet.baseline === null) return;
3728
3806
  console.log(
@@ -3816,8 +3894,8 @@ function printBreakingChanges(breaking, applying) {
3816
3894
  }
3817
3895
  function versionOfCheckout(dir, explicit) {
3818
3896
  if (explicit) return explicit;
3819
- const file = join12(dir, CORE_VERSION_FILE);
3820
- if (existsSync11(file)) return readCoreVersionFile(file);
3897
+ const file = join13(dir, CORE_VERSION_FILE);
3898
+ if (existsSync12(file)) return readCoreVersionFile(file);
3821
3899
  throw new Error(
3822
3900
  `Cannot determine the core version of ${dir}: it has no ${CORE_VERSION_FILE}, and a checkout supplied explicitly is not resolved from a tag. Pass --to to state which version this tree is.`
3823
3901
  );
@@ -3825,8 +3903,8 @@ function versionOfCheckout(dir, explicit) {
3825
3903
  function latestCoreVersion(repo) {
3826
3904
  const fromTags = latestCoreVersionFromTags(repo);
3827
3905
  if (fromTags) return fromTags;
3828
- const file = join12(repo, CORE_VERSION_FILE);
3829
- if (existsSync11(file)) return readCoreVersionFile(file);
3906
+ const file = join13(repo, CORE_VERSION_FILE);
3907
+ if (existsSync12(file)) return readCoreVersionFile(file);
3830
3908
  throw new Error(
3831
3909
  `Cannot determine the template's core version: ${repo} has no core-v* tags and no ${CORE_VERSION_FILE}. Fetch tags (\`git fetch --tags\`) or pass --to explicitly.`
3832
3910
  );
@@ -3844,7 +3922,7 @@ coreCommand.addCommand(coreUpgradeCommand);
3844
3922
  import { Command as Command8 } from "commander";
3845
3923
 
3846
3924
  // src/commands/data-apply.ts
3847
- import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
3925
+ import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
3848
3926
  import { resolve as resolve4 } from "path";
3849
3927
  import chalk5 from "chalk";
3850
3928
  import { Command as Command5 } from "commander";
@@ -4295,16 +4373,16 @@ function isTemplatePlaceholderConfig(raw) {
4295
4373
 
4296
4374
  // src/lib/session.ts
4297
4375
  import {
4298
- existsSync as existsSync12,
4376
+ existsSync as existsSync13,
4299
4377
  mkdirSync as mkdirSync4,
4300
4378
  readdirSync as readdirSync4,
4301
- readFileSync as readFileSync8,
4379
+ readFileSync as readFileSync9,
4302
4380
  rmSync as rmSync6,
4303
4381
  statSync as statSync2,
4304
4382
  writeFileSync as writeFileSync5
4305
4383
  } from "fs";
4306
4384
  import { homedir } from "os";
4307
- import { join as join13 } from "path";
4385
+ import { join as join14 } from "path";
4308
4386
  var LEGACY_STEP_ALIASES = {
4309
4387
  github_config: ["github_branches", "github_instance_files", "github_settings"]
4310
4388
  };
@@ -4313,39 +4391,39 @@ function hasCompleted(session, step) {
4313
4391
  return session.completedSteps.some((done) => LEGACY_STEP_ALIASES[done]?.includes(step) ?? false);
4314
4392
  }
4315
4393
  function sessionsDir() {
4316
- return process.env["BIFFO_SESSIONS_DIR"] ?? join13(homedir(), ".biffo", "sessions");
4394
+ return process.env["BIFFO_SESSIONS_DIR"] ?? join14(homedir(), ".biffo", "sessions");
4317
4395
  }
4318
4396
  function sessionPath(projectName) {
4319
- return join13(sessionsDir(), `${projectName}.json`);
4397
+ return join14(sessionsDir(), `${projectName}.json`);
4320
4398
  }
4321
4399
  function loadSession(projectName) {
4322
4400
  const path = sessionPath(projectName);
4323
- if (!existsSync12(path)) return null;
4401
+ if (!existsSync13(path)) return null;
4324
4402
  try {
4325
- return JSON.parse(readFileSync8(path, "utf8"));
4403
+ return JSON.parse(readFileSync9(path, "utf8"));
4326
4404
  } catch {
4327
4405
  return null;
4328
4406
  }
4329
4407
  }
4330
4408
  function findLatestSession() {
4331
4409
  const dir = sessionsDir();
4332
- if (!existsSync12(dir)) return null;
4410
+ if (!existsSync13(dir)) return null;
4333
4411
  const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
4334
4412
  if (files.length === 0) return null;
4335
4413
  const sorted = files.map((f) => {
4336
- const fullPath = join13(dir, f);
4337
- const mtime = existsSync12(fullPath) ? statSync2(fullPath).mtimeMs : -1;
4414
+ const fullPath = join14(dir, f);
4415
+ const mtime = existsSync13(fullPath) ? statSync2(fullPath).mtimeMs : -1;
4338
4416
  return { f, mtime };
4339
4417
  }).sort((a, b) => b.mtime - a.mtime);
4340
4418
  try {
4341
- return JSON.parse(readFileSync8(join13(dir, sorted[0].f), "utf8"));
4419
+ return JSON.parse(readFileSync9(join14(dir, sorted[0].f), "utf8"));
4342
4420
  } catch {
4343
4421
  return null;
4344
4422
  }
4345
4423
  }
4346
4424
  function saveSession(session) {
4347
4425
  const dir = sessionsDir();
4348
- if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
4426
+ if (!existsSync13(dir)) mkdirSync4(dir, { recursive: true });
4349
4427
  const name = session.config.project?.name ?? "unknown";
4350
4428
  const prior = loadSession(name);
4351
4429
  if (prior) {
@@ -4367,36 +4445,36 @@ function markStepComplete(session, step) {
4367
4445
  }
4368
4446
  function deleteSession(projectName) {
4369
4447
  const path = sessionPath(projectName);
4370
- if (existsSync12(path)) rmSync6(path);
4448
+ if (existsSync13(path)) rmSync6(path);
4371
4449
  }
4372
4450
  function projectsDir() {
4373
- return process.env["BIFFO_PROJECTS_DIR"] ?? join13(homedir(), ".biffo", "projects");
4451
+ return process.env["BIFFO_PROJECTS_DIR"] ?? join14(homedir(), ".biffo", "projects");
4374
4452
  }
4375
4453
  function saveProjectConfig(config) {
4376
4454
  const dir = projectsDir();
4377
- if (!existsSync12(dir)) mkdirSync4(dir, { recursive: true });
4378
- writeFileSync5(join13(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
4455
+ if (!existsSync13(dir)) mkdirSync4(dir, { recursive: true });
4456
+ writeFileSync5(join14(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
4379
4457
  }
4380
4458
  function loadProjectConfig(name) {
4381
- const path = join13(projectsDir(), `${name}.json`);
4382
- if (!existsSync12(path)) return null;
4459
+ const path = join14(projectsDir(), `${name}.json`);
4460
+ if (!existsSync13(path)) return null;
4383
4461
  try {
4384
- const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync8(path, "utf8")));
4462
+ const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(path, "utf8")));
4385
4463
  return result.success ? result.data : null;
4386
4464
  } catch {
4387
4465
  return null;
4388
4466
  }
4389
4467
  }
4390
4468
  function deleteProjectConfig(name) {
4391
- const path = join13(projectsDir(), `${name}.json`);
4392
- if (existsSync12(path)) rmSync6(path);
4469
+ const path = join14(projectsDir(), `${name}.json`);
4470
+ if (existsSync13(path)) rmSync6(path);
4393
4471
  }
4394
4472
  function listProjectConfigs() {
4395
4473
  const dir = projectsDir();
4396
- if (!existsSync12(dir)) return [];
4474
+ if (!existsSync13(dir)) return [];
4397
4475
  return readdirSync4(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
4398
4476
  try {
4399
- const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync8(join13(dir, f), "utf8")));
4477
+ const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(join14(dir, f), "utf8")));
4400
4478
  return result.success ? [result.data] : [];
4401
4479
  } catch {
4402
4480
  return [];
@@ -4465,7 +4543,7 @@ async function runDataApply(name, environment, config, aws) {
4465
4543
  }
4466
4544
  async function resolveConfig(options) {
4467
4545
  if (options.config) {
4468
- const raw = JSON.parse(readFileSync9(resolve4(options.config), "utf8"));
4546
+ const raw = JSON.parse(readFileSync10(resolve4(options.config), "utf8"));
4469
4547
  const result = BiffoConfigSchema.safeParse(raw);
4470
4548
  if (!result.success) {
4471
4549
  log.error(`Invalid config at ${options.config}:`);
@@ -4485,8 +4563,8 @@ async function resolveConfig(options) {
4485
4563
  return cfg;
4486
4564
  }
4487
4565
  const localConfigPath = resolve4(process.cwd(), "biffo.config.json");
4488
- if (existsSync13(localConfigPath)) {
4489
- const raw = JSON.parse(readFileSync9(localConfigPath, "utf8"));
4566
+ if (existsSync14(localConfigPath)) {
4567
+ const raw = JSON.parse(readFileSync10(localConfigPath, "utf8"));
4490
4568
  const result = BiffoConfigSchema.safeParse(raw);
4491
4569
  if (result.success) return result.data;
4492
4570
  if (isTemplatePlaceholderConfig(raw)) {
@@ -4532,8 +4610,8 @@ async function resolveConfig(options) {
4532
4610
 
4533
4611
  // src/commands/data-import.ts
4534
4612
  import { execSync as execSync3 } from "child_process";
4535
- import { cpSync, existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
4536
- import { join as join14, resolve as resolve5 } from "path";
4613
+ import { cpSync, existsSync as existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
4614
+ import { join as join15, resolve as resolve5 } from "path";
4537
4615
  import chalk6 from "chalk";
4538
4616
  import { Command as Command6 } from "commander";
4539
4617
  import inquirer2 from "inquirer";
@@ -4573,23 +4651,23 @@ async function runDataImport(name, options, deps) {
4573
4651
  `Invalid import name '${name}'. Use lowercase letters, numbers, and hyphens, starting with a letter.`
4574
4652
  );
4575
4653
  }
4576
- const servicesDir = join14(options.cwd, "services");
4577
- if (!existsSync14(servicesDir)) {
4654
+ const servicesDir = join15(options.cwd, "services");
4655
+ if (!existsSync15(servicesDir)) {
4578
4656
  throw new Error(
4579
4657
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
4580
4658
  );
4581
4659
  }
4582
- const targetDir = join14(options.cwd, "db", "imports", name);
4583
- if (existsSync14(targetDir)) {
4660
+ const targetDir = join15(options.cwd, "db", "imports", name);
4661
+ if (existsSync15(targetDir)) {
4584
4662
  throw new Error(
4585
4663
  `DDL import '${name}' is already present at db/imports/${name}/. Remove it first to re-import.`
4586
4664
  );
4587
4665
  }
4588
- const isLocalDir = existsSync14(options.source) && statSync3(options.source).isDirectory();
4666
+ const isLocalDir = existsSync15(options.source) && statSync3(options.source).isDirectory();
4589
4667
  let sourceDir;
4590
4668
  let cleanupClone = null;
4591
4669
  if (isLocalDir) {
4592
- sourceDir = options.path ? join14(options.source, options.path) : options.source;
4670
+ sourceDir = options.path ? join15(options.source, options.path) : options.source;
4593
4671
  } else {
4594
4672
  const token = options.token ?? await resolveDdlImportToken();
4595
4673
  log.info(`Cloning ${options.source}...`);
@@ -4597,10 +4675,10 @@ async function runDataImport(name, options, deps) {
4597
4675
  cleanupClone = () => {
4598
4676
  deps.git.cleanup(tmpDir);
4599
4677
  };
4600
- sourceDir = options.path ? join14(tmpDir, options.path) : tmpDir;
4678
+ sourceDir = options.path ? join15(tmpDir, options.path) : tmpDir;
4601
4679
  }
4602
4680
  try {
4603
- if (!existsSync14(sourceDir)) {
4681
+ if (!existsSync15(sourceDir)) {
4604
4682
  throw new Error(`Source directory does not exist: ${sourceDir}`);
4605
4683
  }
4606
4684
  const sqlFiles = readdirSync5(sourceDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name).sort();
@@ -4625,7 +4703,7 @@ async function runDataImport(name, options, deps) {
4625
4703
  }
4626
4704
  mkdirSync5(targetDir, { recursive: true });
4627
4705
  for (const file of sqlFiles) {
4628
- cpSync(join14(sourceDir, file), join14(targetDir, file));
4706
+ cpSync(join15(sourceDir, file), join15(targetDir, file));
4629
4707
  }
4630
4708
  log.success(`Imported ${String(sqlFiles.length)} .sql file(s) to db/imports/${name}/`);
4631
4709
  const commitMessage = `feat(data): import ${name} (${String(sqlFiles.length)} SQL file(s))`;
@@ -4677,8 +4755,8 @@ function printDryRun(name, sqlFiles) {
4677
4755
  }
4678
4756
 
4679
4757
  // src/commands/data-list.ts
4680
- import { existsSync as existsSync15, readdirSync as readdirSync6 } from "fs";
4681
- import { join as join15, resolve as resolve6 } from "path";
4758
+ import { existsSync as existsSync16, readdirSync as readdirSync6 } from "fs";
4759
+ import { join as join16, resolve as resolve6 } from "path";
4682
4760
  import chalk7 from "chalk";
4683
4761
  import { Command as Command7 } from "commander";
4684
4762
  var dataListCommand = new Command7("list").description("List DDL imports vendored in this project checkout").option("--cwd <path>", "Project root to scan (defaults to the current directory)").action(async (options) => {
@@ -4691,15 +4769,15 @@ var dataListCommand = new Command7("list").description("List DDL imports vendore
4691
4769
  }
4692
4770
  });
4693
4771
  async function runDataList(options) {
4694
- const importsDir = join15(options.cwd, "db", "imports");
4695
- if (!existsSync15(importsDir)) {
4772
+ const importsDir = join16(options.cwd, "db", "imports");
4773
+ if (!existsSync16(importsDir)) {
4696
4774
  console.log(chalk7.dim("\n No DDL imports in this checkout.\n"));
4697
4775
  return;
4698
4776
  }
4699
4777
  const candidates = readdirSync6(importsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
4700
4778
  const imports = [];
4701
4779
  for (const name of candidates) {
4702
- const fileCount = readdirSync6(join15(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
4780
+ const fileCount = readdirSync6(join16(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
4703
4781
  if (fileCount > 0) imports.push({ name, fileCount });
4704
4782
  }
4705
4783
  if (imports.length === 0) {
@@ -4729,7 +4807,7 @@ dataCommand.addCommand(dataListCommand);
4729
4807
 
4730
4808
  // src/commands/deploy.ts
4731
4809
  import { execSync as execSync4 } from "child_process";
4732
- import { existsSync as existsSync16, readFileSync as readFileSync10 } from "fs";
4810
+ import { existsSync as existsSync17, readFileSync as readFileSync11 } from "fs";
4733
4811
  import { resolve as resolve7 } from "path";
4734
4812
  import chalk8 from "chalk";
4735
4813
  import { Command as Command9 } from "commander";
@@ -5086,7 +5164,7 @@ var deployCommand = new Command9("deploy").description("Deploy infrastructure an
5086
5164
  );
5087
5165
  async function resolveConfig2(options) {
5088
5166
  if (options.config) {
5089
- const raw = JSON.parse(readFileSync10(resolve7(options.config), "utf8"));
5167
+ const raw = JSON.parse(readFileSync11(resolve7(options.config), "utf8"));
5090
5168
  const result = BiffoConfigSchema.safeParse(raw);
5091
5169
  if (!result.success) {
5092
5170
  log.error(`Invalid config at ${options.config}:`);
@@ -5106,8 +5184,8 @@ async function resolveConfig2(options) {
5106
5184
  return cfg;
5107
5185
  }
5108
5186
  const localConfigPath = resolve7(process.cwd(), "biffo.config.json");
5109
- if (existsSync16(localConfigPath)) {
5110
- const raw = JSON.parse(readFileSync10(localConfigPath, "utf8"));
5187
+ if (existsSync17(localConfigPath)) {
5188
+ const raw = JSON.parse(readFileSync11(localConfigPath, "utf8"));
5111
5189
  const result = BiffoConfigSchema.safeParse(raw);
5112
5190
  if (result.success) return result.data;
5113
5191
  if (isTemplatePlaceholderConfig(raw)) {
@@ -5503,7 +5581,7 @@ function resolveGithubToken() {
5503
5581
 
5504
5582
  // src/commands/destroy.ts
5505
5583
  import { execSync as execSync5 } from "child_process";
5506
- import { readFileSync as readFileSync11 } from "fs";
5584
+ import { readFileSync as readFileSync12 } from "fs";
5507
5585
  import { resolve as resolve8 } from "path";
5508
5586
  import chalk9 from "chalk";
5509
5587
  import { Command as Command10 } from "commander";
@@ -5593,7 +5671,7 @@ var destroyCommand = new Command10("destroy").description("Destroy infrastructur
5593
5671
  });
5594
5672
  async function resolveConfig3(options) {
5595
5673
  if (options.config) {
5596
- const raw = JSON.parse(readFileSync11(resolve8(options.config), "utf8"));
5674
+ const raw = JSON.parse(readFileSync12(resolve8(options.config), "utf8"));
5597
5675
  const result = BiffoConfigSchema.safeParse(raw);
5598
5676
  if (!result.success) {
5599
5677
  log.error(`Invalid config at ${options.config}:`);
@@ -5613,7 +5691,7 @@ async function resolveConfig3(options) {
5613
5691
  return cfg;
5614
5692
  }
5615
5693
  try {
5616
- const raw = JSON.parse(readFileSync11(resolve8(process.cwd(), "biffo.config.json"), "utf8"));
5694
+ const raw = JSON.parse(readFileSync12(resolve8(process.cwd(), "biffo.config.json"), "utf8"));
5617
5695
  const result = BiffoConfigSchema.safeParse(raw);
5618
5696
  if (result.success) return result.data;
5619
5697
  } catch {
@@ -5663,15 +5741,15 @@ function resolveGithubToken2() {
5663
5741
  }
5664
5742
 
5665
5743
  // src/commands/init.ts
5666
- import { readFileSync as readFileSync15 } from "fs";
5744
+ import { readFileSync as readFileSync16 } from "fs";
5667
5745
  import { resolve as resolve10 } from "path";
5668
5746
  import chalk12 from "chalk";
5669
5747
  import { Command as Command12 } from "commander";
5670
5748
  import inquirer5 from "inquirer";
5671
5749
 
5672
5750
  // src/lib/build-freshness.ts
5673
- import { existsSync as existsSync17, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
5674
- import { dirname as dirname5, join as join16, relative as relative2, sep as sep2 } from "path";
5751
+ import { existsSync as existsSync18, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
5752
+ import { dirname as dirname5, join as join17, relative as relative2, sep as sep2 } from "path";
5675
5753
  import { fileURLToPath as fileURLToPath3 } from "url";
5676
5754
  var SKIP_ENV_VAR = "BIFFO_SKIP_BUILD_FRESHNESS_CHECK";
5677
5755
  function checkBuildFreshness(options = {}) {
@@ -5685,7 +5763,7 @@ function checkBuildFreshness(options = {}) {
5685
5763
  if (!packageRoot) {
5686
5764
  return { status: "skipped", reason: `no package.json above ${moduleDir}`, newerSources: [] };
5687
5765
  }
5688
- const distDir = join16(packageRoot, "dist");
5766
+ const distDir = join17(packageRoot, "dist");
5689
5767
  if (!isInside(distDir, moduleDir)) {
5690
5768
  return {
5691
5769
  status: "skipped",
@@ -5693,16 +5771,16 @@ function checkBuildFreshness(options = {}) {
5693
5771
  newerSources: []
5694
5772
  };
5695
5773
  }
5696
- const srcDir = join16(packageRoot, "src");
5697
- if (!existsSync17(srcDir)) {
5774
+ const srcDir = join17(packageRoot, "src");
5775
+ if (!existsSync18(srcDir)) {
5698
5776
  return {
5699
5777
  status: "skipped",
5700
5778
  reason: "no src/ alongside dist/ \u2014 this is a shipped package",
5701
5779
  newerSources: []
5702
5780
  };
5703
5781
  }
5704
- const entry = join16(distDir, "index.js");
5705
- if (!existsSync17(entry)) {
5782
+ const entry = join17(distDir, "index.js");
5783
+ if (!existsSync18(entry)) {
5706
5784
  return { status: "skipped", reason: `${entry} not found`, newerSources: [] };
5707
5785
  }
5708
5786
  const builtAt = statSync4(entry).mtimeMs;
@@ -5746,7 +5824,7 @@ function collectSourceFiles(srcDir) {
5746
5824
  const found = [];
5747
5825
  const walk = (dir) => {
5748
5826
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
5749
- const full = join16(dir, entry.name);
5827
+ const full = join17(dir, entry.name);
5750
5828
  if (entry.isDirectory()) {
5751
5829
  if (entry.name === "node_modules") continue;
5752
5830
  walk(full);
@@ -5765,7 +5843,7 @@ function collectSourceFiles(srcDir) {
5765
5843
  function findPackageRoot(from) {
5766
5844
  let dir = from;
5767
5845
  for (; ; ) {
5768
- if (existsSync17(join16(dir, "package.json"))) return dir;
5846
+ if (existsSync18(join17(dir, "package.json"))) return dir;
5769
5847
  const parent = dirname5(dir);
5770
5848
  if (parent === dir) return null;
5771
5849
  dir = parent;
@@ -5779,9 +5857,9 @@ function isInside(parent, child) {
5779
5857
 
5780
5858
  // src/lib/credentials.ts
5781
5859
  import { execSync as execSync6 } from "child_process";
5782
- import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
5860
+ import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
5783
5861
  import { homedir as homedir2 } from "os";
5784
- import { join as join17 } from "path";
5862
+ import { join as join18 } from "path";
5785
5863
  import { GetCallerIdentityCommand as GetCallerIdentityCommand2, STSClient as STSClient2 } from "@aws-sdk/client-sts";
5786
5864
  import chalk10 from "chalk";
5787
5865
  import inquirer4 from "inquirer";
@@ -5960,11 +6038,11 @@ async function verifySelectedAwsCredentials(profile, region) {
5960
6038
  return sts.send(new GetCallerIdentityCommand2({}));
5961
6039
  }
5962
6040
  function discoverAwsProfiles() {
5963
- const files = [join17(homedir2(), ".aws", "credentials"), join17(homedir2(), ".aws", "config")];
6041
+ const files = [join18(homedir2(), ".aws", "credentials"), join18(homedir2(), ".aws", "config")];
5964
6042
  const profiles = /* @__PURE__ */ new Set();
5965
6043
  for (const file of files) {
5966
- if (!existsSync18(file)) continue;
5967
- const content = readFileSync12(file, "utf8");
6044
+ if (!existsSync19(file)) continue;
6045
+ const content = readFileSync13(file, "utf8");
5968
6046
  for (const match of content.matchAll(/^\s*\[([^\]]+)\]\s*$/gm)) {
5969
6047
  const section = match[1]?.trim();
5970
6048
  if (!section) continue;
@@ -6054,34 +6132,34 @@ var SiblingConfigSchema = z6.object({
6054
6132
 
6055
6133
  // src/lib/sibling-session.ts
6056
6134
  import {
6057
- existsSync as existsSync19,
6135
+ existsSync as existsSync20,
6058
6136
  mkdirSync as mkdirSync6,
6059
6137
  readdirSync as readdirSync8,
6060
- readFileSync as readFileSync13,
6138
+ readFileSync as readFileSync14,
6061
6139
  rmSync as rmSync7,
6062
6140
  statSync as statSync5,
6063
6141
  writeFileSync as writeFileSync6
6064
6142
  } from "fs";
6065
6143
  import { homedir as homedir3 } from "os";
6066
- import { join as join18 } from "path";
6144
+ import { join as join19 } from "path";
6067
6145
  function sessionsDir2() {
6068
- return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join18(homedir3(), ".biffo", "sibling-sessions");
6146
+ return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join19(homedir3(), ".biffo", "sibling-sessions");
6069
6147
  }
6070
6148
  function sessionPath2(projectName) {
6071
- return join18(sessionsDir2(), `${projectName}.json`);
6149
+ return join19(sessionsDir2(), `${projectName}.json`);
6072
6150
  }
6073
6151
  function loadSiblingSession(projectName) {
6074
6152
  const path = sessionPath2(projectName);
6075
- if (!existsSync19(path)) return null;
6153
+ if (!existsSync20(path)) return null;
6076
6154
  try {
6077
- return JSON.parse(readFileSync13(path, "utf8"));
6155
+ return JSON.parse(readFileSync14(path, "utf8"));
6078
6156
  } catch {
6079
6157
  return null;
6080
6158
  }
6081
6159
  }
6082
6160
  function saveSiblingSession(session) {
6083
6161
  const dir = sessionsDir2();
6084
- if (!existsSync19(dir)) mkdirSync6(dir, { recursive: true });
6162
+ if (!existsSync20(dir)) mkdirSync6(dir, { recursive: true });
6085
6163
  const name = session.config.project?.name ?? "unknown";
6086
6164
  const prior = loadSiblingSession(name);
6087
6165
  if (prior) {
@@ -6103,30 +6181,30 @@ function markSiblingStepComplete(session, step) {
6103
6181
  }
6104
6182
  function deleteSiblingSession(projectName) {
6105
6183
  const path = sessionPath2(projectName);
6106
- if (existsSync19(path)) rmSync7(path);
6184
+ if (existsSync20(path)) rmSync7(path);
6107
6185
  }
6108
6186
 
6109
6187
  // src/commands/sibling-create.ts
6110
- import { cpSync as cpSync2, existsSync as existsSync20, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
6188
+ import { cpSync as cpSync2, existsSync as existsSync21, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
6111
6189
  import { tmpdir as tmpdir4 } from "os";
6112
- import { dirname as dirname6, join as join20, resolve as resolve9 } from "path";
6190
+ import { dirname as dirname6, join as join21, resolve as resolve9 } from "path";
6113
6191
  import { fileURLToPath as fileURLToPath4 } from "url";
6114
6192
  import chalk11 from "chalk";
6115
6193
  import { Command as Command11 } from "commander";
6116
6194
 
6117
6195
  // src/lib/skeleton-dotfiles.ts
6118
6196
  import { readdirSync as readdirSync9, renameSync } from "fs";
6119
- import { join as join19 } from "path";
6197
+ import { join as join20 } from "path";
6120
6198
  var PACKAGED_GITIGNORE = "_gitignore";
6121
6199
  var REAL_GITIGNORE = ".gitignore";
6122
6200
  function restorePackagedDotfiles(dir) {
6123
6201
  const restored = [];
6124
6202
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
6125
- const full = join19(dir, entry.name);
6203
+ const full = join20(dir, entry.name);
6126
6204
  if (entry.isDirectory()) {
6127
6205
  restored.push(...restorePackagedDotfiles(full));
6128
6206
  } else if (entry.name === PACKAGED_GITIGNORE) {
6129
- const target = join19(dir, REAL_GITIGNORE);
6207
+ const target = join20(dir, REAL_GITIGNORE);
6130
6208
  renameSync(full, target);
6131
6209
  restored.push(target);
6132
6210
  }
@@ -6171,7 +6249,7 @@ async function runSiblingCreateCommand(name, options) {
6171
6249
  printDryRun2(config, coreConfig, options.templateRoot);
6172
6250
  return;
6173
6251
  }
6174
- if (!existsSync20(options.templateRoot)) {
6252
+ if (!existsSync21(options.templateRoot)) {
6175
6253
  throw new Error(`Sibling template not found at ${options.templateRoot}`);
6176
6254
  }
6177
6255
  let session = null;
@@ -6388,7 +6466,7 @@ function assertPathPrefixIsAllowed(pathPrefix) {
6388
6466
  }
6389
6467
  }
6390
6468
  function readSiblingConfig(path, root = false) {
6391
- const raw = JSON.parse(readFileSync14(path, "utf8"));
6469
+ const raw = JSON.parse(readFileSync15(path, "utf8"));
6392
6470
  const withDefaults = raw && typeof raw === "object" && "project" in raw && "core" in raw ? {
6393
6471
  ...raw,
6394
6472
  core: {
@@ -6422,7 +6500,7 @@ function resolveCoreConfig(config, configPath) {
6422
6500
  throw new Error("Either core.project_name or core.config_path is required.");
6423
6501
  }
6424
6502
  function parseCoreConfig(path) {
6425
- const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync14(path, "utf8")));
6503
+ const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync15(path, "utf8")));
6426
6504
  if (!result.success) {
6427
6505
  throw new Error(
6428
6506
  `Invalid core configuration at ${path}:
@@ -6461,7 +6539,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
6461
6539
  return coreIdentity;
6462
6540
  }
6463
6541
  async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
6464
- const workDir = mkdtempSync4(join20(tmpdir4(), `biffo-sibling-${config.project.name}-`));
6542
+ const workDir = mkdtempSync4(join21(tmpdir4(), `biffo-sibling-${config.project.name}-`));
6465
6543
  try {
6466
6544
  writeSiblingTemplate(skeletonRoot, workDir, config, {
6467
6545
  coreProjectName: coreConfig.project.name,
@@ -6486,13 +6564,13 @@ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, git
6486
6564
  }
6487
6565
  }
6488
6566
  function writeSiblingTemplate(templateRoot, targetDir, config, context) {
6489
- if (!existsSync20(templateRoot)) {
6567
+ if (!existsSync21(templateRoot)) {
6490
6568
  throw new Error(`Sibling template not found at ${templateRoot}`);
6491
6569
  }
6492
6570
  cpSync2(templateRoot, targetDir, { recursive: true });
6493
6571
  restorePackagedDotfiles(targetDir);
6494
6572
  writeFileSync7(
6495
- join20(targetDir, "biffo.sibling.json"),
6573
+ join21(targetDir, "biffo.sibling.json"),
6496
6574
  JSON.stringify(
6497
6575
  {
6498
6576
  name: config.project.name,
@@ -6510,10 +6588,10 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
6510
6588
  2
6511
6589
  ) + "\n"
6512
6590
  );
6513
- const envPath = join20(targetDir, "apps", "frontend", ".env.example");
6591
+ const envPath = join21(targetDir, "apps", "frontend", ".env.example");
6514
6592
  try {
6515
6593
  const path = basePathFor(context.pathPrefix);
6516
- const content = readFileSync14(envPath, "utf8").replace(/^NEXT_PUBLIC_SIBLING_NAME=.*$/m, `NEXT_PUBLIC_SIBLING_NAME=${config.project.name}`).replace(/^NEXT_PUBLIC_SIBLING_PATH_PREFIX=.*$/m, `NEXT_PUBLIC_SIBLING_PATH_PREFIX=${path}`).replace(/^NEXT_PUBLIC_BASE_PATH=.*$/m, `NEXT_PUBLIC_BASE_PATH=${path}`);
6594
+ const content = readFileSync15(envPath, "utf8").replace(/^NEXT_PUBLIC_SIBLING_NAME=.*$/m, `NEXT_PUBLIC_SIBLING_NAME=${config.project.name}`).replace(/^NEXT_PUBLIC_SIBLING_PATH_PREFIX=.*$/m, `NEXT_PUBLIC_SIBLING_PATH_PREFIX=${path}`).replace(/^NEXT_PUBLIC_BASE_PATH=.*$/m, `NEXT_PUBLIC_BASE_PATH=${path}`);
6517
6595
  writeFileSync7(envPath, content);
6518
6596
  } catch (err) {
6519
6597
  if (err.code !== "ENOENT") throw err;
@@ -6560,7 +6638,7 @@ async function configureSiblingGithub(github, config, coreConfig, session, coreI
6560
6638
  }
6561
6639
  function readExistingSiblingOrigins(filePath) {
6562
6640
  try {
6563
- return JSON.parse(readFileSync14(filePath, "utf8"));
6641
+ return JSON.parse(readFileSync15(filePath, "utf8"));
6564
6642
  } catch (err) {
6565
6643
  if (err.code === "ENOENT") return {};
6566
6644
  throw err;
@@ -6579,10 +6657,10 @@ function assertGitIdentity(identity) {
6579
6657
  );
6580
6658
  }
6581
6659
  function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
6582
- const cdnVarsPath = join20(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
6660
+ const cdnVarsPath = join21(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
6583
6661
  let declaresSiblingOrigins = false;
6584
6662
  try {
6585
- declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync14(cdnVarsPath, "utf8"));
6663
+ declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync15(cdnVarsPath, "utf8"));
6586
6664
  } catch {
6587
6665
  declaresSiblingOrigins = false;
6588
6666
  }
@@ -6592,10 +6670,10 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
6592
6670
  );
6593
6671
  }
6594
6672
  if (!isRootPathPrefix(pathPrefix)) return;
6595
- const cdnMainPath = join20(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
6673
+ const cdnMainPath = join21(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
6596
6674
  let supportsRoot = false;
6597
6675
  try {
6598
- supportsRoot = /root_sibling_registered/.test(readFileSync14(cdnMainPath, "utf8"));
6676
+ supportsRoot = /root_sibling_registered/.test(readFileSync15(cdnMainPath, "utf8"));
6599
6677
  } catch {
6600
6678
  supportsRoot = false;
6601
6679
  }
@@ -6625,8 +6703,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
6625
6703
  for (const env of config.environments) {
6626
6704
  const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
6627
6705
  const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
6628
- const relativePath = join20("infra", "environments", env, "siblings.auto.tfvars.json");
6629
- const filePath = join20(cloneDir, relativePath);
6706
+ const relativePath = join21("infra", "environments", env, "siblings.auto.tfvars.json");
6707
+ const filePath = join21(cloneDir, relativePath);
6630
6708
  const existing = readExistingSiblingOrigins(filePath);
6631
6709
  const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
6632
6710
  name,
@@ -6706,8 +6784,8 @@ function defaultSiblingTemplateRoot() {
6706
6784
  const start = dirname6(fileURLToPath4(import.meta.url));
6707
6785
  let dir = start;
6708
6786
  for (; ; ) {
6709
- const candidate = join20(dir, "_skeletons", "sibling-template");
6710
- if (existsSync20(candidate)) return candidate;
6787
+ const candidate = join21(dir, "_skeletons", "sibling-template");
6788
+ if (existsSync21(candidate)) return candidate;
6711
6789
  const parent = dirname6(dir);
6712
6790
  if (parent === dir) break;
6713
6791
  dir = parent;
@@ -6731,7 +6809,7 @@ var initCommand = new Command12("init").description("Scaffold a new project from
6731
6809
  let config;
6732
6810
  let githubToken;
6733
6811
  if (options.config) {
6734
- const rawConfig = JSON.parse(readFileSync15(resolve10(options.config), "utf8"));
6812
+ const rawConfig = JSON.parse(readFileSync16(resolve10(options.config), "utf8"));
6735
6813
  config = parseConfig(rawConfig);
6736
6814
  const { account_id: accountId, region } = config.cloud.config;
6737
6815
  session = resolveConfigFileSession(config, accountId, region, options.fresh === true);
@@ -7176,8 +7254,8 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
7176
7254
  import { Command as Command20 } from "commander";
7177
7255
 
7178
7256
  // src/commands/plugin-create.ts
7179
- import { existsSync as existsSync23, readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "fs";
7180
- import { dirname as dirname8, join as join23, resolve as resolve11 } from "path";
7257
+ import { existsSync as existsSync24, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
7258
+ import { dirname as dirname8, join as join24, resolve as resolve11 } from "path";
7181
7259
  import { fileURLToPath as fileURLToPath5 } from "url";
7182
7260
  import chalk13 from "chalk";
7183
7261
  import { Command as Command13 } from "commander";
@@ -7241,21 +7319,21 @@ function workflowCheckContexts(workflow) {
7241
7319
  }
7242
7320
 
7243
7321
  // src/lib/plugin-locations.ts
7244
- import { existsSync as existsSync21, readdirSync as readdirSync10 } from "fs";
7245
- import { join as join21 } from "path";
7322
+ import { existsSync as existsSync22, readdirSync as readdirSync10 } from "fs";
7323
+ import { join as join22 } from "path";
7246
7324
  var FIRST_PARTY_PLUGINS_DIR = "_plugins";
7247
7325
  var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
7248
7326
  function pluginDir(name, channel) {
7249
7327
  return channel === "first-party" ? `services/${FIRST_PARTY_PLUGINS_DIR}/${name}` : `services/${name}`;
7250
7328
  }
7251
7329
  function scanDir(absDir, relDir, channel) {
7252
- if (!existsSync21(absDir)) return [];
7330
+ if (!existsSync22(absDir)) return [];
7253
7331
  const found = [];
7254
7332
  for (const entry of readdirSync10(absDir, { withFileTypes: true })) {
7255
7333
  if (!entry.isDirectory()) continue;
7256
7334
  if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
7257
- const manifestPath = join21(absDir, entry.name, PLUGIN_MANIFEST_FILE);
7258
- if (!existsSync21(manifestPath)) continue;
7335
+ const manifestPath = join22(absDir, entry.name, PLUGIN_MANIFEST_FILE);
7336
+ if (!existsSync22(manifestPath)) continue;
7259
7337
  found.push({
7260
7338
  dirName: entry.name,
7261
7339
  relDir: `${relDir}/${entry.name}`,
@@ -7266,11 +7344,11 @@ function scanDir(absDir, relDir, channel) {
7266
7344
  return found;
7267
7345
  }
7268
7346
  function findInstalledPlugins(cwd) {
7269
- const servicesDir = join21(cwd, "services");
7347
+ const servicesDir = join22(cwd, "services");
7270
7348
  return [
7271
7349
  ...scanDir(servicesDir, "services", "third-party"),
7272
7350
  ...scanDir(
7273
- join21(servicesDir, FIRST_PARTY_PLUGINS_DIR),
7351
+ join22(servicesDir, FIRST_PARTY_PLUGINS_DIR),
7274
7352
  `services/${FIRST_PARTY_PLUGINS_DIR}`,
7275
7353
  "first-party"
7276
7354
  )
@@ -7473,13 +7551,13 @@ function validateManifest(raw) {
7473
7551
  // src/lib/plugin-scaffold.ts
7474
7552
  import {
7475
7553
  copyFileSync,
7476
- existsSync as existsSync22,
7554
+ existsSync as existsSync23,
7477
7555
  mkdirSync as mkdirSync8,
7478
- readFileSync as readFileSync16,
7556
+ readFileSync as readFileSync17,
7479
7557
  readdirSync as readdirSync11,
7480
7558
  writeFileSync as writeFileSync8
7481
7559
  } from "fs";
7482
- import { dirname as dirname7, join as join22 } from "path";
7560
+ import { dirname as dirname7, join as join23 } from "path";
7483
7561
  var STANDALONE_ONLY_ENTRIES = {
7484
7562
  ".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
7485
7563
  "registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
@@ -7533,10 +7611,10 @@ function applySubstitutions(text, names) {
7533
7611
  var BINARY_EXTENSIONS = /\.(png|jpe?g|gif|ico|woff2?|ttf|zip|gz)$/i;
7534
7612
  function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7535
7613
  const layout = options.layout ?? "in-tree";
7536
- if (!existsSync22(skeletonRoot)) {
7614
+ if (!existsSync23(skeletonRoot)) {
7537
7615
  throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
7538
7616
  }
7539
- if (!existsSync22(join22(skeletonRoot, "terraform"))) {
7617
+ if (!existsSync23(join23(skeletonRoot, "terraform"))) {
7540
7618
  throw new Error(
7541
7619
  `Plugin skeleton at ${skeletonRoot} has no terraform/ directory. Refusing to scaffold a plugin that cannot receive events (issue #194) \u2014 the skeleton is broken.`
7542
7620
  );
@@ -7544,7 +7622,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7544
7622
  const skipped = [];
7545
7623
  const files = [];
7546
7624
  const walk = (relDir) => {
7547
- const absDir = join22(skeletonRoot, relDir);
7625
+ const absDir = join23(skeletonRoot, relDir);
7548
7626
  for (const entry of readdirSync11(absDir, { withFileTypes: true }).sort(
7549
7627
  (a, b) => a.name.localeCompare(b.name)
7550
7628
  )) {
@@ -7559,14 +7637,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7559
7637
  continue;
7560
7638
  }
7561
7639
  const destRel = applySubstitutions(relPath, names);
7562
- const destPath = join22(destDir, destRel);
7640
+ const destPath = join23(destDir, destRel);
7563
7641
  mkdirSync8(dirname7(destPath), { recursive: true });
7564
7642
  if (BINARY_EXTENSIONS.test(entry.name)) {
7565
- copyFileSync(join22(skeletonRoot, relPath), destPath);
7643
+ copyFileSync(join23(skeletonRoot, relPath), destPath);
7566
7644
  } else {
7567
7645
  writeFileSync8(
7568
7646
  destPath,
7569
- applySubstitutions(readFileSync16(join22(skeletonRoot, relPath), "utf8"), names)
7647
+ applySubstitutions(readFileSync17(join23(skeletonRoot, relPath), "utf8"), names)
7570
7648
  );
7571
7649
  }
7572
7650
  files.push(destRel);
@@ -7583,8 +7661,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7583
7661
  function findSkeletonRoot(startDir, skeleton) {
7584
7662
  let dir = startDir;
7585
7663
  for (; ; ) {
7586
- const candidate = join22(dir, "_skeletons", skeleton);
7587
- if (existsSync22(candidate)) return candidate;
7664
+ const candidate = join23(dir, "_skeletons", skeleton);
7665
+ if (existsSync23(candidate)) return candidate;
7588
7666
  const parent = dirname7(dir);
7589
7667
  if (parent === dir) return null;
7590
7668
  dir = parent;
@@ -7652,7 +7730,7 @@ async function runPluginCreate(name, options, deps) {
7652
7730
  reportBranchProtectionSummary();
7653
7731
  return;
7654
7732
  }
7655
- const isInstance = existsSync23(join23(options.cwd, INSTANCE_CORE_FILE));
7733
+ const isInstance = existsSync24(join24(options.cwd, INSTANCE_CORE_FILE));
7656
7734
  if (options.firstParty && isInstance) {
7657
7735
  throw new Error(
7658
7736
  `--first-party scaffolds into services/_plugins/, which is template-owned: \`biffo core upgrade\` three-way-merges it against the template on every upgrade, and the template has no '${names.slug}'. This checkout is a Biffo instance (${INSTANCE_CORE_FILE} is present), so your plugin belongs in the user-owned ${pluginDir(names.slug, "third-party")}/ \u2014 re-run without --first-party.`
@@ -7660,19 +7738,19 @@ async function runPluginCreate(name, options, deps) {
7660
7738
  }
7661
7739
  const channel = options.firstParty ? "first-party" : "third-party";
7662
7740
  const relDir = pluginDir(names.slug, channel);
7663
- const destDir = join23(options.cwd, relDir);
7664
- const servicesDir = join23(options.cwd, "services");
7665
- if (!existsSync23(servicesDir)) {
7741
+ const destDir = join24(options.cwd, relDir);
7742
+ const servicesDir = join24(options.cwd, "services");
7743
+ if (!existsSync24(servicesDir)) {
7666
7744
  throw new Error(
7667
7745
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
7668
7746
  );
7669
7747
  }
7670
- if (existsSync23(destDir)) {
7748
+ if (existsSync24(destDir)) {
7671
7749
  throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
7672
7750
  }
7673
7751
  const here = dirname8(fileURLToPath5(import.meta.url));
7674
- const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join23(options.cwd, "_skeletons", "plugin-template");
7675
- if (!existsSync23(skeletonRoot)) {
7752
+ const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join24(options.cwd, "_skeletons", "plugin-template");
7753
+ if (!existsSync24(skeletonRoot)) {
7676
7754
  throw new Error(
7677
7755
  `Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
7678
7756
  );
@@ -7687,8 +7765,8 @@ async function runPluginCreate(name, options, deps) {
7687
7765
  for (const { entry, reason } of skipped) {
7688
7766
  log.info(`Skipped ${entry} \u2014 ${reason}`);
7689
7767
  }
7690
- const manifestPath = join23(destDir, "biffo.plugin.json");
7691
- const manifest = validateManifest(JSON.parse(readFileSync17(manifestPath, "utf8")));
7768
+ const manifestPath = join24(destDir, "biffo.plugin.json");
7769
+ const manifest = validateManifest(JSON.parse(readFileSync18(manifestPath, "utf8")));
7692
7770
  if (manifest.name !== names.slug) {
7693
7771
  throw new Error(
7694
7772
  `Scaffolded manifest declares name '${manifest.name}', expected '${names.slug}'. The skeleton's manifest name may have diverged from 'example-plugin'.`
@@ -7711,8 +7789,8 @@ async function runPluginCreate(name, options, deps) {
7711
7789
  printNextSteps(names, relDir, channel);
7712
7790
  }
7713
7791
  async function runStandaloneCreate(names, options, deps) {
7714
- const destDir = join23(options.cwd, names.dist);
7715
- if (existsSync23(destDir)) {
7792
+ const destDir = join24(options.cwd, names.dist);
7793
+ if (existsSync24(destDir)) {
7716
7794
  throw new Error(`${names.dist}/ already exists. Choose a different name, or remove it first.`);
7717
7795
  }
7718
7796
  const skeletonRoot = resolveSkeletonRoot(options);
@@ -7730,7 +7808,7 @@ async function runStandaloneCreate(names, options, deps) {
7730
7808
  restorePackagedDotfiles(destDir);
7731
7809
  log.success(`Scaffolded ${String(files.length)} file(s) into ${names.dist}/`);
7732
7810
  const manifest = validateManifest(
7733
- JSON.parse(readFileSync17(join23(destDir, "biffo.plugin.json"), "utf8"))
7811
+ JSON.parse(readFileSync18(join24(destDir, "biffo.plugin.json"), "utf8"))
7734
7812
  );
7735
7813
  if (manifest.name !== names.slug) {
7736
7814
  throw new Error(
@@ -7769,8 +7847,8 @@ async function createAndPushStandaloneRepo(org, names, destDir, options, deps) {
7769
7847
  await deps.git.push(destDir, "dev", { token });
7770
7848
  log.success(`Pushed dev to ${org}/${names.dist}`);
7771
7849
  await github.setDefaultBranch(org, names.dist, "dev");
7772
- const ciPath = join23(destDir, ".github", "workflows", "ci.yml");
7773
- const contexts = existsSync23(ciPath) ? workflowCheckContexts(readFileSync17(ciPath, "utf8")) : [];
7850
+ const ciPath = join24(destDir, ".github", "workflows", "ci.yml");
7851
+ const contexts = existsSync24(ciPath) ? workflowCheckContexts(readFileSync18(ciPath, "utf8")) : [];
7774
7852
  if (contexts.length === 0) {
7775
7853
  log.warn(
7776
7854
  `Could not determine required status checks from ${ciPath} \u2014 skipping branch protection. Configure it manually on dev once you know the CI job names.`
@@ -7796,8 +7874,8 @@ async function registerInRegistrySources(names, cloneUrl, token, deps) {
7796
7874
  let dir;
7797
7875
  try {
7798
7876
  dir = await deps.git.cloneForEditing(REGISTRY_REPO, "biffo-registry", token);
7799
- const path = join23(dir, "sources.json");
7800
- const file = JSON.parse(readFileSync17(path, "utf8"));
7877
+ const path = join24(dir, "sources.json");
7878
+ const file = JSON.parse(readFileSync18(path, "utf8"));
7801
7879
  const next = addSource(file, {
7802
7880
  name: names.slug,
7803
7881
  repo: cloneUrl.replace(/\.git$/, ""),
@@ -7905,8 +7983,8 @@ function printStandaloneNextSteps(names, minor) {
7905
7983
  }
7906
7984
  function resolveSkeletonRoot(options) {
7907
7985
  const here = dirname8(fileURLToPath5(import.meta.url));
7908
- const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join23(options.cwd, "_skeletons", "plugin-template");
7909
- if (!existsSync23(skeletonRoot)) {
7986
+ const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join24(options.cwd, "_skeletons", "plugin-template");
7987
+ if (!existsSync24(skeletonRoot)) {
7910
7988
  throw new Error(
7911
7989
  `Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
7912
7990
  );
@@ -8081,14 +8159,14 @@ function printEntry(entry) {
8081
8159
  }
8082
8160
 
8083
8161
  // src/commands/plugin-install.ts
8084
- import { cpSync as cpSync3, existsSync as existsSync25, mkdirSync as mkdirSync9, readFileSync as readFileSync19, statSync as statSync6 } from "fs";
8085
- import { basename, join as join26, relative as relative3, resolve as resolve12 } from "path";
8162
+ import { cpSync as cpSync3, existsSync as existsSync26, mkdirSync as mkdirSync9, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
8163
+ import { basename, join as join27, relative as relative3, resolve as resolve12 } from "path";
8086
8164
  import chalk15 from "chalk";
8087
8165
  import { Command as Command15 } from "commander";
8088
8166
 
8089
8167
  // src/adapters/plugin-migrations/index.ts
8090
8168
  import { execa as execa4 } from "execa";
8091
- import { join as join24 } from "path";
8169
+ import { join as join25 } from "path";
8092
8170
  var PluginMigrationsAdapter = class {
8093
8171
  /**
8094
8172
  * Generates migration file(s) for `pluginNames` (every discovered
@@ -8097,22 +8175,22 @@ var PluginMigrationsAdapter = class {
8097
8175
  * or declared no tables.
8098
8176
  */
8099
8177
  async generate(cwd, pluginNames) {
8100
- const scriptPath = join24(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
8178
+ const scriptPath = join25(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
8101
8179
  const args = [
8102
8180
  "run",
8103
8181
  "python",
8104
8182
  scriptPath,
8105
8183
  "--services-root",
8106
- join24(cwd, "services"),
8184
+ join25(cwd, "services"),
8107
8185
  "--versions-dir",
8108
- join24(cwd, "services", "api", "migrations", "versions")
8186
+ join25(cwd, "services", "api", "migrations", "versions")
8109
8187
  ];
8110
8188
  for (const name of pluginNames ?? []) {
8111
8189
  args.push("--plugin", name);
8112
8190
  }
8113
8191
  let result;
8114
8192
  try {
8115
- result = await execa4("uv", args, { cwd: join24(cwd, "services", "api") });
8193
+ result = await execa4("uv", args, { cwd: join25(cwd, "services", "api") });
8116
8194
  } catch (err) {
8117
8195
  const cause = err;
8118
8196
  if (cause.code === "ENOENT") {
@@ -8129,8 +8207,8 @@ var PluginMigrationsAdapter = class {
8129
8207
  };
8130
8208
 
8131
8209
  // src/lib/plugin-workspace-sources.ts
8132
- import { existsSync as existsSync24, readdirSync as readdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
8133
- import { join as join25 } from "path";
8210
+ import { existsSync as existsSync25, readdirSync as readdirSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
8211
+ import { join as join26 } from "path";
8134
8212
  function readTomlStringArray(text, key) {
8135
8213
  const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
8136
8214
  if (!open) return [];
@@ -8174,9 +8252,9 @@ function readDependencyNames(text) {
8174
8252
  return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
8175
8253
  }
8176
8254
  function workspaceMemberNames(instanceRoot) {
8177
- const rootPyproject = join25(instanceRoot, "pyproject.toml");
8178
- if (!existsSync24(rootPyproject)) return /* @__PURE__ */ new Set();
8179
- const text = readFileSync18(rootPyproject, "utf8");
8255
+ const rootPyproject = join26(instanceRoot, "pyproject.toml");
8256
+ if (!existsSync25(rootPyproject)) return /* @__PURE__ */ new Set();
8257
+ const text = readFileSync19(rootPyproject, "utf8");
8180
8258
  const members = readTomlStringArray(text, "members");
8181
8259
  const excluded = new Set(readTomlStringArray(text, "exclude"));
8182
8260
  const dirs = [];
@@ -8185,7 +8263,7 @@ function workspaceMemberNames(instanceRoot) {
8185
8263
  const base = member.slice(0, -2);
8186
8264
  let entries;
8187
8265
  try {
8188
- entries = readdirSync12(join25(instanceRoot, base), { withFileTypes: true });
8266
+ entries = readdirSync12(join26(instanceRoot, base), { withFileTypes: true });
8189
8267
  } catch {
8190
8268
  continue;
8191
8269
  }
@@ -8199,9 +8277,9 @@ function workspaceMemberNames(instanceRoot) {
8199
8277
  }
8200
8278
  const names = /* @__PURE__ */ new Set();
8201
8279
  for (const dir of dirs) {
8202
- const pp = join25(instanceRoot, dir, "pyproject.toml");
8203
- if (!existsSync24(pp)) continue;
8204
- const name = readProjectName(readFileSync18(pp, "utf8"));
8280
+ const pp = join26(instanceRoot, dir, "pyproject.toml");
8281
+ if (!existsSync25(pp)) continue;
8282
+ const name = readProjectName(readFileSync19(pp, "utf8"));
8205
8283
  if (name) names.add(name);
8206
8284
  }
8207
8285
  return names;
@@ -8212,8 +8290,8 @@ function existingWorkspaceSources(text) {
8212
8290
  );
8213
8291
  }
8214
8292
  function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
8215
- if (!existsSync24(pluginPyprojectPath) || memberNames.size === 0) return [];
8216
- const text = readFileSync18(pluginPyprojectPath, "utf8");
8293
+ if (!existsSync25(pluginPyprojectPath) || memberNames.size === 0) return [];
8294
+ const text = readFileSync19(pluginPyprojectPath, "utf8");
8217
8295
  const already = existingWorkspaceSources(text);
8218
8296
  const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
8219
8297
  if (toAdd.length === 0) return [];
@@ -8279,14 +8357,14 @@ var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
8279
8357
  ".terraform"
8280
8358
  ]);
8281
8359
  function resolveLocalPlugin(localPath) {
8282
- if (!existsSync25(localPath)) {
8360
+ if (!existsSync26(localPath)) {
8283
8361
  throw new Error(`--local path does not exist: ${localPath}`);
8284
8362
  }
8285
8363
  if (!statSync6(localPath).isDirectory()) {
8286
8364
  throw new Error(`--local path is not a directory: ${localPath}`);
8287
8365
  }
8288
- const manifestPath = join26(localPath, "biffo.plugin.json");
8289
- if (!existsSync25(manifestPath)) {
8366
+ const manifestPath = join27(localPath, "biffo.plugin.json");
8367
+ if (!existsSync26(manifestPath)) {
8290
8368
  throw new Error(
8291
8369
  `${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>\`.)`
8292
8370
  );
@@ -8312,8 +8390,8 @@ function parsePluginTarget(target) {
8312
8390
  async function cloneAndValidatePlugin(entry, git) {
8313
8391
  const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
8314
8392
  try {
8315
- const manifestPath = join26(tmpDir, "biffo.plugin.json");
8316
- if (!existsSync25(manifestPath)) {
8393
+ const manifestPath = join27(tmpDir, "biffo.plugin.json");
8394
+ if (!existsSync26(manifestPath)) {
8317
8395
  throw new Error(
8318
8396
  `Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
8319
8397
  );
@@ -8341,8 +8419,8 @@ async function runPluginInstall(target, options, deps) {
8341
8419
  `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\`).`
8342
8420
  );
8343
8421
  }
8344
- const servicesDir = join26(options.cwd, "services");
8345
- if (!existsSync25(servicesDir)) {
8422
+ const servicesDir = join27(options.cwd, "services");
8423
+ if (!existsSync26(servicesDir)) {
8346
8424
  throw new Error(
8347
8425
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8348
8426
  );
@@ -8360,10 +8438,10 @@ async function runPluginInstall(target, options, deps) {
8360
8438
  }
8361
8439
  const pluginName = entry ? entry.name : source.name;
8362
8440
  const relTargetDir = pluginDir(pluginName, "third-party");
8363
- const targetDir = join26(options.cwd, relTargetDir);
8364
- const modulesDir = join26(options.cwd, "modules", "plugins", pluginName);
8441
+ const targetDir = join27(options.cwd, relTargetDir);
8442
+ const modulesDir = join27(options.cwd, "modules", "plugins", pluginName);
8365
8443
  const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
8366
- if (existsSync25(targetDir) && !inTreeSource) {
8444
+ if (existsSync26(targetDir) && !inTreeSource) {
8367
8445
  throw new Error(
8368
8446
  `Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
8369
8447
  );
@@ -8405,8 +8483,8 @@ async function runPluginInstall(target, options, deps) {
8405
8483
  });
8406
8484
  log.success(`Installed plugin source at ${relTargetDir}/`);
8407
8485
  }
8408
- const pluginPyproject = join26(targetDir, "pyproject.toml");
8409
- if (existsSync25(pluginPyproject)) {
8486
+ const pluginPyproject = join27(targetDir, "pyproject.toml");
8487
+ if (existsSync26(pluginPyproject)) {
8410
8488
  const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(options.cwd));
8411
8489
  if (sourced.length > 0) {
8412
8490
  log.info(
@@ -8415,8 +8493,8 @@ async function runPluginInstall(target, options, deps) {
8415
8493
  }
8416
8494
  }
8417
8495
  const stagePaths = [relTargetDir];
8418
- const tfSourceDir = join26(targetDir, "terraform");
8419
- if (existsSync25(tfSourceDir)) {
8496
+ const tfSourceDir = join27(targetDir, "terraform");
8497
+ if (existsSync26(tfSourceDir)) {
8420
8498
  mkdirSync9(modulesDir, { recursive: true });
8421
8499
  cpSync3(tfSourceDir, modulesDir, { recursive: true });
8422
8500
  stagePaths.push(`modules/plugins/${pluginName}`);
@@ -8473,7 +8551,7 @@ async function runPluginInstall(target, options, deps) {
8473
8551
  }
8474
8552
  function parseManifestFile(path) {
8475
8553
  try {
8476
- return JSON.parse(readFileSync19(path, "utf8"));
8554
+ return JSON.parse(readFileSync20(path, "utf8"));
8477
8555
  } catch (err) {
8478
8556
  throw new Error(`Could not parse ${path} as JSON: ${err.message}`);
8479
8557
  }
@@ -8510,8 +8588,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
8510
8588
  }
8511
8589
 
8512
8590
  // src/commands/plugin-list.ts
8513
- import { existsSync as existsSync26, readFileSync as readFileSync20 } from "fs";
8514
- import { join as join27, resolve as resolve13 } from "path";
8591
+ import { existsSync as existsSync27, readFileSync as readFileSync21 } from "fs";
8592
+ import { join as join28, resolve as resolve13 } from "path";
8515
8593
  import chalk16 from "chalk";
8516
8594
  import { Command as Command16 } from "commander";
8517
8595
  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) => {
@@ -8524,8 +8602,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
8524
8602
  }
8525
8603
  });
8526
8604
  async function runPluginList(options) {
8527
- const servicesDir = join27(options.cwd, "services");
8528
- if (!existsSync26(servicesDir)) {
8605
+ const servicesDir = join28(options.cwd, "services");
8606
+ if (!existsSync27(servicesDir)) {
8529
8607
  throw new Error(
8530
8608
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8531
8609
  );
@@ -8533,7 +8611,7 @@ async function runPluginList(options) {
8533
8611
  const plugins = [];
8534
8612
  for (const location of findInstalledPlugins(options.cwd)) {
8535
8613
  try {
8536
- const manifest = validateManifest(JSON.parse(readFileSync20(location.manifestPath, "utf8")));
8614
+ const manifest = validateManifest(JSON.parse(readFileSync21(location.manifestPath, "utf8")));
8537
8615
  plugins.push({
8538
8616
  name: manifest.name,
8539
8617
  version: manifest.version,
@@ -8570,8 +8648,8 @@ async function runPluginList(options) {
8570
8648
  }
8571
8649
 
8572
8650
  // src/commands/plugin-sync-migrations.ts
8573
- import { existsSync as existsSync27 } from "fs";
8574
- import { join as join28, relative as relative4, resolve as resolve14 } from "path";
8651
+ import { existsSync as existsSync28 } from "fs";
8652
+ import { join as join29, relative as relative4, resolve as resolve14 } from "path";
8575
8653
  import chalk17 from "chalk";
8576
8654
  import { Command as Command17 } from "commander";
8577
8655
  var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
@@ -8592,11 +8670,11 @@ var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
8592
8670
  }
8593
8671
  );
8594
8672
  async function runPluginSyncMigrations(name, options, deps) {
8595
- const servicesDir = join28(options.cwd, "services");
8596
- if (!existsSync27(servicesDir)) {
8673
+ const servicesDir = join29(options.cwd, "services");
8674
+ if (!existsSync28(servicesDir)) {
8597
8675
  throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
8598
8676
  }
8599
- if (name && !existsSync27(join28(servicesDir, name, "biffo.plugin.json"))) {
8677
+ if (name && !existsSync28(join29(servicesDir, name, "biffo.plugin.json"))) {
8600
8678
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
8601
8679
  }
8602
8680
  if (options.dryRun) {
@@ -8632,8 +8710,8 @@ async function runPluginSyncMigrations(name, options, deps) {
8632
8710
  }
8633
8711
 
8634
8712
  // src/commands/plugin-uninstall.ts
8635
- import { existsSync as existsSync28, readFileSync as readFileSync21, rmSync as rmSync8 } from "fs";
8636
- import { join as join29, resolve as resolve15 } from "path";
8713
+ import { existsSync as existsSync29, readFileSync as readFileSync22, rmSync as rmSync8 } from "fs";
8714
+ import { join as join30, resolve as resolve15 } from "path";
8637
8715
  import chalk18 from "chalk";
8638
8716
  import { Command as Command18 } from "commander";
8639
8717
  import inquirer6 from "inquirer";
@@ -8665,16 +8743,16 @@ async function runPluginUninstall(name, options, deps) {
8665
8743
  if (!NAME_PATTERN2.test(name)) {
8666
8744
  throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
8667
8745
  }
8668
- const servicesDir = join29(options.cwd, "services");
8669
- if (!existsSync28(servicesDir)) {
8746
+ const servicesDir = join30(options.cwd, "services");
8747
+ if (!existsSync29(servicesDir)) {
8670
8748
  throw new Error(
8671
8749
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8672
8750
  );
8673
8751
  }
8674
- const targetDir = join29(servicesDir, name);
8675
- if (!existsSync28(targetDir)) {
8676
- const firstParty = join29(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
8677
- if (existsSync28(firstParty)) {
8752
+ const targetDir = join30(servicesDir, name);
8753
+ if (!existsSync29(targetDir)) {
8754
+ const firstParty = join30(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
8755
+ if (existsSync29(firstParty)) {
8678
8756
  throw new Error(
8679
8757
  `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.`
8680
8758
  );
@@ -8682,9 +8760,9 @@ async function runPluginUninstall(name, options, deps) {
8682
8760
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
8683
8761
  }
8684
8762
  const version = readInstalledVersion(targetDir);
8685
- const modulesDir = join29(options.cwd, "modules", "plugins", name);
8763
+ const modulesDir = join30(options.cwd, "modules", "plugins", name);
8686
8764
  const stagePaths = [`services/${name}`];
8687
- if (existsSync28(modulesDir)) {
8765
+ if (existsSync29(modulesDir)) {
8688
8766
  stagePaths.push(`modules/plugins/${name}`);
8689
8767
  }
8690
8768
  if (options.dryRun) {
@@ -8706,7 +8784,7 @@ async function runPluginUninstall(name, options, deps) {
8706
8784
  }
8707
8785
  rmSync8(targetDir, { recursive: true, force: true });
8708
8786
  log.success(`Removed services/${name}/`);
8709
- if (existsSync28(modulesDir)) {
8787
+ if (existsSync29(modulesDir)) {
8710
8788
  rmSync8(modulesDir, { recursive: true, force: true });
8711
8789
  log.success(`Removed modules/plugins/${name}/`);
8712
8790
  const wiring = syncPluginTerraform(options.cwd);
@@ -8743,10 +8821,10 @@ async function runPluginUninstall(name, options, deps) {
8743
8821
  }
8744
8822
  }
8745
8823
  function readInstalledVersion(targetDir) {
8746
- const manifestPath = join29(targetDir, "biffo.plugin.json");
8747
- if (!existsSync28(manifestPath)) return void 0;
8824
+ const manifestPath = join30(targetDir, "biffo.plugin.json");
8825
+ if (!existsSync29(manifestPath)) return void 0;
8748
8826
  try {
8749
- return validateManifest(JSON.parse(readFileSync21(manifestPath, "utf8"))).version;
8827
+ return validateManifest(JSON.parse(readFileSync22(manifestPath, "utf8"))).version;
8750
8828
  } catch {
8751
8829
  return void 0;
8752
8830
  }
@@ -8779,8 +8857,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
8779
8857
  }
8780
8858
 
8781
8859
  // src/commands/plugin-upgrade.ts
8782
- import { cpSync as cpSync4, existsSync as existsSync29, mkdirSync as mkdirSync10, readFileSync as readFileSync22, rmSync as rmSync9 } from "fs";
8783
- import { join as join30, relative as relative5, resolve as resolve16 } from "path";
8860
+ import { cpSync as cpSync4, existsSync as existsSync30, mkdirSync as mkdirSync10, readFileSync as readFileSync23, rmSync as rmSync9 } from "fs";
8861
+ import { join as join31, relative as relative5, resolve as resolve16 } from "path";
8784
8862
  import chalk19 from "chalk";
8785
8863
  import { Command as Command19 } from "commander";
8786
8864
  import inquirer7 from "inquirer";
@@ -8805,14 +8883,14 @@ var pluginUpgradeCommand = new Command19("upgrade").description(
8805
8883
  });
8806
8884
  async function runPluginUpgrade(target, options, deps) {
8807
8885
  const { name, minor } = parsePluginTarget(target);
8808
- const servicesDir = join30(options.cwd, "services");
8809
- if (!existsSync29(servicesDir)) {
8886
+ const servicesDir = join31(options.cwd, "services");
8887
+ if (!existsSync30(servicesDir)) {
8810
8888
  throw new Error(
8811
8889
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8812
8890
  );
8813
8891
  }
8814
- const targetDir = join30(servicesDir, name);
8815
- if (!existsSync29(targetDir)) {
8892
+ const targetDir = join31(servicesDir, name);
8893
+ if (!existsSync30(targetDir)) {
8816
8894
  throw new Error(
8817
8895
  `Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
8818
8896
  );
@@ -8826,7 +8904,7 @@ async function runPluginUpgrade(target, options, deps) {
8826
8904
  `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.`
8827
8905
  );
8828
8906
  }
8829
- const modulesDir = join30(options.cwd, "modules", "plugins", entry.name);
8907
+ const modulesDir = join31(options.cwd, "modules", "plugins", entry.name);
8830
8908
  if (options.dryRun) {
8831
8909
  printDryRun6(entry, currentVersion);
8832
8910
  return;
@@ -8859,11 +8937,11 @@ async function runPluginUpgrade(target, options, deps) {
8859
8937
  cpSync4(tmpDir, targetDir, { recursive: true });
8860
8938
  log.success(`Upgraded plugin source at services/${entry.name}/`);
8861
8939
  const stagePaths = [`services/${entry.name}`];
8862
- if (existsSync29(modulesDir)) {
8940
+ if (existsSync30(modulesDir)) {
8863
8941
  rmSync9(modulesDir, { recursive: true, force: true });
8864
8942
  }
8865
- const tfSourceDir = join30(targetDir, "terraform");
8866
- if (existsSync29(tfSourceDir)) {
8943
+ const tfSourceDir = join31(targetDir, "terraform");
8944
+ if (existsSync30(tfSourceDir)) {
8867
8945
  mkdirSync10(modulesDir, { recursive: true });
8868
8946
  cpSync4(tfSourceDir, modulesDir, { recursive: true });
8869
8947
  stagePaths.push(`modules/plugins/${entry.name}`);
@@ -8897,10 +8975,10 @@ async function runPluginUpgrade(target, options, deps) {
8897
8975
  }
8898
8976
  }
8899
8977
  function readInstalledVersion2(targetDir) {
8900
- const manifestPath = join30(targetDir, "biffo.plugin.json");
8901
- if (!existsSync29(manifestPath)) return void 0;
8978
+ const manifestPath = join31(targetDir, "biffo.plugin.json");
8979
+ if (!existsSync30(manifestPath)) return void 0;
8902
8980
  try {
8903
- return validateManifest(JSON.parse(readFileSync22(manifestPath, "utf8"))).version;
8981
+ return validateManifest(JSON.parse(readFileSync23(manifestPath, "utf8"))).version;
8904
8982
  } catch {
8905
8983
  return void 0;
8906
8984
  }
@@ -8942,7 +9020,7 @@ pluginCommand.addCommand(pluginInfoCommand);
8942
9020
  import { Command as Command22 } from "commander";
8943
9021
 
8944
9022
  // src/commands/sibling-check-identity.ts
8945
- import { existsSync as existsSync30, readFileSync as readFileSync23 } from "fs";
9023
+ import { existsSync as existsSync31, readFileSync as readFileSync24 } from "fs";
8946
9024
  import { resolve as resolve17 } from "path";
8947
9025
  import chalk20 from "chalk";
8948
9026
  import { Command as Command21 } from "commander";
@@ -9136,7 +9214,7 @@ async function fetchPublishedIdentity(portalUrl) {
9136
9214
  }
9137
9215
  async function resolveConfig4(options) {
9138
9216
  if (options.config) {
9139
- const raw = JSON.parse(readFileSync23(resolve17(options.config), "utf8"));
9217
+ const raw = JSON.parse(readFileSync24(resolve17(options.config), "utf8"));
9140
9218
  const result = BiffoConfigSchema.safeParse(raw);
9141
9219
  if (!result.success) {
9142
9220
  log.error(`Invalid config at ${options.config}:`);
@@ -9156,8 +9234,8 @@ async function resolveConfig4(options) {
9156
9234
  return cfg;
9157
9235
  }
9158
9236
  const localConfigPath = resolve17(process.cwd(), "biffo.config.json");
9159
- if (existsSync30(localConfigPath)) {
9160
- const raw = JSON.parse(readFileSync23(localConfigPath, "utf8"));
9237
+ if (existsSync31(localConfigPath)) {
9238
+ const raw = JSON.parse(readFileSync24(localConfigPath, "utf8"));
9161
9239
  const result = BiffoConfigSchema.safeParse(raw);
9162
9240
  if (result.success) return result.data;
9163
9241
  if (isTemplatePlaceholderConfig(raw)) {
@@ -9203,21 +9281,21 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
9203
9281
  import { Command as Command23 } from "commander";
9204
9282
 
9205
9283
  // src/scripts/check-adr-numbering.ts
9206
- import { existsSync as existsSync32 } from "fs";
9207
- import { join as join32 } from "path";
9284
+ import { existsSync as existsSync33 } from "fs";
9285
+ import { join as join33 } from "path";
9208
9286
  import { execa as execa5 } from "execa";
9209
9287
 
9210
9288
  // src/lib/adr-numbering-guard.ts
9211
- import { existsSync as existsSync31, readdirSync as readdirSync13, readFileSync as readFileSync24 } from "fs";
9212
- import { join as join31 } from "path";
9289
+ import { existsSync as existsSync32, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "fs";
9290
+ import { join as join32 } from "path";
9213
9291
  var ADR_FILENAME = /^(\d{4})-.+\.md$/;
9214
9292
  var ALLOWLIST_FILENAME = ".numbering-allowlist";
9215
9293
  var TEMPLATE_ADR_RESERVED_UPTO = "0099";
9216
9294
  function readAdrNumberingAllowlist(adrDir) {
9217
- const path = join31(adrDir, ALLOWLIST_FILENAME);
9218
- if (!existsSync31(path)) return /* @__PURE__ */ new Set();
9295
+ const path = join32(adrDir, ALLOWLIST_FILENAME);
9296
+ if (!existsSync32(path)) return /* @__PURE__ */ new Set();
9219
9297
  const numbers = /* @__PURE__ */ new Set();
9220
- for (const rawLine of readFileSync24(path, "utf8").split("\n")) {
9298
+ for (const rawLine of readFileSync25(path, "utf8").split("\n")) {
9221
9299
  const line = rawLine.split("#")[0].trim();
9222
9300
  if (line) numbers.add(line);
9223
9301
  }
@@ -9225,7 +9303,7 @@ function readAdrNumberingAllowlist(adrDir) {
9225
9303
  }
9226
9304
  function adrNumbersIn(adrDir) {
9227
9305
  const claims = /* @__PURE__ */ new Map();
9228
- if (!existsSync31(adrDir)) return claims;
9306
+ if (!existsSync32(adrDir)) return claims;
9229
9307
  for (const entry of readdirSync13(adrDir).sort()) {
9230
9308
  const match = ADR_FILENAME.exec(entry);
9231
9309
  if (!match) continue;
@@ -9280,8 +9358,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
9280
9358
  // src/scripts/check-adr-numbering.ts
9281
9359
  async function runAdrNumberingCheck() {
9282
9360
  const root = (await execa5("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
9283
- const adrDir = join32(root, "docs", "ADR");
9284
- if (!existsSync32(adrDir)) {
9361
+ const adrDir = join33(root, "docs", "ADR");
9362
+ if (!existsSync33(adrDir)) {
9285
9363
  console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
9286
9364
  return;
9287
9365
  }
@@ -9602,8 +9680,8 @@ async function runOwnershipCheck(argv) {
9602
9680
  const { stdout } = await execa7("git", ["diff", "--cached", "--name-status"], { cwd: root });
9603
9681
  ({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
9604
9682
  if (messageFile) {
9605
- const { readFileSync: readFileSync27, existsSync: existsSync37 } = await import("fs");
9606
- if (existsSync37(messageFile)) commitMessage = readFileSync27(messageFile, "utf8");
9683
+ const { readFileSync: readFileSync28, existsSync: existsSync38 } = await import("fs");
9684
+ if (existsSync38(messageFile)) commitMessage = readFileSync28(messageFile, "utf8");
9607
9685
  }
9608
9686
  } else {
9609
9687
  const base = process.env["GITHUB_BASE_REF"] ?? args[0];
@@ -9704,33 +9782,33 @@ ${BOLD}If the divergence is deliberate${OFF}
9704
9782
  }
9705
9783
 
9706
9784
  // src/scripts/check-plugin-collisions.ts
9707
- import { existsSync as existsSync34 } from "fs";
9708
- import { join as join34 } from "path";
9785
+ import { existsSync as existsSync35 } from "fs";
9786
+ import { join as join35 } from "path";
9709
9787
  import { execa as execa8 } from "execa";
9710
9788
 
9711
9789
  // src/lib/plugin-collision-guard.ts
9712
- import { existsSync as existsSync33, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
9713
- import { join as join33 } from "path";
9790
+ import { existsSync as existsSync34, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
9791
+ import { join as join34 } from "path";
9714
9792
  var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
9715
9793
  var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
9716
9794
  function subdirectories(dir) {
9717
- if (!existsSync33(dir)) return [];
9795
+ if (!existsSync34(dir)) return [];
9718
9796
  return readdirSync14(dir).filter((entry) => {
9719
9797
  if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
9720
9798
  try {
9721
- return statSync7(join33(dir, entry)).isDirectory();
9799
+ return statSync7(join34(dir, entry)).isDirectory();
9722
9800
  } catch {
9723
9801
  return false;
9724
9802
  }
9725
9803
  });
9726
9804
  }
9727
9805
  function regularPackagesOf(pluginDir2) {
9728
- return subdirectories(pluginDir2).filter((name) => existsSync33(join33(pluginDir2, name, "__init__.py"))).sort();
9806
+ return subdirectories(pluginDir2).filter((name) => existsSync34(join34(pluginDir2, name, "__init__.py"))).sort();
9729
9807
  }
9730
9808
  function bareTestModulesOf(pluginDir2) {
9731
- const testsDir = join33(pluginDir2, "tests");
9732
- if (!existsSync33(testsDir)) return [];
9733
- if (existsSync33(join33(testsDir, "__init__.py"))) return [];
9809
+ const testsDir = join34(pluginDir2, "tests");
9810
+ if (!existsSync34(testsDir)) return [];
9811
+ if (existsSync34(join34(testsDir, "__init__.py"))) return [];
9734
9812
  return readdirSync14(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
9735
9813
  }
9736
9814
  function findCollisions(servicesDir, pluginDirs) {
@@ -9739,7 +9817,7 @@ function findCollisions(servicesDir, pluginDirs) {
9739
9817
  const gather = (kind, namesOf) => {
9740
9818
  const claims = /* @__PURE__ */ new Map();
9741
9819
  for (const plugin of plugins) {
9742
- for (const name of namesOf(join33(servicesDir, plugin))) {
9820
+ for (const name of namesOf(join34(servicesDir, plugin))) {
9743
9821
  claims.set(name, [...claims.get(name) ?? [], plugin]);
9744
9822
  }
9745
9823
  }
@@ -9777,8 +9855,8 @@ function formatCollisions(collisions) {
9777
9855
  // src/scripts/check-plugin-collisions.ts
9778
9856
  async function runPluginCollisionCheck() {
9779
9857
  const root = (await execa8("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
9780
- const servicesDir = join34(root, "services");
9781
- if (!existsSync34(servicesDir)) {
9858
+ const servicesDir = join35(root, "services");
9859
+ if (!existsSync35(servicesDir)) {
9782
9860
  console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
9783
9861
  return;
9784
9862
  }
@@ -9798,8 +9876,8 @@ async function runPluginCollisionCheck() {
9798
9876
  import { execa as execa9 } from "execa";
9799
9877
 
9800
9878
  // src/lib/plugin-terraform-guard.ts
9801
- import { existsSync as existsSync35, readFileSync as readFileSync25, readdirSync as readdirSync15 } from "fs";
9802
- import { dirname as dirname9, join as join35, relative as relative6, sep as sep3 } from "path";
9879
+ import { existsSync as existsSync36, readFileSync as readFileSync26, readdirSync as readdirSync15 } from "fs";
9880
+ import { dirname as dirname9, join as join36, relative as relative6, sep as sep3 } from "path";
9803
9881
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
9804
9882
  var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
9805
9883
  function findPluginManifests(root) {
@@ -9814,9 +9892,9 @@ function findPluginManifests(root) {
9814
9892
  for (const entry of entries) {
9815
9893
  if (entry.isDirectory()) {
9816
9894
  if (SKIP_DIRS.has(entry.name)) continue;
9817
- walk(join35(dir, entry.name));
9895
+ walk(join36(dir, entry.name));
9818
9896
  } else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
9819
- found.push(relative6(root, join35(dir, entry.name)).split(sep3).join("/"));
9897
+ found.push(relative6(root, join36(dir, entry.name)).split(sep3).join("/"));
9820
9898
  }
9821
9899
  }
9822
9900
  };
@@ -9826,7 +9904,7 @@ function findPluginManifests(root) {
9826
9904
  function readSubscriptions(absManifestPath) {
9827
9905
  let parsed;
9828
9906
  try {
9829
- parsed = JSON.parse(readFileSync25(absManifestPath, "utf8"));
9907
+ parsed = JSON.parse(readFileSync26(absManifestPath, "utf8"));
9830
9908
  } catch {
9831
9909
  return null;
9832
9910
  }
@@ -9841,14 +9919,14 @@ function readSubscriptions(absManifestPath) {
9841
9919
  }
9842
9920
  function checkPluginTerraform(root) {
9843
9921
  const violations = [];
9844
- const coreManifest = existsSync35(join35(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
9922
+ const coreManifest = existsSync36(join36(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
9845
9923
  for (const manifest of findPluginManifests(root)) {
9846
9924
  if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
9847
- const absManifest = join35(root, manifest);
9925
+ const absManifest = join36(root, manifest);
9848
9926
  const subscriptions = readSubscriptions(absManifest);
9849
9927
  if (subscriptions === null) continue;
9850
9928
  const pluginDir2 = dirname9(absManifest);
9851
- if (existsSync35(join35(pluginDir2, "terraform"))) continue;
9929
+ if (existsSync36(join36(pluginDir2, "terraform"))) continue;
9852
9930
  const relPluginDir = relative6(root, pluginDir2).split(sep3).join("/");
9853
9931
  violations.push({
9854
9932
  manifest,
@@ -10044,8 +10122,8 @@ function rawArgsAfter(subcommand) {
10044
10122
  }
10045
10123
 
10046
10124
  // src/commands/doctor.ts
10047
- import { existsSync as existsSync36, readFileSync as readFileSync26 } from "fs";
10048
- import { join as join36, resolve as resolve18 } from "path";
10125
+ import { existsSync as existsSync37, readFileSync as readFileSync27 } from "fs";
10126
+ import { join as join37, resolve as resolve18 } from "path";
10049
10127
  import chalk21 from "chalk";
10050
10128
  import { Command as Command24 } from "commander";
10051
10129
 
@@ -10220,10 +10298,10 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
10220
10298
  return runDoctorChecks(facts);
10221
10299
  }
10222
10300
  function readLocalCoreVersion(cwd) {
10223
- const path = join36(cwd, INSTANCE_CORE_FILE);
10224
- if (!existsSync36(path)) return null;
10301
+ const path = join37(cwd, INSTANCE_CORE_FILE);
10302
+ if (!existsSync37(path)) return null;
10225
10303
  try {
10226
- return parseCoreRecord(readFileSync26(path, "utf8"));
10304
+ return parseCoreRecord(readFileSync27(path, "utf8"));
10227
10305
  } catch {
10228
10306
  return null;
10229
10307
  }
@@ -10238,10 +10316,10 @@ function parseCoreRecord(contents) {
10238
10316
  }
10239
10317
  }
10240
10318
  function readFossil(cwd) {
10241
- const path = join36(cwd, CORE_VERSION_FILE);
10242
- if (!existsSync36(path)) return null;
10319
+ const path = join37(cwd, CORE_VERSION_FILE);
10320
+ if (!existsSync37(path)) return null;
10243
10321
  try {
10244
- const value = readFileSync26(path, "utf8").trim();
10322
+ const value = readFileSync27(path, "utf8").trim();
10245
10323
  return value === "" ? null : value;
10246
10324
  } catch {
10247
10325
  return null;