@biffo/cli 0.258.0 → 0.258.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.
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 existsSync13, rmSync as rmSync5 } from "fs";
574
- import { join as join14, resolve as resolve3 } from "path";
573
+ import { existsSync as existsSync14, rmSync as rmSync5 } from "fs";
574
+ import { join as join15, 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";
@@ -2783,17 +2783,110 @@ function materializeTemplateAtTag(repo, version, git = defaultGit2) {
2783
2783
  return { dir, cleanup: () => rmSync3(dir, { recursive: true, force: true }) };
2784
2784
  }
2785
2785
 
2786
- // src/lib/instance-seams.ts
2786
+ // src/lib/core-upgrade-target-fidelity.ts
2787
+ import { execFileSync as execFileSync4 } from "child_process";
2788
+ import { createHash as createHash2 } from "crypto";
2787
2789
  import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
2788
2790
  import { join as join9 } from "path";
2791
+ var VERBATIM_STATUSES = /* @__PURE__ */ new Set(["take-theirs", "added", "add-conflict", "restored"]);
2792
+ var DERIVED_STATUSES = /* @__PURE__ */ new Set(["merged", "conflict"]);
2793
+ var defaultGit3 = (args) => execFileSync4("git", args, { encoding: "utf8" });
2794
+ function blobId(content) {
2795
+ const bytes = Buffer.from(content, "utf8");
2796
+ return createHash2("sha1").update(`blob ${String(bytes.length)}\0`).update(bytes).digest("hex");
2797
+ }
2798
+ function tagBlobIds(repo, tag, git) {
2799
+ const out = git(["-C", repo, "ls-tree", "-r", "-z", tag]);
2800
+ const blobs = /* @__PURE__ */ new Map();
2801
+ for (const record of out.split("\0")) {
2802
+ if (record === "") continue;
2803
+ const tab = record.indexOf(" ");
2804
+ if (tab === -1) continue;
2805
+ const meta = record.slice(0, tab).split(" ");
2806
+ const id = meta[2];
2807
+ if (meta[1] !== "blob" || id === void 0) continue;
2808
+ blobs.set(record.slice(tab + 1), id);
2809
+ }
2810
+ return blobs;
2811
+ }
2812
+ function assertTargetFidelity(options) {
2813
+ const git = options.git ?? defaultGit3;
2814
+ if (options.explicitTargetTree === true) {
2815
+ return {
2816
+ checked: 0,
2817
+ findings: [],
2818
+ unverifiable: "the target tree was supplied explicitly (--to-template), so there is no tag to compare against. Re-run with --to <version> to have the upgrade verify its own output."
2819
+ };
2820
+ }
2821
+ const tag = coreTag(options.toVersion);
2822
+ let blobs;
2823
+ try {
2824
+ blobs = tagBlobIds(options.templateRepo, tag, git);
2825
+ } catch (err) {
2826
+ return {
2827
+ checked: 0,
2828
+ findings: [],
2829
+ unverifiable: `could not read ${tag} from ${options.templateRepo} to verify the upgrade's own output: ${err.message}`
2830
+ };
2831
+ }
2832
+ const findings = [];
2833
+ let checked = 0;
2834
+ for (const entry of options.entries) {
2835
+ let content;
2836
+ let source;
2837
+ if (VERBATIM_STATUSES.has(entry.status)) {
2838
+ content = entry.content;
2839
+ source = "output";
2840
+ } else if (DERIVED_STATUSES.has(entry.status)) {
2841
+ const abs = join9(options.theirsDir, entry.path);
2842
+ content = existsSync8(abs) ? readFileSync6(abs, "utf8") : void 0;
2843
+ source = "merge-input";
2844
+ } else {
2845
+ continue;
2846
+ }
2847
+ if (content === void 0) continue;
2848
+ checked++;
2849
+ const expected = blobs.get(entry.path);
2850
+ if (expected === void 0) {
2851
+ findings.push({ path: entry.path, reason: "absent-from-tag", source });
2852
+ continue;
2853
+ }
2854
+ if (blobId(content) === expected) continue;
2855
+ let tagText;
2856
+ try {
2857
+ tagText = git(["-C", options.templateRepo, "show", `${tag}:${entry.path}`]);
2858
+ } catch {
2859
+ findings.push({ path: entry.path, reason: "content-differs", source });
2860
+ continue;
2861
+ }
2862
+ if (tagText !== content) {
2863
+ findings.push({ path: entry.path, reason: "content-differs", source });
2864
+ }
2865
+ }
2866
+ return { checked, findings, unverifiable: null };
2867
+ }
2868
+ function fidelityFailure(report, toVersion) {
2869
+ const lines = report.findings.map((f) => {
2870
+ const why = f.reason === "absent-from-tag" ? "resolved content for a path the tag does not contain" : "content differs from the tag";
2871
+ return ` ${f.path} \u2014 ${why} (${f.source})`;
2872
+ });
2873
+ return `This upgrade resolved content that is not what core-v${toVersion} ships, for ${String(report.findings.length)} of ${String(report.checked)} checked path(s):
2874
+ ${lines.join("\n")}
2875
+
2876
+ An upgrade must be reproducible from a version number, so this is refused rather than distributed (#1399). The content came from somewhere other than the tag \u2014 a stale or wrong template checkout, or an unmerged branch. Verify the template repo is at the target tag and re-run; do not commit the plan as-is.`;
2877
+ }
2878
+
2879
+ // src/lib/instance-seams.ts
2880
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
2881
+ import { join as join10 } from "path";
2789
2882
  var INSTANCE_SEAM_PREFIX = "@/instance-";
2790
2883
  function readSeams(templateDir, portalRelDir) {
2791
2884
  const seams = /* @__PURE__ */ new Map();
2792
- const tsconfigPath = join9(templateDir, portalRelDir, "tsconfig.json");
2793
- if (!existsSync8(tsconfigPath)) return seams;
2885
+ const tsconfigPath = join10(templateDir, portalRelDir, "tsconfig.json");
2886
+ if (!existsSync9(tsconfigPath)) return seams;
2794
2887
  let parsed;
2795
2888
  try {
2796
- parsed = JSON.parse(readFileSync6(tsconfigPath, "utf8"));
2889
+ parsed = JSON.parse(readFileSync7(tsconfigPath, "utf8"));
2797
2890
  } catch {
2798
2891
  return seams;
2799
2892
  }
@@ -2816,15 +2909,15 @@ function findNewUndeclaredSeams(baseDir, theirsDir, oursDir, portalRelDir = "app
2816
2909
  const undeclared = [];
2817
2910
  for (const [specifier, seam] of theirsSeams) {
2818
2911
  if (baseSeams.has(specifier)) continue;
2819
- if (existsSync8(join9(oursDir, seam.instanceFile))) continue;
2912
+ if (existsSync9(join10(oursDir, seam.instanceFile))) continue;
2820
2913
  undeclared.push(seam);
2821
2914
  }
2822
2915
  return undeclared.sort((a, b) => a.specifier.localeCompare(b.specifier));
2823
2916
  }
2824
2917
 
2825
2918
  // src/lib/breaking-changes.ts
2826
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
2827
- import { join as join10 } from "path";
2919
+ import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
2920
+ import { join as join11 } from "path";
2828
2921
  var UPGRADE_GUIDE_PATH = "docs/guides/core-upgrade.md";
2829
2922
  var SECTION_HEADING = "## Breaking changes by version";
2830
2923
  var ENTRY_HEADING = /^###\s+(\d+\.\d+\.\d+)\s*[—-]\s*(.+?)\s*$/;
@@ -2849,9 +2942,9 @@ function parseBreakingChanges(guide) {
2849
2942
  return entries;
2850
2943
  }
2851
2944
  function readBreakingChanges(templateRoot) {
2852
- const path = join10(templateRoot, UPGRADE_GUIDE_PATH);
2853
- if (!existsSync9(path)) return [];
2854
- return parseBreakingChanges(readFileSync7(path, "utf8"));
2945
+ const path = join11(templateRoot, UPGRADE_GUIDE_PATH);
2946
+ if (!existsSync10(path)) return [];
2947
+ return parseBreakingChanges(readFileSync8(path, "utf8"));
2855
2948
  }
2856
2949
  function breakingChangesBetween(from, to, entries) {
2857
2950
  parseCoreVersion(from);
@@ -2868,8 +2961,8 @@ var GLOBAL_DISPATCH_WORKFLOW_PATHS = [
2868
2961
  ];
2869
2962
 
2870
2963
  // src/lib/plugin-terraform-wiring.ts
2871
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
2872
- import { join as join11 } from "path";
2964
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync9, readdirSync as readdirSync3, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
2965
+ import { join as join12 } from "path";
2873
2966
  var TEMPLATE_MODULE_DIR = "_template";
2874
2967
  var DEFAULT_PLUGIN_HANDLER = "src.lambda.main.handler";
2875
2968
  var GENERATED_TF_FILE = "plugins.generated.tf";
@@ -2894,7 +2987,7 @@ function standardArguments(pluginName, handler) {
2894
2987
  ];
2895
2988
  }
2896
2989
  function listPluginModules(cwd) {
2897
- const dir = join11(cwd, "modules", "plugins");
2990
+ const dir = join12(cwd, "modules", "plugins");
2898
2991
  let entries;
2899
2992
  try {
2900
2993
  entries = readdirSync3(dir, { withFileTypes: true });
@@ -2906,7 +2999,7 @@ function listPluginModules(cwd) {
2906
2999
  var FIRST_PARTY_TERRAFORM = (name) => `../../../services/_plugins/${name}/terraform`;
2907
3000
  var THIRD_PARTY_TERRAFORM = (name) => `../../../modules/plugins/${name}`;
2908
3001
  function isFirstPartyPlugin(cwd, name) {
2909
- return existsSync10(join11(cwd, "services", "_plugins", name, "terraform", "main.tf"));
3002
+ return existsSync11(join12(cwd, "services", "_plugins", name, "terraform", "main.tf"));
2910
3003
  }
2911
3004
  function pluginModuleSource(cwd, name) {
2912
3005
  return isFirstPartyPlugin(cwd, name) ? FIRST_PARTY_TERRAFORM(name) : THIRD_PARTY_TERRAFORM(name);
@@ -2915,7 +3008,7 @@ function listWireablePlugins(cwd) {
2915
3008
  return listPluginModules(cwd).filter((name) => !isFirstPartyPlugin(cwd, name)).sort();
2916
3009
  }
2917
3010
  function firstPartyPluginNames(cwd) {
2918
- const dir = join11(cwd, "services", "_plugins");
3011
+ const dir = join12(cwd, "services", "_plugins");
2919
3012
  let entries;
2920
3013
  try {
2921
3014
  entries = readdirSync3(dir, { withFileTypes: true });
@@ -2929,7 +3022,7 @@ function staleFirstPartyCopies(cwd) {
2929
3022
  return firstPartyPluginNames(cwd).filter((name) => copied.has(name));
2930
3023
  }
2931
3024
  function listEnvironments(cwd) {
2932
- const dir = join11(cwd, "infra", "environments");
3025
+ const dir = join12(cwd, "infra", "environments");
2933
3026
  let entries;
2934
3027
  try {
2935
3028
  entries = readdirSync3(dir, { withFileTypes: true });
@@ -2937,12 +3030,12 @@ function listEnvironments(cwd) {
2937
3030
  return [];
2938
3031
  }
2939
3032
  return entries.filter((e) => {
2940
- if (!e.isDirectory() || !existsSync10(join11(dir, e.name, "main.tf"))) return false;
2941
- return declaredVariables(join11(dir, e.name)).has("enabled_plugins");
3033
+ if (!e.isDirectory() || !existsSync11(join12(dir, e.name, "main.tf"))) return false;
3034
+ return declaredVariables(join12(dir, e.name)).has("enabled_plugins");
2942
3035
  }).map((e) => e.name).sort();
2943
3036
  }
2944
3037
  function listUnwirableEnvironments(cwd) {
2945
- const dir = join11(cwd, "infra", "environments");
3038
+ const dir = join12(cwd, "infra", "environments");
2946
3039
  let entries;
2947
3040
  try {
2948
3041
  entries = readdirSync3(dir, { withFileTypes: true });
@@ -2950,7 +3043,7 @@ function listUnwirableEnvironments(cwd) {
2950
3043
  return [];
2951
3044
  }
2952
3045
  return entries.filter(
2953
- (e) => e.isDirectory() && existsSync10(join11(dir, e.name, "main.tf")) && !declaredVariables(join11(dir, e.name)).has("enabled_plugins")
3046
+ (e) => e.isDirectory() && existsSync11(join12(dir, e.name, "main.tf")) && !declaredVariables(join12(dir, e.name)).has("enabled_plugins")
2954
3047
  ).map((e) => e.name).sort();
2955
3048
  }
2956
3049
  function declaredVariables(moduleDir) {
@@ -2965,7 +3058,7 @@ function declaredVariables(moduleDir) {
2965
3058
  if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
2966
3059
  let contents;
2967
3060
  try {
2968
- contents = readFileSync8(join11(moduleDir, entry.name), "utf8");
3061
+ contents = readFileSync9(join12(moduleDir, entry.name), "utf8");
2969
3062
  } catch {
2970
3063
  continue;
2971
3064
  }
@@ -2987,7 +3080,7 @@ function declaredOutputs(moduleDir) {
2987
3080
  if (!entry.isFile() || !entry.name.endsWith(".tf")) continue;
2988
3081
  let contents;
2989
3082
  try {
2990
- contents = readFileSync8(join11(moduleDir, entry.name), "utf8");
3083
+ contents = readFileSync9(join12(moduleDir, entry.name), "utf8");
2991
3084
  } catch {
2992
3085
  continue;
2993
3086
  }
@@ -3069,7 +3162,7 @@ function syncPluginTerraform(cwd) {
3069
3162
  const skippedEnvironments = listUnwirableEnvironments(cwd);
3070
3163
  const changedPaths = [];
3071
3164
  const rendered = plugins.map((name) => {
3072
- const moduleDir = join11(cwd, "modules", "plugins", name);
3165
+ const moduleDir = join12(cwd, "modules", "plugins", name);
3073
3166
  return {
3074
3167
  name,
3075
3168
  declaredVariables: declaredVariables(moduleDir),
@@ -3078,16 +3171,16 @@ function syncPluginTerraform(cwd) {
3078
3171
  };
3079
3172
  });
3080
3173
  for (const env of environments) {
3081
- const envDir = join11(cwd, "infra", "environments", env);
3082
- const tfPath = join11(envDir, GENERATED_TF_FILE);
3083
- const tfvarsPath = join11(envDir, GENERATED_TFVARS_FILE);
3174
+ const envDir = join12(cwd, "infra", "environments", env);
3175
+ const tfPath = join12(envDir, GENERATED_TF_FILE);
3176
+ const tfvarsPath = join12(envDir, GENERATED_TFVARS_FILE);
3084
3177
  const relBase = `infra/environments/${env}`;
3085
3178
  if (plugins.length === 0) {
3086
3179
  for (const [abs, rel] of [
3087
3180
  [tfPath, `${relBase}/${GENERATED_TF_FILE}`],
3088
3181
  [tfvarsPath, `${relBase}/${GENERATED_TFVARS_FILE}`]
3089
3182
  ]) {
3090
- if (existsSync10(abs)) {
3183
+ if (existsSync11(abs)) {
3091
3184
  rmSync4(abs);
3092
3185
  changedPaths.push(rel);
3093
3186
  }
@@ -3103,8 +3196,8 @@ function syncPluginTerraform(cwd) {
3103
3196
  }
3104
3197
 
3105
3198
  // src/lib/lockfile-refresh.ts
3106
- import { existsSync as existsSync11 } from "fs";
3107
- import { join as join12 } from "path";
3199
+ import { existsSync as existsSync12 } from "fs";
3200
+ import { join as join13 } from "path";
3108
3201
  var LOCKFILE_TRIGGERS = [
3109
3202
  {
3110
3203
  manifest: "package.json",
@@ -3127,7 +3220,7 @@ function lockfilesNeedingRefresh(changedPaths, instanceDir, triggers = LOCKFILE_
3127
3220
  const locked = changedPaths.filter((p) => !isForeignManifest(p));
3128
3221
  return triggers.filter((t) => {
3129
3222
  const touched = locked.some((p) => p === t.manifest || p.endsWith(`/${t.manifest}`));
3130
- return touched && existsSync11(join12(instanceDir, t.lockfile));
3223
+ return touched && existsSync12(join13(instanceDir, t.lockfile));
3131
3224
  });
3132
3225
  }
3133
3226
  async function refreshLockfiles(instanceDir, triggers, run) {
@@ -3147,11 +3240,11 @@ function describeFailures(outcomes) {
3147
3240
  }
3148
3241
 
3149
3242
  // src/lib/instance-dependency-install.ts
3150
- import { existsSync as existsSync12 } from "fs";
3151
- import { join as join13 } from "path";
3243
+ import { existsSync as existsSync13 } from "fs";
3244
+ import { join as join14 } from "path";
3152
3245
  function dependencyInstallSteps(instanceDir) {
3153
3246
  const steps = [{ ecosystem: "pnpm", command: ["pnpm", "install"] }];
3154
- if (existsSync12(join13(instanceDir, "pyproject.toml"))) {
3247
+ if (existsSync13(join14(instanceDir, "pyproject.toml"))) {
3155
3248
  steps.push({ ecosystem: "uv", command: ["uv", "sync"] });
3156
3249
  }
3157
3250
  return steps;
@@ -3363,6 +3456,13 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
3363
3456
  });
3364
3457
  const orphanRatchet = checkOrphanRatchet(plan.orphaned.length, readOrphanBaseline(options.cwd));
3365
3458
  const newSeams = findNewUndeclaredSeams(baseDir, theirsDir, options.cwd);
3459
+ const fidelity = assertTargetFidelity({
3460
+ entries: plan.entries,
3461
+ templateRepo,
3462
+ toVersion,
3463
+ theirsDir,
3464
+ explicitTargetTree: options.theirsDir !== void 0
3465
+ });
3366
3466
  const heading = options.apply ? "Biffo core upgrade" : "Biffo core upgrade (dry run)";
3367
3467
  console.log(chalk4.bold(`
3368
3468
  ${heading}
@@ -3371,6 +3471,10 @@ async function runCoreUpgradeResolved(options, deps, cleanups) {
3371
3471
  console.log(` merge base: ${fromVersion}`);
3372
3472
  console.log(` target: ${toVersion}
3373
3473
  `);
3474
+ printTargetFidelity(fidelity, toVersion);
3475
+ if (fidelity.findings.length > 0) {
3476
+ throw new Error(fidelityFailure(fidelity, toVersion));
3477
+ }
3374
3478
  printNewInstanceSeams(newSeams);
3375
3479
  printOrphanReport(plan.orphaned, orphanRatchet);
3376
3480
  if (orphanRatchet.increased) {
@@ -3518,7 +3622,7 @@ async function buildCommitAndOpenPr(options, deps, plan, migrations, fromVersion
3518
3622
  );
3519
3623
  }
3520
3624
  const cleanedCoreVersion = coreVersionCleanup?.action === "delete";
3521
- if (cleanedCoreVersion && existsSync13(coreVersionCleanup.path)) {
3625
+ if (cleanedCoreVersion && existsSync14(coreVersionCleanup.path)) {
3522
3626
  rmSync5(coreVersionCleanup.path);
3523
3627
  log.info(
3524
3628
  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).`
@@ -3863,6 +3967,29 @@ function printNewInstanceSeams(seams) {
3863
3967
  }
3864
3968
  console.log();
3865
3969
  }
3970
+ function printTargetFidelity(report, toVersion) {
3971
+ if (report.unverifiable !== null) {
3972
+ console.log(chalk4.yellow(` Target fidelity: NOT VERIFIED \u2014 ${report.unverifiable}`));
3973
+ console.log();
3974
+ return;
3975
+ }
3976
+ if (report.findings.length === 0) {
3977
+ console.log(
3978
+ chalk4.dim(
3979
+ ` Target fidelity: ${String(report.checked)} path(s) verified byte-identical to core-v${toVersion}.`
3980
+ )
3981
+ );
3982
+ console.log();
3983
+ return;
3984
+ }
3985
+ console.log(
3986
+ chalk4.red(
3987
+ ` Target fidelity: ${String(report.findings.length)} of ${String(report.checked)} checked path(s) are NOT core-v${toVersion} content (#1399):`
3988
+ )
3989
+ );
3990
+ for (const f of report.findings) console.log(` ${chalk4.red(f.path)} \u2014 ${f.reason}`);
3991
+ console.log();
3992
+ }
3866
3993
  function printOrphanReport(orphaned, ratchet) {
3867
3994
  if (orphaned.length === 0 && ratchet.baseline === null) return;
3868
3995
  console.log(
@@ -3956,8 +4083,8 @@ function printBreakingChanges(breaking, applying) {
3956
4083
  }
3957
4084
  function versionOfCheckout(dir, explicit) {
3958
4085
  if (explicit) return explicit;
3959
- const file = join14(dir, CORE_VERSION_FILE);
3960
- if (existsSync13(file)) return readCoreVersionFile(file);
4086
+ const file = join15(dir, CORE_VERSION_FILE);
4087
+ if (existsSync14(file)) return readCoreVersionFile(file);
3961
4088
  throw new Error(
3962
4089
  `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.`
3963
4090
  );
@@ -3965,8 +4092,8 @@ function versionOfCheckout(dir, explicit) {
3965
4092
  function latestCoreVersion(repo) {
3966
4093
  const fromTags = latestCoreVersionFromTags(repo);
3967
4094
  if (fromTags) return fromTags;
3968
- const file = join14(repo, CORE_VERSION_FILE);
3969
- if (existsSync13(file)) return readCoreVersionFile(file);
4095
+ const file = join15(repo, CORE_VERSION_FILE);
4096
+ if (existsSync14(file)) return readCoreVersionFile(file);
3970
4097
  throw new Error(
3971
4098
  `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.`
3972
4099
  );
@@ -3984,7 +4111,7 @@ coreCommand.addCommand(coreUpgradeCommand);
3984
4111
  import { Command as Command8 } from "commander";
3985
4112
 
3986
4113
  // src/commands/data-apply.ts
3987
- import { existsSync as existsSync15, readFileSync as readFileSync10 } from "fs";
4114
+ import { existsSync as existsSync16, readFileSync as readFileSync11 } from "fs";
3988
4115
  import { resolve as resolve4 } from "path";
3989
4116
  import chalk5 from "chalk";
3990
4117
  import { Command as Command5 } from "commander";
@@ -4435,16 +4562,16 @@ function isTemplatePlaceholderConfig(raw) {
4435
4562
 
4436
4563
  // src/lib/session.ts
4437
4564
  import {
4438
- existsSync as existsSync14,
4565
+ existsSync as existsSync15,
4439
4566
  mkdirSync as mkdirSync4,
4440
4567
  readdirSync as readdirSync4,
4441
- readFileSync as readFileSync9,
4568
+ readFileSync as readFileSync10,
4442
4569
  rmSync as rmSync6,
4443
4570
  statSync as statSync2,
4444
4571
  writeFileSync as writeFileSync5
4445
4572
  } from "fs";
4446
4573
  import { homedir } from "os";
4447
- import { join as join15 } from "path";
4574
+ import { join as join16 } from "path";
4448
4575
  var LEGACY_STEP_ALIASES = {
4449
4576
  github_config: ["github_branches", "github_instance_files", "github_settings"]
4450
4577
  };
@@ -4453,39 +4580,39 @@ function hasCompleted(session, step) {
4453
4580
  return session.completedSteps.some((done) => LEGACY_STEP_ALIASES[done]?.includes(step) ?? false);
4454
4581
  }
4455
4582
  function sessionsDir() {
4456
- return process.env["BIFFO_SESSIONS_DIR"] ?? join15(homedir(), ".biffo", "sessions");
4583
+ return process.env["BIFFO_SESSIONS_DIR"] ?? join16(homedir(), ".biffo", "sessions");
4457
4584
  }
4458
4585
  function sessionPath(projectName) {
4459
- return join15(sessionsDir(), `${projectName}.json`);
4586
+ return join16(sessionsDir(), `${projectName}.json`);
4460
4587
  }
4461
4588
  function loadSession(projectName) {
4462
4589
  const path = sessionPath(projectName);
4463
- if (!existsSync14(path)) return null;
4590
+ if (!existsSync15(path)) return null;
4464
4591
  try {
4465
- return JSON.parse(readFileSync9(path, "utf8"));
4592
+ return JSON.parse(readFileSync10(path, "utf8"));
4466
4593
  } catch {
4467
4594
  return null;
4468
4595
  }
4469
4596
  }
4470
4597
  function findLatestSession() {
4471
4598
  const dir = sessionsDir();
4472
- if (!existsSync14(dir)) return null;
4599
+ if (!existsSync15(dir)) return null;
4473
4600
  const files = readdirSync4(dir).filter((f) => f.endsWith(".json"));
4474
4601
  if (files.length === 0) return null;
4475
4602
  const sorted = files.map((f) => {
4476
- const fullPath = join15(dir, f);
4477
- const mtime = existsSync14(fullPath) ? statSync2(fullPath).mtimeMs : -1;
4603
+ const fullPath = join16(dir, f);
4604
+ const mtime = existsSync15(fullPath) ? statSync2(fullPath).mtimeMs : -1;
4478
4605
  return { f, mtime };
4479
4606
  }).sort((a, b) => b.mtime - a.mtime);
4480
4607
  try {
4481
- return JSON.parse(readFileSync9(join15(dir, sorted[0].f), "utf8"));
4608
+ return JSON.parse(readFileSync10(join16(dir, sorted[0].f), "utf8"));
4482
4609
  } catch {
4483
4610
  return null;
4484
4611
  }
4485
4612
  }
4486
4613
  function saveSession(session) {
4487
4614
  const dir = sessionsDir();
4488
- if (!existsSync14(dir)) mkdirSync4(dir, { recursive: true });
4615
+ if (!existsSync15(dir)) mkdirSync4(dir, { recursive: true });
4489
4616
  const name = session.config.project?.name ?? "unknown";
4490
4617
  const prior = loadSession(name);
4491
4618
  if (prior) {
@@ -4507,36 +4634,36 @@ function markStepComplete(session, step) {
4507
4634
  }
4508
4635
  function deleteSession(projectName) {
4509
4636
  const path = sessionPath(projectName);
4510
- if (existsSync14(path)) rmSync6(path);
4637
+ if (existsSync15(path)) rmSync6(path);
4511
4638
  }
4512
4639
  function projectsDir() {
4513
- return process.env["BIFFO_PROJECTS_DIR"] ?? join15(homedir(), ".biffo", "projects");
4640
+ return process.env["BIFFO_PROJECTS_DIR"] ?? join16(homedir(), ".biffo", "projects");
4514
4641
  }
4515
4642
  function saveProjectConfig(config) {
4516
4643
  const dir = projectsDir();
4517
- if (!existsSync14(dir)) mkdirSync4(dir, { recursive: true });
4518
- writeFileSync5(join15(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
4644
+ if (!existsSync15(dir)) mkdirSync4(dir, { recursive: true });
4645
+ writeFileSync5(join16(dir, `${config.project.name}.json`), JSON.stringify(config, null, 2));
4519
4646
  }
4520
4647
  function loadProjectConfig(name) {
4521
- const path = join15(projectsDir(), `${name}.json`);
4522
- if (!existsSync14(path)) return null;
4648
+ const path = join16(projectsDir(), `${name}.json`);
4649
+ if (!existsSync15(path)) return null;
4523
4650
  try {
4524
- const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(path, "utf8")));
4651
+ const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync10(path, "utf8")));
4525
4652
  return result.success ? result.data : null;
4526
4653
  } catch {
4527
4654
  return null;
4528
4655
  }
4529
4656
  }
4530
4657
  function deleteProjectConfig(name) {
4531
- const path = join15(projectsDir(), `${name}.json`);
4532
- if (existsSync14(path)) rmSync6(path);
4658
+ const path = join16(projectsDir(), `${name}.json`);
4659
+ if (existsSync15(path)) rmSync6(path);
4533
4660
  }
4534
4661
  function listProjectConfigs() {
4535
4662
  const dir = projectsDir();
4536
- if (!existsSync14(dir)) return [];
4663
+ if (!existsSync15(dir)) return [];
4537
4664
  return readdirSync4(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
4538
4665
  try {
4539
- const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync9(join15(dir, f), "utf8")));
4666
+ const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync10(join16(dir, f), "utf8")));
4540
4667
  return result.success ? [result.data] : [];
4541
4668
  } catch {
4542
4669
  return [];
@@ -4605,7 +4732,7 @@ async function runDataApply(name, environment, config, aws) {
4605
4732
  }
4606
4733
  async function resolveConfig(options) {
4607
4734
  if (options.config) {
4608
- const raw = JSON.parse(readFileSync10(resolve4(options.config), "utf8"));
4735
+ const raw = JSON.parse(readFileSync11(resolve4(options.config), "utf8"));
4609
4736
  const result = BiffoConfigSchema.safeParse(raw);
4610
4737
  if (!result.success) {
4611
4738
  log.error(`Invalid config at ${options.config}:`);
@@ -4625,8 +4752,8 @@ async function resolveConfig(options) {
4625
4752
  return cfg;
4626
4753
  }
4627
4754
  const localConfigPath = resolve4(process.cwd(), "biffo.config.json");
4628
- if (existsSync15(localConfigPath)) {
4629
- const raw = JSON.parse(readFileSync10(localConfigPath, "utf8"));
4755
+ if (existsSync16(localConfigPath)) {
4756
+ const raw = JSON.parse(readFileSync11(localConfigPath, "utf8"));
4630
4757
  const result = BiffoConfigSchema.safeParse(raw);
4631
4758
  if (result.success) return result.data;
4632
4759
  if (isTemplatePlaceholderConfig(raw)) {
@@ -4672,8 +4799,8 @@ async function resolveConfig(options) {
4672
4799
 
4673
4800
  // src/commands/data-import.ts
4674
4801
  import { execSync as execSync3 } from "child_process";
4675
- import { cpSync, existsSync as existsSync16, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
4676
- import { join as join16, resolve as resolve5 } from "path";
4802
+ import { cpSync, existsSync as existsSync17, mkdirSync as mkdirSync5, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
4803
+ import { join as join17, resolve as resolve5 } from "path";
4677
4804
  import chalk6 from "chalk";
4678
4805
  import { Command as Command6 } from "commander";
4679
4806
  import inquirer2 from "inquirer";
@@ -4713,23 +4840,23 @@ async function runDataImport(name, options, deps) {
4713
4840
  `Invalid import name '${name}'. Use lowercase letters, numbers, and hyphens, starting with a letter.`
4714
4841
  );
4715
4842
  }
4716
- const servicesDir = join16(options.cwd, "services");
4717
- if (!existsSync16(servicesDir)) {
4843
+ const servicesDir = join17(options.cwd, "services");
4844
+ if (!existsSync17(servicesDir)) {
4718
4845
  throw new Error(
4719
4846
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
4720
4847
  );
4721
4848
  }
4722
- const targetDir = join16(options.cwd, "db", "imports", name);
4723
- if (existsSync16(targetDir)) {
4849
+ const targetDir = join17(options.cwd, "db", "imports", name);
4850
+ if (existsSync17(targetDir)) {
4724
4851
  throw new Error(
4725
4852
  `DDL import '${name}' is already present at db/imports/${name}/. Remove it first to re-import.`
4726
4853
  );
4727
4854
  }
4728
- const isLocalDir = existsSync16(options.source) && statSync3(options.source).isDirectory();
4855
+ const isLocalDir = existsSync17(options.source) && statSync3(options.source).isDirectory();
4729
4856
  let sourceDir;
4730
4857
  let cleanupClone = null;
4731
4858
  if (isLocalDir) {
4732
- sourceDir = options.path ? join16(options.source, options.path) : options.source;
4859
+ sourceDir = options.path ? join17(options.source, options.path) : options.source;
4733
4860
  } else {
4734
4861
  const token = options.token ?? await resolveDdlImportToken();
4735
4862
  log.info(`Cloning ${options.source}...`);
@@ -4737,10 +4864,10 @@ async function runDataImport(name, options, deps) {
4737
4864
  cleanupClone = () => {
4738
4865
  deps.git.cleanup(tmpDir);
4739
4866
  };
4740
- sourceDir = options.path ? join16(tmpDir, options.path) : tmpDir;
4867
+ sourceDir = options.path ? join17(tmpDir, options.path) : tmpDir;
4741
4868
  }
4742
4869
  try {
4743
- if (!existsSync16(sourceDir)) {
4870
+ if (!existsSync17(sourceDir)) {
4744
4871
  throw new Error(`Source directory does not exist: ${sourceDir}`);
4745
4872
  }
4746
4873
  const sqlFiles = readdirSync5(sourceDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name).sort();
@@ -4765,7 +4892,7 @@ async function runDataImport(name, options, deps) {
4765
4892
  }
4766
4893
  mkdirSync5(targetDir, { recursive: true });
4767
4894
  for (const file of sqlFiles) {
4768
- cpSync(join16(sourceDir, file), join16(targetDir, file));
4895
+ cpSync(join17(sourceDir, file), join17(targetDir, file));
4769
4896
  }
4770
4897
  log.success(`Imported ${String(sqlFiles.length)} .sql file(s) to db/imports/${name}/`);
4771
4898
  const commitMessage = `feat(data): import ${name} (${String(sqlFiles.length)} SQL file(s))`;
@@ -4817,8 +4944,8 @@ function printDryRun(name, sqlFiles) {
4817
4944
  }
4818
4945
 
4819
4946
  // src/commands/data-list.ts
4820
- import { existsSync as existsSync17, readdirSync as readdirSync6 } from "fs";
4821
- import { join as join17, resolve as resolve6 } from "path";
4947
+ import { existsSync as existsSync18, readdirSync as readdirSync6 } from "fs";
4948
+ import { join as join18, resolve as resolve6 } from "path";
4822
4949
  import chalk7 from "chalk";
4823
4950
  import { Command as Command7 } from "commander";
4824
4951
  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) => {
@@ -4831,15 +4958,15 @@ var dataListCommand = new Command7("list").description("List DDL imports vendore
4831
4958
  }
4832
4959
  });
4833
4960
  async function runDataList(options) {
4834
- const importsDir = join17(options.cwd, "db", "imports");
4835
- if (!existsSync17(importsDir)) {
4961
+ const importsDir = join18(options.cwd, "db", "imports");
4962
+ if (!existsSync18(importsDir)) {
4836
4963
  console.log(chalk7.dim("\n No DDL imports in this checkout.\n"));
4837
4964
  return;
4838
4965
  }
4839
4966
  const candidates = readdirSync6(importsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
4840
4967
  const imports = [];
4841
4968
  for (const name of candidates) {
4842
- const fileCount = readdirSync6(join17(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
4969
+ const fileCount = readdirSync6(join18(importsDir, name)).filter((f) => f.endsWith(".sql")).length;
4843
4970
  if (fileCount > 0) imports.push({ name, fileCount });
4844
4971
  }
4845
4972
  if (imports.length === 0) {
@@ -4869,7 +4996,7 @@ dataCommand.addCommand(dataListCommand);
4869
4996
 
4870
4997
  // src/commands/deploy.ts
4871
4998
  import { execSync as execSync4 } from "child_process";
4872
- import { existsSync as existsSync18, readFileSync as readFileSync11 } from "fs";
4999
+ import { existsSync as existsSync19, readFileSync as readFileSync12 } from "fs";
4873
5000
  import { resolve as resolve7 } from "path";
4874
5001
  import chalk8 from "chalk";
4875
5002
  import { Command as Command9 } from "commander";
@@ -5226,7 +5353,7 @@ var deployCommand = new Command9("deploy").description("Deploy infrastructure an
5226
5353
  );
5227
5354
  async function resolveConfig2(options) {
5228
5355
  if (options.config) {
5229
- const raw = JSON.parse(readFileSync11(resolve7(options.config), "utf8"));
5356
+ const raw = JSON.parse(readFileSync12(resolve7(options.config), "utf8"));
5230
5357
  const result = BiffoConfigSchema.safeParse(raw);
5231
5358
  if (!result.success) {
5232
5359
  log.error(`Invalid config at ${options.config}:`);
@@ -5246,8 +5373,8 @@ async function resolveConfig2(options) {
5246
5373
  return cfg;
5247
5374
  }
5248
5375
  const localConfigPath = resolve7(process.cwd(), "biffo.config.json");
5249
- if (existsSync18(localConfigPath)) {
5250
- const raw = JSON.parse(readFileSync11(localConfigPath, "utf8"));
5376
+ if (existsSync19(localConfigPath)) {
5377
+ const raw = JSON.parse(readFileSync12(localConfigPath, "utf8"));
5251
5378
  const result = BiffoConfigSchema.safeParse(raw);
5252
5379
  if (result.success) return result.data;
5253
5380
  if (isTemplatePlaceholderConfig(raw)) {
@@ -5643,7 +5770,7 @@ function resolveGithubToken() {
5643
5770
 
5644
5771
  // src/commands/destroy.ts
5645
5772
  import { execSync as execSync5 } from "child_process";
5646
- import { readFileSync as readFileSync12 } from "fs";
5773
+ import { readFileSync as readFileSync13 } from "fs";
5647
5774
  import { resolve as resolve8 } from "path";
5648
5775
  import chalk9 from "chalk";
5649
5776
  import { Command as Command10 } from "commander";
@@ -5733,7 +5860,7 @@ var destroyCommand = new Command10("destroy").description("Destroy infrastructur
5733
5860
  });
5734
5861
  async function resolveConfig3(options) {
5735
5862
  if (options.config) {
5736
- const raw = JSON.parse(readFileSync12(resolve8(options.config), "utf8"));
5863
+ const raw = JSON.parse(readFileSync13(resolve8(options.config), "utf8"));
5737
5864
  const result = BiffoConfigSchema.safeParse(raw);
5738
5865
  if (!result.success) {
5739
5866
  log.error(`Invalid config at ${options.config}:`);
@@ -5753,7 +5880,7 @@ async function resolveConfig3(options) {
5753
5880
  return cfg;
5754
5881
  }
5755
5882
  try {
5756
- const raw = JSON.parse(readFileSync12(resolve8(process.cwd(), "biffo.config.json"), "utf8"));
5883
+ const raw = JSON.parse(readFileSync13(resolve8(process.cwd(), "biffo.config.json"), "utf8"));
5757
5884
  const result = BiffoConfigSchema.safeParse(raw);
5758
5885
  if (result.success) return result.data;
5759
5886
  } catch {
@@ -5803,15 +5930,15 @@ function resolveGithubToken2() {
5803
5930
  }
5804
5931
 
5805
5932
  // src/commands/init.ts
5806
- import { readFileSync as readFileSync16 } from "fs";
5933
+ import { readFileSync as readFileSync17 } from "fs";
5807
5934
  import { resolve as resolve10 } from "path";
5808
5935
  import chalk12 from "chalk";
5809
5936
  import { Command as Command12 } from "commander";
5810
5937
  import inquirer5 from "inquirer";
5811
5938
 
5812
5939
  // src/lib/build-freshness.ts
5813
- import { existsSync as existsSync19, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
5814
- import { dirname as dirname5, join as join18, relative as relative2, sep as sep2 } from "path";
5940
+ import { existsSync as existsSync20, readdirSync as readdirSync7, statSync as statSync4 } from "fs";
5941
+ import { dirname as dirname5, join as join19, relative as relative2, sep as sep2 } from "path";
5815
5942
  import { fileURLToPath as fileURLToPath3 } from "url";
5816
5943
  var SKIP_ENV_VAR = "BIFFO_SKIP_BUILD_FRESHNESS_CHECK";
5817
5944
  function checkBuildFreshness(options = {}) {
@@ -5825,7 +5952,7 @@ function checkBuildFreshness(options = {}) {
5825
5952
  if (!packageRoot) {
5826
5953
  return { status: "skipped", reason: `no package.json above ${moduleDir}`, newerSources: [] };
5827
5954
  }
5828
- const distDir = join18(packageRoot, "dist");
5955
+ const distDir = join19(packageRoot, "dist");
5829
5956
  if (!isInside(distDir, moduleDir)) {
5830
5957
  return {
5831
5958
  status: "skipped",
@@ -5833,16 +5960,16 @@ function checkBuildFreshness(options = {}) {
5833
5960
  newerSources: []
5834
5961
  };
5835
5962
  }
5836
- const srcDir = join18(packageRoot, "src");
5837
- if (!existsSync19(srcDir)) {
5963
+ const srcDir = join19(packageRoot, "src");
5964
+ if (!existsSync20(srcDir)) {
5838
5965
  return {
5839
5966
  status: "skipped",
5840
5967
  reason: "no src/ alongside dist/ \u2014 this is a shipped package",
5841
5968
  newerSources: []
5842
5969
  };
5843
5970
  }
5844
- const entry = join18(distDir, "index.js");
5845
- if (!existsSync19(entry)) {
5971
+ const entry = join19(distDir, "index.js");
5972
+ if (!existsSync20(entry)) {
5846
5973
  return { status: "skipped", reason: `${entry} not found`, newerSources: [] };
5847
5974
  }
5848
5975
  const builtAt = statSync4(entry).mtimeMs;
@@ -5886,7 +6013,7 @@ function collectSourceFiles(srcDir) {
5886
6013
  const found = [];
5887
6014
  const walk = (dir) => {
5888
6015
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
5889
- const full = join18(dir, entry.name);
6016
+ const full = join19(dir, entry.name);
5890
6017
  if (entry.isDirectory()) {
5891
6018
  if (entry.name === "node_modules") continue;
5892
6019
  walk(full);
@@ -5905,7 +6032,7 @@ function collectSourceFiles(srcDir) {
5905
6032
  function findPackageRoot(from) {
5906
6033
  let dir = from;
5907
6034
  for (; ; ) {
5908
- if (existsSync19(join18(dir, "package.json"))) return dir;
6035
+ if (existsSync20(join19(dir, "package.json"))) return dir;
5909
6036
  const parent = dirname5(dir);
5910
6037
  if (parent === dir) return null;
5911
6038
  dir = parent;
@@ -5919,9 +6046,9 @@ function isInside(parent, child) {
5919
6046
 
5920
6047
  // src/lib/credentials.ts
5921
6048
  import { execSync as execSync6 } from "child_process";
5922
- import { existsSync as existsSync20, readFileSync as readFileSync13 } from "fs";
6049
+ import { existsSync as existsSync21, readFileSync as readFileSync14 } from "fs";
5923
6050
  import { homedir as homedir2 } from "os";
5924
- import { join as join19 } from "path";
6051
+ import { join as join20 } from "path";
5925
6052
  import { GetCallerIdentityCommand as GetCallerIdentityCommand2, STSClient as STSClient2 } from "@aws-sdk/client-sts";
5926
6053
  import chalk10 from "chalk";
5927
6054
  import inquirer4 from "inquirer";
@@ -6100,11 +6227,11 @@ async function verifySelectedAwsCredentials(profile, region) {
6100
6227
  return sts.send(new GetCallerIdentityCommand2({}));
6101
6228
  }
6102
6229
  function discoverAwsProfiles() {
6103
- const files = [join19(homedir2(), ".aws", "credentials"), join19(homedir2(), ".aws", "config")];
6230
+ const files = [join20(homedir2(), ".aws", "credentials"), join20(homedir2(), ".aws", "config")];
6104
6231
  const profiles = /* @__PURE__ */ new Set();
6105
6232
  for (const file of files) {
6106
- if (!existsSync20(file)) continue;
6107
- const content = readFileSync13(file, "utf8");
6233
+ if (!existsSync21(file)) continue;
6234
+ const content = readFileSync14(file, "utf8");
6108
6235
  for (const match of content.matchAll(/^\s*\[([^\]]+)\]\s*$/gm)) {
6109
6236
  const section = match[1]?.trim();
6110
6237
  if (!section) continue;
@@ -6194,34 +6321,34 @@ var SiblingConfigSchema = z6.object({
6194
6321
 
6195
6322
  // src/lib/sibling-session.ts
6196
6323
  import {
6197
- existsSync as existsSync21,
6324
+ existsSync as existsSync22,
6198
6325
  mkdirSync as mkdirSync6,
6199
6326
  readdirSync as readdirSync8,
6200
- readFileSync as readFileSync14,
6327
+ readFileSync as readFileSync15,
6201
6328
  rmSync as rmSync7,
6202
6329
  statSync as statSync5,
6203
6330
  writeFileSync as writeFileSync6
6204
6331
  } from "fs";
6205
6332
  import { homedir as homedir3 } from "os";
6206
- import { join as join20 } from "path";
6333
+ import { join as join21 } from "path";
6207
6334
  function sessionsDir2() {
6208
- return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join20(homedir3(), ".biffo", "sibling-sessions");
6335
+ return process.env["BIFFO_SIBLING_SESSIONS_DIR"] ?? join21(homedir3(), ".biffo", "sibling-sessions");
6209
6336
  }
6210
6337
  function sessionPath2(projectName) {
6211
- return join20(sessionsDir2(), `${projectName}.json`);
6338
+ return join21(sessionsDir2(), `${projectName}.json`);
6212
6339
  }
6213
6340
  function loadSiblingSession(projectName) {
6214
6341
  const path = sessionPath2(projectName);
6215
- if (!existsSync21(path)) return null;
6342
+ if (!existsSync22(path)) return null;
6216
6343
  try {
6217
- return JSON.parse(readFileSync14(path, "utf8"));
6344
+ return JSON.parse(readFileSync15(path, "utf8"));
6218
6345
  } catch {
6219
6346
  return null;
6220
6347
  }
6221
6348
  }
6222
6349
  function saveSiblingSession(session) {
6223
6350
  const dir = sessionsDir2();
6224
- if (!existsSync21(dir)) mkdirSync6(dir, { recursive: true });
6351
+ if (!existsSync22(dir)) mkdirSync6(dir, { recursive: true });
6225
6352
  const name = session.config.project?.name ?? "unknown";
6226
6353
  const prior = loadSiblingSession(name);
6227
6354
  if (prior) {
@@ -6243,30 +6370,30 @@ function markSiblingStepComplete(session, step) {
6243
6370
  }
6244
6371
  function deleteSiblingSession(projectName) {
6245
6372
  const path = sessionPath2(projectName);
6246
- if (existsSync21(path)) rmSync7(path);
6373
+ if (existsSync22(path)) rmSync7(path);
6247
6374
  }
6248
6375
 
6249
6376
  // src/commands/sibling-create.ts
6250
- import { cpSync as cpSync2, existsSync as existsSync22, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
6377
+ import { cpSync as cpSync2, existsSync as existsSync23, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
6251
6378
  import { tmpdir as tmpdir4 } from "os";
6252
- import { dirname as dirname6, join as join22, resolve as resolve9 } from "path";
6379
+ import { dirname as dirname6, join as join23, resolve as resolve9 } from "path";
6253
6380
  import { fileURLToPath as fileURLToPath4 } from "url";
6254
6381
  import chalk11 from "chalk";
6255
6382
  import { Command as Command11 } from "commander";
6256
6383
 
6257
6384
  // src/lib/skeleton-dotfiles.ts
6258
6385
  import { readdirSync as readdirSync9, renameSync } from "fs";
6259
- import { join as join21 } from "path";
6386
+ import { join as join22 } from "path";
6260
6387
  var PACKAGED_GITIGNORE = "_gitignore";
6261
6388
  var REAL_GITIGNORE = ".gitignore";
6262
6389
  function restorePackagedDotfiles(dir) {
6263
6390
  const restored = [];
6264
6391
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
6265
- const full = join21(dir, entry.name);
6392
+ const full = join22(dir, entry.name);
6266
6393
  if (entry.isDirectory()) {
6267
6394
  restored.push(...restorePackagedDotfiles(full));
6268
6395
  } else if (entry.name === PACKAGED_GITIGNORE) {
6269
- const target = join21(dir, REAL_GITIGNORE);
6396
+ const target = join22(dir, REAL_GITIGNORE);
6270
6397
  renameSync(full, target);
6271
6398
  restored.push(target);
6272
6399
  }
@@ -6311,7 +6438,7 @@ async function runSiblingCreateCommand(name, options) {
6311
6438
  printDryRun2(config, coreConfig, options.templateRoot);
6312
6439
  return;
6313
6440
  }
6314
- if (!existsSync22(options.templateRoot)) {
6441
+ if (!existsSync23(options.templateRoot)) {
6315
6442
  throw new Error(`Sibling template not found at ${options.templateRoot}`);
6316
6443
  }
6317
6444
  let session = null;
@@ -6528,7 +6655,7 @@ function assertPathPrefixIsAllowed(pathPrefix) {
6528
6655
  }
6529
6656
  }
6530
6657
  function readSiblingConfig(path, root = false) {
6531
- const raw = JSON.parse(readFileSync15(path, "utf8"));
6658
+ const raw = JSON.parse(readFileSync16(path, "utf8"));
6532
6659
  const withDefaults = raw && typeof raw === "object" && "project" in raw && "core" in raw ? {
6533
6660
  ...raw,
6534
6661
  core: {
@@ -6562,7 +6689,7 @@ function resolveCoreConfig(config, configPath) {
6562
6689
  throw new Error("Either core.project_name or core.config_path is required.");
6563
6690
  }
6564
6691
  function parseCoreConfig(path) {
6565
- const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync15(path, "utf8")));
6692
+ const result = BiffoConfigSchema.safeParse(JSON.parse(readFileSync16(path, "utf8")));
6566
6693
  if (!result.success) {
6567
6694
  throw new Error(
6568
6695
  `Invalid core configuration at ${path}:
@@ -6601,7 +6728,7 @@ async function resolveCoreIdentity(coreAws, coreConfig, environments) {
6601
6728
  return coreIdentity;
6602
6729
  }
6603
6730
  async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, githubToken) {
6604
- const workDir = mkdtempSync4(join22(tmpdir4(), `biffo-sibling-${config.project.name}-`));
6731
+ const workDir = mkdtempSync4(join23(tmpdir4(), `biffo-sibling-${config.project.name}-`));
6605
6732
  try {
6606
6733
  writeSiblingTemplate(skeletonRoot, workDir, config, {
6607
6734
  coreProjectName: coreConfig.project.name,
@@ -6626,13 +6753,13 @@ async function pushSkeleton(git, skeletonRoot, cloneUrl, config, coreConfig, git
6626
6753
  }
6627
6754
  }
6628
6755
  function writeSiblingTemplate(templateRoot, targetDir, config, context) {
6629
- if (!existsSync22(templateRoot)) {
6756
+ if (!existsSync23(templateRoot)) {
6630
6757
  throw new Error(`Sibling template not found at ${templateRoot}`);
6631
6758
  }
6632
6759
  cpSync2(templateRoot, targetDir, { recursive: true });
6633
6760
  restorePackagedDotfiles(targetDir);
6634
6761
  writeFileSync7(
6635
- join22(targetDir, "biffo.sibling.json"),
6762
+ join23(targetDir, "biffo.sibling.json"),
6636
6763
  JSON.stringify(
6637
6764
  {
6638
6765
  name: config.project.name,
@@ -6651,14 +6778,14 @@ function writeSiblingTemplate(templateRoot, targetDir, config, context) {
6651
6778
  ) + "\n"
6652
6779
  );
6653
6780
  writeFileSync7(
6654
- join22(targetDir, ".biffo-shared-version"),
6781
+ join23(targetDir, ".biffo-shared-version"),
6655
6782
  `core-v${context.templateVersion.replace(/^core-v/, "")}
6656
6783
  `
6657
6784
  );
6658
- const envPath = join22(targetDir, "apps", "frontend", ".env.example");
6785
+ const envPath = join23(targetDir, "apps", "frontend", ".env.example");
6659
6786
  try {
6660
6787
  const path = basePathFor(context.pathPrefix);
6661
- 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}`);
6788
+ const content = readFileSync16(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}`);
6662
6789
  writeFileSync7(envPath, content);
6663
6790
  } catch (err) {
6664
6791
  if (err.code !== "ENOENT") throw err;
@@ -6705,7 +6832,7 @@ async function configureSiblingGithub(github, config, coreConfig, session, coreI
6705
6832
  }
6706
6833
  function readExistingSiblingOrigins(filePath) {
6707
6834
  try {
6708
- return JSON.parse(readFileSync15(filePath, "utf8"));
6835
+ return JSON.parse(readFileSync16(filePath, "utf8"));
6709
6836
  } catch (err) {
6710
6837
  if (err.code === "ENOENT") return {};
6711
6838
  throw err;
@@ -6724,10 +6851,10 @@ function assertGitIdentity(identity) {
6724
6851
  );
6725
6852
  }
6726
6853
  function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x") {
6727
- const cdnVarsPath = join22(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
6854
+ const cdnVarsPath = join23(cloneDir, "modules", "cloud", "aws", "cdn", "variables.tf");
6728
6855
  let declaresSiblingOrigins = false;
6729
6856
  try {
6730
- declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync15(cdnVarsPath, "utf8"));
6857
+ declaresSiblingOrigins = /variable\s+"sibling_origins"/.test(readFileSync16(cdnVarsPath, "utf8"));
6731
6858
  } catch {
6732
6859
  declaresSiblingOrigins = false;
6733
6860
  }
@@ -6737,10 +6864,10 @@ function assertCoreSupportsSiblingRouting(cloneDir, coreRepo, pathPrefix = "x")
6737
6864
  );
6738
6865
  }
6739
6866
  if (!isRootPathPrefix(pathPrefix)) return;
6740
- const cdnMainPath = join22(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
6867
+ const cdnMainPath = join23(cloneDir, "modules", "cloud", "aws", "cdn", "main.tf");
6741
6868
  let supportsRoot = false;
6742
6869
  try {
6743
- supportsRoot = /root_sibling_registered/.test(readFileSync15(cdnMainPath, "utf8"));
6870
+ supportsRoot = /root_sibling_registered/.test(readFileSync16(cdnMainPath, "utf8"));
6744
6871
  } catch {
6745
6872
  supportsRoot = false;
6746
6873
  }
@@ -6770,8 +6897,8 @@ async function registerWithCore(git, github, config, coreConfig, pathPrefix, git
6770
6897
  for (const env of config.environments) {
6771
6898
  const bucketName = siteBucketName(config.project.name, env, siblingAccountId);
6772
6899
  const domain = bucketRegionalDomain(bucketName, coreAwsRegion);
6773
- const relativePath = join22("infra", "environments", env, "siblings.auto.tfvars.json");
6774
- const filePath = join22(cloneDir, relativePath);
6900
+ const relativePath = join23("infra", "environments", env, "siblings.auto.tfvars.json");
6901
+ const filePath = join23(cloneDir, relativePath);
6775
6902
  const existing = readExistingSiblingOrigins(filePath);
6776
6903
  const siblings = upsertSiblingOrigin(existing.sibling_origins ?? [], {
6777
6904
  name,
@@ -6851,8 +6978,8 @@ function defaultSiblingTemplateRoot() {
6851
6978
  const start = dirname6(fileURLToPath4(import.meta.url));
6852
6979
  let dir = start;
6853
6980
  for (; ; ) {
6854
- const candidate = join22(dir, "_skeletons", "sibling-template");
6855
- if (existsSync22(candidate)) return candidate;
6981
+ const candidate = join23(dir, "_skeletons", "sibling-template");
6982
+ if (existsSync23(candidate)) return candidate;
6856
6983
  const parent = dirname6(dir);
6857
6984
  if (parent === dir) break;
6858
6985
  dir = parent;
@@ -6876,7 +7003,7 @@ var initCommand = new Command12("init").description("Scaffold a new project from
6876
7003
  let config;
6877
7004
  let githubToken;
6878
7005
  if (options.config) {
6879
- const rawConfig = JSON.parse(readFileSync16(resolve10(options.config), "utf8"));
7006
+ const rawConfig = JSON.parse(readFileSync17(resolve10(options.config), "utf8"));
6880
7007
  config = parseConfig(rawConfig);
6881
7008
  const { account_id: accountId, region } = config.cloud.config;
6882
7009
  session = resolveConfigFileSession(config, accountId, region, options.fresh === true);
@@ -7321,8 +7448,8 @@ async function promptForConfig(awsAccountId, awsRegion, awsProfile) {
7321
7448
  import { Command as Command20 } from "commander";
7322
7449
 
7323
7450
  // src/commands/plugin-create.ts
7324
- import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
7325
- import { dirname as dirname8, join as join25, resolve as resolve11 } from "path";
7451
+ import { existsSync as existsSync26, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
7452
+ import { dirname as dirname8, join as join26, resolve as resolve11 } from "path";
7326
7453
  import { fileURLToPath as fileURLToPath5 } from "url";
7327
7454
  import chalk13 from "chalk";
7328
7455
  import { Command as Command13 } from "commander";
@@ -7386,21 +7513,21 @@ function workflowCheckContexts(workflow) {
7386
7513
  }
7387
7514
 
7388
7515
  // src/lib/plugin-locations.ts
7389
- import { existsSync as existsSync23, readdirSync as readdirSync10 } from "fs";
7390
- import { join as join23 } from "path";
7516
+ import { existsSync as existsSync24, readdirSync as readdirSync10 } from "fs";
7517
+ import { join as join24 } from "path";
7391
7518
  var FIRST_PARTY_PLUGINS_DIR = "_plugins";
7392
7519
  var PLUGIN_MANIFEST_FILE = "biffo.plugin.json";
7393
7520
  function pluginDir(name, channel) {
7394
7521
  return channel === "first-party" ? `services/${FIRST_PARTY_PLUGINS_DIR}/${name}` : `services/${name}`;
7395
7522
  }
7396
7523
  function scanDir(absDir, relDir, channel) {
7397
- if (!existsSync23(absDir)) return [];
7524
+ if (!existsSync24(absDir)) return [];
7398
7525
  const found = [];
7399
7526
  for (const entry of readdirSync10(absDir, { withFileTypes: true })) {
7400
7527
  if (!entry.isDirectory()) continue;
7401
7528
  if (channel === "third-party" && entry.name === FIRST_PARTY_PLUGINS_DIR) continue;
7402
- const manifestPath = join23(absDir, entry.name, PLUGIN_MANIFEST_FILE);
7403
- if (!existsSync23(manifestPath)) continue;
7529
+ const manifestPath = join24(absDir, entry.name, PLUGIN_MANIFEST_FILE);
7530
+ if (!existsSync24(manifestPath)) continue;
7404
7531
  found.push({
7405
7532
  dirName: entry.name,
7406
7533
  relDir: `${relDir}/${entry.name}`,
@@ -7411,11 +7538,11 @@ function scanDir(absDir, relDir, channel) {
7411
7538
  return found;
7412
7539
  }
7413
7540
  function findInstalledPlugins(cwd) {
7414
- const servicesDir = join23(cwd, "services");
7541
+ const servicesDir = join24(cwd, "services");
7415
7542
  return [
7416
7543
  ...scanDir(servicesDir, "services", "third-party"),
7417
7544
  ...scanDir(
7418
- join23(servicesDir, FIRST_PARTY_PLUGINS_DIR),
7545
+ join24(servicesDir, FIRST_PARTY_PLUGINS_DIR),
7419
7546
  `services/${FIRST_PARTY_PLUGINS_DIR}`,
7420
7547
  "first-party"
7421
7548
  )
@@ -7618,13 +7745,13 @@ function validateManifest(raw) {
7618
7745
  // src/lib/plugin-scaffold.ts
7619
7746
  import {
7620
7747
  copyFileSync,
7621
- existsSync as existsSync24,
7748
+ existsSync as existsSync25,
7622
7749
  mkdirSync as mkdirSync8,
7623
- readFileSync as readFileSync17,
7750
+ readFileSync as readFileSync18,
7624
7751
  readdirSync as readdirSync11,
7625
7752
  writeFileSync as writeFileSync8
7626
7753
  } from "fs";
7627
- import { dirname as dirname7, join as join24 } from "path";
7754
+ import { dirname as dirname7, join as join25 } from "path";
7628
7755
  var STANDALONE_ONLY_ENTRIES = {
7629
7756
  ".github": "standalone-repo CI/release workflows \u2014 the host monorepo already runs lint/type/test/security over services/",
7630
7757
  "registry-schema.json": "the plugin-registry publishing schema, used when submitting a *published* plugin to the registry repo, not by an in-tree plugin"
@@ -7678,10 +7805,10 @@ function applySubstitutions(text, names) {
7678
7805
  var BINARY_EXTENSIONS = /\.(png|jpe?g|gif|ico|woff2?|ttf|zip|gz)$/i;
7679
7806
  function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7680
7807
  const layout = options.layout ?? "in-tree";
7681
- if (!existsSync24(skeletonRoot)) {
7808
+ if (!existsSync25(skeletonRoot)) {
7682
7809
  throw new Error(`Plugin skeleton not found at ${skeletonRoot}`);
7683
7810
  }
7684
- if (!existsSync24(join24(skeletonRoot, "terraform"))) {
7811
+ if (!existsSync25(join25(skeletonRoot, "terraform"))) {
7685
7812
  throw new Error(
7686
7813
  `Plugin skeleton at ${skeletonRoot} has no terraform/ directory. Refusing to scaffold a plugin that cannot receive events (issue #194) \u2014 the skeleton is broken.`
7687
7814
  );
@@ -7689,7 +7816,7 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7689
7816
  const skipped = [];
7690
7817
  const files = [];
7691
7818
  const walk = (relDir) => {
7692
- const absDir = join24(skeletonRoot, relDir);
7819
+ const absDir = join25(skeletonRoot, relDir);
7693
7820
  for (const entry of readdirSync11(absDir, { withFileTypes: true }).sort(
7694
7821
  (a, b) => a.name.localeCompare(b.name)
7695
7822
  )) {
@@ -7704,14 +7831,14 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7704
7831
  continue;
7705
7832
  }
7706
7833
  const destRel = applySubstitutions(relPath, names);
7707
- const destPath = join24(destDir, destRel);
7834
+ const destPath = join25(destDir, destRel);
7708
7835
  mkdirSync8(dirname7(destPath), { recursive: true });
7709
7836
  if (BINARY_EXTENSIONS.test(entry.name)) {
7710
- copyFileSync(join24(skeletonRoot, relPath), destPath);
7837
+ copyFileSync(join25(skeletonRoot, relPath), destPath);
7711
7838
  } else {
7712
7839
  writeFileSync8(
7713
7840
  destPath,
7714
- applySubstitutions(readFileSync17(join24(skeletonRoot, relPath), "utf8"), names)
7841
+ applySubstitutions(readFileSync18(join25(skeletonRoot, relPath), "utf8"), names)
7715
7842
  );
7716
7843
  }
7717
7844
  files.push(destRel);
@@ -7728,8 +7855,8 @@ function scaffoldPlugin(skeletonRoot, destDir, names, options = {}) {
7728
7855
  function findSkeletonRoot(startDir, skeleton) {
7729
7856
  let dir = startDir;
7730
7857
  for (; ; ) {
7731
- const candidate = join24(dir, "_skeletons", skeleton);
7732
- if (existsSync24(candidate)) return candidate;
7858
+ const candidate = join25(dir, "_skeletons", skeleton);
7859
+ if (existsSync25(candidate)) return candidate;
7733
7860
  const parent = dirname7(dir);
7734
7861
  if (parent === dir) return null;
7735
7862
  dir = parent;
@@ -7797,7 +7924,7 @@ async function runPluginCreate(name, options, deps) {
7797
7924
  reportBranchProtectionSummary();
7798
7925
  return;
7799
7926
  }
7800
- const isInstance = existsSync25(join25(options.cwd, INSTANCE_CORE_FILE));
7927
+ const isInstance = existsSync26(join26(options.cwd, INSTANCE_CORE_FILE));
7801
7928
  if (options.firstParty && isInstance) {
7802
7929
  throw new Error(
7803
7930
  `--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.`
@@ -7805,19 +7932,19 @@ async function runPluginCreate(name, options, deps) {
7805
7932
  }
7806
7933
  const channel = options.firstParty ? "first-party" : "third-party";
7807
7934
  const relDir = pluginDir(names.slug, channel);
7808
- const destDir = join25(options.cwd, relDir);
7809
- const servicesDir = join25(options.cwd, "services");
7810
- if (!existsSync25(servicesDir)) {
7935
+ const destDir = join26(options.cwd, relDir);
7936
+ const servicesDir = join26(options.cwd, "services");
7937
+ if (!existsSync26(servicesDir)) {
7811
7938
  throw new Error(
7812
7939
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
7813
7940
  );
7814
7941
  }
7815
- if (existsSync25(destDir)) {
7942
+ if (existsSync26(destDir)) {
7816
7943
  throw new Error(`${relDir}/ already exists. Choose a different name, or remove it first.`);
7817
7944
  }
7818
7945
  const here = dirname8(fileURLToPath5(import.meta.url));
7819
- const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join25(options.cwd, "_skeletons", "plugin-template");
7820
- if (!existsSync25(skeletonRoot)) {
7946
+ const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join26(options.cwd, "_skeletons", "plugin-template");
7947
+ if (!existsSync26(skeletonRoot)) {
7821
7948
  throw new Error(
7822
7949
  `Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
7823
7950
  );
@@ -7832,8 +7959,8 @@ async function runPluginCreate(name, options, deps) {
7832
7959
  for (const { entry, reason } of skipped) {
7833
7960
  log.info(`Skipped ${entry} \u2014 ${reason}`);
7834
7961
  }
7835
- const manifestPath = join25(destDir, "biffo.plugin.json");
7836
- const manifest = validateManifest(JSON.parse(readFileSync18(manifestPath, "utf8")));
7962
+ const manifestPath = join26(destDir, "biffo.plugin.json");
7963
+ const manifest = validateManifest(JSON.parse(readFileSync19(manifestPath, "utf8")));
7837
7964
  if (manifest.name !== names.slug) {
7838
7965
  throw new Error(
7839
7966
  `Scaffolded manifest declares name '${manifest.name}', expected '${names.slug}'. The skeleton's manifest name may have diverged from 'example-plugin'.`
@@ -7856,8 +7983,8 @@ async function runPluginCreate(name, options, deps) {
7856
7983
  printNextSteps(names, relDir, channel);
7857
7984
  }
7858
7985
  async function runStandaloneCreate(names, options, deps) {
7859
- const destDir = join25(options.cwd, names.dist);
7860
- if (existsSync25(destDir)) {
7986
+ const destDir = join26(options.cwd, names.dist);
7987
+ if (existsSync26(destDir)) {
7861
7988
  throw new Error(`${names.dist}/ already exists. Choose a different name, or remove it first.`);
7862
7989
  }
7863
7990
  const skeletonRoot = resolveSkeletonRoot(options);
@@ -7875,7 +8002,7 @@ async function runStandaloneCreate(names, options, deps) {
7875
8002
  restorePackagedDotfiles(destDir);
7876
8003
  log.success(`Scaffolded ${String(files.length)} file(s) into ${names.dist}/`);
7877
8004
  const manifest = validateManifest(
7878
- JSON.parse(readFileSync18(join25(destDir, "biffo.plugin.json"), "utf8"))
8005
+ JSON.parse(readFileSync19(join26(destDir, "biffo.plugin.json"), "utf8"))
7879
8006
  );
7880
8007
  if (manifest.name !== names.slug) {
7881
8008
  throw new Error(
@@ -7914,8 +8041,8 @@ async function createAndPushStandaloneRepo(org, names, destDir, options, deps) {
7914
8041
  await deps.git.push(destDir, "dev", { token });
7915
8042
  log.success(`Pushed dev to ${org}/${names.dist}`);
7916
8043
  await github.setDefaultBranch(org, names.dist, "dev");
7917
- const ciPath = join25(destDir, ".github", "workflows", "ci.yml");
7918
- const contexts = existsSync25(ciPath) ? workflowCheckContexts(readFileSync18(ciPath, "utf8")) : [];
8044
+ const ciPath = join26(destDir, ".github", "workflows", "ci.yml");
8045
+ const contexts = existsSync26(ciPath) ? workflowCheckContexts(readFileSync19(ciPath, "utf8")) : [];
7919
8046
  if (contexts.length === 0) {
7920
8047
  log.warn(
7921
8048
  `Could not determine required status checks from ${ciPath} \u2014 skipping branch protection. Configure it manually on dev once you know the CI job names.`
@@ -7944,8 +8071,8 @@ async function registerInRegistrySources(names, cloneUrl, token, deps) {
7944
8071
  let dir;
7945
8072
  try {
7946
8073
  dir = await deps.git.cloneForEditing(REGISTRY_REPO, "biffo-registry", token);
7947
- const path = join25(dir, "sources.json");
7948
- const file = JSON.parse(readFileSync18(path, "utf8"));
8074
+ const path = join26(dir, "sources.json");
8075
+ const file = JSON.parse(readFileSync19(path, "utf8"));
7949
8076
  const next = addSource(file, {
7950
8077
  name: names.slug,
7951
8078
  repo: cloneUrl.replace(/\.git$/, ""),
@@ -8053,8 +8180,8 @@ function printStandaloneNextSteps(names, minor) {
8053
8180
  }
8054
8181
  function resolveSkeletonRoot(options) {
8055
8182
  const here = dirname8(fileURLToPath5(import.meta.url));
8056
- const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join25(options.cwd, "_skeletons", "plugin-template");
8057
- if (!existsSync25(skeletonRoot)) {
8183
+ const skeletonRoot = options.skeletonRoot ?? findSkeletonRoot(here, "plugin-template") ?? join26(options.cwd, "_skeletons", "plugin-template");
8184
+ if (!existsSync26(skeletonRoot)) {
8058
8185
  throw new Error(
8059
8186
  `Could not find the plugin skeleton (_skeletons/plugin-template/). Pass --skeleton <path> to point at it explicitly.`
8060
8187
  );
@@ -8229,14 +8356,14 @@ function printEntry(entry) {
8229
8356
  }
8230
8357
 
8231
8358
  // src/commands/plugin-install.ts
8232
- import { cpSync as cpSync3, existsSync as existsSync27, mkdirSync as mkdirSync9, readFileSync as readFileSync20, statSync as statSync6 } from "fs";
8233
- import { basename, join as join28, relative as relative3, resolve as resolve12 } from "path";
8359
+ import { cpSync as cpSync3, existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as readFileSync21, statSync as statSync6 } from "fs";
8360
+ import { basename, join as join29, relative as relative3, resolve as resolve12 } from "path";
8234
8361
  import chalk15 from "chalk";
8235
8362
  import { Command as Command15 } from "commander";
8236
8363
 
8237
8364
  // src/adapters/plugin-migrations/index.ts
8238
8365
  import { execa as execa4 } from "execa";
8239
- import { join as join26 } from "path";
8366
+ import { join as join27 } from "path";
8240
8367
  var PluginMigrationsAdapter = class {
8241
8368
  /**
8242
8369
  * Generates migration file(s) for `pluginNames` (every discovered
@@ -8245,22 +8372,22 @@ var PluginMigrationsAdapter = class {
8245
8372
  * or declared no tables.
8246
8373
  */
8247
8374
  async generate(cwd, pluginNames) {
8248
- const scriptPath = join26(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
8375
+ const scriptPath = join27(cwd, "services", "api", "scripts", "generate_plugin_migrations.py");
8249
8376
  const args = [
8250
8377
  "run",
8251
8378
  "python",
8252
8379
  scriptPath,
8253
8380
  "--services-root",
8254
- join26(cwd, "services"),
8381
+ join27(cwd, "services"),
8255
8382
  "--versions-dir",
8256
- join26(cwd, "services", "api", "migrations", "versions")
8383
+ join27(cwd, "services", "api", "migrations", "versions")
8257
8384
  ];
8258
8385
  for (const name of pluginNames ?? []) {
8259
8386
  args.push("--plugin", name);
8260
8387
  }
8261
8388
  let result;
8262
8389
  try {
8263
- result = await execa4("uv", args, { cwd: join26(cwd, "services", "api") });
8390
+ result = await execa4("uv", args, { cwd: join27(cwd, "services", "api") });
8264
8391
  } catch (err) {
8265
8392
  const cause = err;
8266
8393
  if (cause.code === "ENOENT") {
@@ -8277,8 +8404,8 @@ var PluginMigrationsAdapter = class {
8277
8404
  };
8278
8405
 
8279
8406
  // src/lib/plugin-workspace-sources.ts
8280
- import { existsSync as existsSync26, readdirSync as readdirSync12, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
8281
- import { join as join27 } from "path";
8407
+ import { existsSync as existsSync27, readdirSync as readdirSync12, readFileSync as readFileSync20, writeFileSync as writeFileSync10 } from "fs";
8408
+ import { join as join28 } from "path";
8282
8409
  function readTomlStringArray(text, key) {
8283
8410
  const open = new RegExp(`^${key}\\s*=\\s*\\[`, "m").exec(text);
8284
8411
  if (!open) return [];
@@ -8322,9 +8449,9 @@ function readDependencyNames(text) {
8322
8449
  return readTomlStringArray(text, "dependencies").map((dep) => /^\s*([A-Za-z0-9._-]+)/.exec(dep)?.[1] ?? "").filter(Boolean);
8323
8450
  }
8324
8451
  function workspaceMemberNames(instanceRoot) {
8325
- const rootPyproject = join27(instanceRoot, "pyproject.toml");
8326
- if (!existsSync26(rootPyproject)) return /* @__PURE__ */ new Set();
8327
- const text = readFileSync19(rootPyproject, "utf8");
8452
+ const rootPyproject = join28(instanceRoot, "pyproject.toml");
8453
+ if (!existsSync27(rootPyproject)) return /* @__PURE__ */ new Set();
8454
+ const text = readFileSync20(rootPyproject, "utf8");
8328
8455
  const members = readTomlStringArray(text, "members");
8329
8456
  const excluded = new Set(readTomlStringArray(text, "exclude"));
8330
8457
  const dirs = [];
@@ -8333,7 +8460,7 @@ function workspaceMemberNames(instanceRoot) {
8333
8460
  const base = member.slice(0, -2);
8334
8461
  let entries;
8335
8462
  try {
8336
- entries = readdirSync12(join27(instanceRoot, base), { withFileTypes: true });
8463
+ entries = readdirSync12(join28(instanceRoot, base), { withFileTypes: true });
8337
8464
  } catch {
8338
8465
  continue;
8339
8466
  }
@@ -8347,9 +8474,9 @@ function workspaceMemberNames(instanceRoot) {
8347
8474
  }
8348
8475
  const names = /* @__PURE__ */ new Set();
8349
8476
  for (const dir of dirs) {
8350
- const pp = join27(instanceRoot, dir, "pyproject.toml");
8351
- if (!existsSync26(pp)) continue;
8352
- const name = readProjectName(readFileSync19(pp, "utf8"));
8477
+ const pp = join28(instanceRoot, dir, "pyproject.toml");
8478
+ if (!existsSync27(pp)) continue;
8479
+ const name = readProjectName(readFileSync20(pp, "utf8"));
8353
8480
  if (name) names.add(name);
8354
8481
  }
8355
8482
  return names;
@@ -8360,8 +8487,8 @@ function existingWorkspaceSources(text) {
8360
8487
  );
8361
8488
  }
8362
8489
  function ensureWorkspaceSources(pluginPyprojectPath, memberNames) {
8363
- if (!existsSync26(pluginPyprojectPath) || memberNames.size === 0) return [];
8364
- const text = readFileSync19(pluginPyprojectPath, "utf8");
8490
+ if (!existsSync27(pluginPyprojectPath) || memberNames.size === 0) return [];
8491
+ const text = readFileSync20(pluginPyprojectPath, "utf8");
8365
8492
  const already = existingWorkspaceSources(text);
8366
8493
  const toAdd = readDependencyNames(text).filter((n) => memberNames.has(n) && !already.has(n));
8367
8494
  if (toAdd.length === 0) return [];
@@ -8427,14 +8554,14 @@ var LOCAL_COPY_EXCLUDES = /* @__PURE__ */ new Set([
8427
8554
  ".terraform"
8428
8555
  ]);
8429
8556
  function resolveLocalPlugin(localPath) {
8430
- if (!existsSync27(localPath)) {
8557
+ if (!existsSync28(localPath)) {
8431
8558
  throw new Error(`--local path does not exist: ${localPath}`);
8432
8559
  }
8433
8560
  if (!statSync6(localPath).isDirectory()) {
8434
8561
  throw new Error(`--local path is not a directory: ${localPath}`);
8435
8562
  }
8436
- const manifestPath = join28(localPath, "biffo.plugin.json");
8437
- if (!existsSync27(manifestPath)) {
8563
+ const manifestPath = join29(localPath, "biffo.plugin.json");
8564
+ if (!existsSync28(manifestPath)) {
8438
8565
  throw new Error(
8439
8566
  `${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>\`.)`
8440
8567
  );
@@ -8460,8 +8587,8 @@ function parsePluginTarget(target) {
8460
8587
  async function cloneAndValidatePlugin(entry, git) {
8461
8588
  const tmpDir = await git.cloneToTemp(entry.repo, `biffo-plugin-${entry.name}`);
8462
8589
  try {
8463
- const manifestPath = join28(tmpDir, "biffo.plugin.json");
8464
- if (!existsSync27(manifestPath)) {
8590
+ const manifestPath = join29(tmpDir, "biffo.plugin.json");
8591
+ if (!existsSync28(manifestPath)) {
8465
8592
  throw new Error(
8466
8593
  `Plugin repo ${entry.repo} does not contain a biffo.plugin.json manifest at its root.`
8467
8594
  );
@@ -8489,8 +8616,8 @@ async function runPluginInstall(target, options, deps) {
8489
8616
  `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\`).`
8490
8617
  );
8491
8618
  }
8492
- const servicesDir = join28(options.cwd, "services");
8493
- if (!existsSync27(servicesDir)) {
8619
+ const servicesDir = join29(options.cwd, "services");
8620
+ if (!existsSync28(servicesDir)) {
8494
8621
  throw new Error(
8495
8622
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8496
8623
  );
@@ -8508,10 +8635,10 @@ async function runPluginInstall(target, options, deps) {
8508
8635
  }
8509
8636
  const pluginName = entry ? entry.name : source.name;
8510
8637
  const relTargetDir = pluginDir(pluginName, "third-party");
8511
- const targetDir = join28(options.cwd, relTargetDir);
8512
- const modulesDir = join28(options.cwd, "modules", "plugins", pluginName);
8638
+ const targetDir = join29(options.cwd, relTargetDir);
8639
+ const modulesDir = join29(options.cwd, "modules", "plugins", pluginName);
8513
8640
  const inTreeSource = options.local !== void 0 && resolve12(options.local) === resolve12(targetDir);
8514
- if (existsSync27(targetDir) && !inTreeSource) {
8641
+ if (existsSync28(targetDir) && !inTreeSource) {
8515
8642
  throw new Error(
8516
8643
  `Plugin '${pluginName}' is already installed at ${relTargetDir}/. Remove it first, or wait for a future 'biffo plugin upgrade' command.`
8517
8644
  );
@@ -8553,8 +8680,8 @@ async function runPluginInstall(target, options, deps) {
8553
8680
  });
8554
8681
  log.success(`Installed plugin source at ${relTargetDir}/`);
8555
8682
  }
8556
- const pluginPyproject = join28(targetDir, "pyproject.toml");
8557
- if (existsSync27(pluginPyproject)) {
8683
+ const pluginPyproject = join29(targetDir, "pyproject.toml");
8684
+ if (existsSync28(pluginPyproject)) {
8558
8685
  const sourced = ensureWorkspaceSources(pluginPyproject, workspaceMemberNames(options.cwd));
8559
8686
  if (sourced.length > 0) {
8560
8687
  log.info(
@@ -8563,8 +8690,8 @@ async function runPluginInstall(target, options, deps) {
8563
8690
  }
8564
8691
  }
8565
8692
  const stagePaths = [relTargetDir];
8566
- const tfSourceDir = join28(targetDir, "terraform");
8567
- if (existsSync27(tfSourceDir)) {
8693
+ const tfSourceDir = join29(targetDir, "terraform");
8694
+ if (existsSync28(tfSourceDir)) {
8568
8695
  mkdirSync9(modulesDir, { recursive: true });
8569
8696
  cpSync3(tfSourceDir, modulesDir, { recursive: true });
8570
8697
  stagePaths.push(`modules/plugins/${pluginName}`);
@@ -8621,7 +8748,7 @@ async function runPluginInstall(target, options, deps) {
8621
8748
  }
8622
8749
  function parseManifestFile(path) {
8623
8750
  try {
8624
- return JSON.parse(readFileSync20(path, "utf8"));
8751
+ return JSON.parse(readFileSync21(path, "utf8"));
8625
8752
  } catch (err) {
8626
8753
  throw new Error(`Could not parse ${path} as JSON: ${err.message}`);
8627
8754
  }
@@ -8658,8 +8785,8 @@ function printDryRun4(entry, source, relTargetDir, inTreeSource) {
8658
8785
  }
8659
8786
 
8660
8787
  // src/commands/plugin-list.ts
8661
- import { existsSync as existsSync28, readFileSync as readFileSync21 } from "fs";
8662
- import { join as join29, resolve as resolve13 } from "path";
8788
+ import { existsSync as existsSync29, readFileSync as readFileSync22 } from "fs";
8789
+ import { join as join30, resolve as resolve13 } from "path";
8663
8790
  import chalk16 from "chalk";
8664
8791
  import { Command as Command16 } from "commander";
8665
8792
  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) => {
@@ -8672,8 +8799,8 @@ var pluginListCommand = new Command16("list").description("List plugins installe
8672
8799
  }
8673
8800
  });
8674
8801
  async function runPluginList(options) {
8675
- const servicesDir = join29(options.cwd, "services");
8676
- if (!existsSync28(servicesDir)) {
8802
+ const servicesDir = join30(options.cwd, "services");
8803
+ if (!existsSync29(servicesDir)) {
8677
8804
  throw new Error(
8678
8805
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8679
8806
  );
@@ -8681,7 +8808,7 @@ async function runPluginList(options) {
8681
8808
  const plugins = [];
8682
8809
  for (const location of findInstalledPlugins(options.cwd)) {
8683
8810
  try {
8684
- const manifest = validateManifest(JSON.parse(readFileSync21(location.manifestPath, "utf8")));
8811
+ const manifest = validateManifest(JSON.parse(readFileSync22(location.manifestPath, "utf8")));
8685
8812
  plugins.push({
8686
8813
  name: manifest.name,
8687
8814
  version: manifest.version,
@@ -8718,8 +8845,8 @@ async function runPluginList(options) {
8718
8845
  }
8719
8846
 
8720
8847
  // src/commands/plugin-sync-migrations.ts
8721
- import { existsSync as existsSync29 } from "fs";
8722
- import { join as join30, relative as relative4, resolve as resolve14 } from "path";
8848
+ import { existsSync as existsSync30 } from "fs";
8849
+ import { join as join31, relative as relative4, resolve as resolve14 } from "path";
8723
8850
  import chalk17 from "chalk";
8724
8851
  import { Command as Command17 } from "commander";
8725
8852
  var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
@@ -8740,11 +8867,11 @@ var pluginSyncMigrationsCommand = new Command17("sync-migrations").description(
8740
8867
  }
8741
8868
  );
8742
8869
  async function runPluginSyncMigrations(name, options, deps) {
8743
- const servicesDir = join30(options.cwd, "services");
8744
- if (!existsSync29(servicesDir)) {
8870
+ const servicesDir = join31(options.cwd, "services");
8871
+ if (!existsSync30(servicesDir)) {
8745
8872
  throw new Error(`${servicesDir} does not exist \u2014 is ${options.cwd} a Biffo project checkout?`);
8746
8873
  }
8747
- if (name && !existsSync29(join30(servicesDir, name, "biffo.plugin.json"))) {
8874
+ if (name && !existsSync30(join31(servicesDir, name, "biffo.plugin.json"))) {
8748
8875
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
8749
8876
  }
8750
8877
  if (options.dryRun) {
@@ -8780,8 +8907,8 @@ async function runPluginSyncMigrations(name, options, deps) {
8780
8907
  }
8781
8908
 
8782
8909
  // src/commands/plugin-uninstall.ts
8783
- import { existsSync as existsSync30, readFileSync as readFileSync22, rmSync as rmSync8 } from "fs";
8784
- import { join as join31, resolve as resolve15 } from "path";
8910
+ import { existsSync as existsSync31, readFileSync as readFileSync23, rmSync as rmSync8 } from "fs";
8911
+ import { join as join32, resolve as resolve15 } from "path";
8785
8912
  import chalk18 from "chalk";
8786
8913
  import { Command as Command18 } from "commander";
8787
8914
  import inquirer6 from "inquirer";
@@ -8813,16 +8940,16 @@ async function runPluginUninstall(name, options, deps) {
8813
8940
  if (!NAME_PATTERN2.test(name)) {
8814
8941
  throw new Error(`Invalid plugin name '${name}'. Expected a lowercase kebab-case slug.`);
8815
8942
  }
8816
- const servicesDir = join31(options.cwd, "services");
8817
- if (!existsSync30(servicesDir)) {
8943
+ const servicesDir = join32(options.cwd, "services");
8944
+ if (!existsSync31(servicesDir)) {
8818
8945
  throw new Error(
8819
8946
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8820
8947
  );
8821
8948
  }
8822
- const targetDir = join31(servicesDir, name);
8823
- if (!existsSync30(targetDir)) {
8824
- const firstParty = join31(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
8825
- if (existsSync30(firstParty)) {
8949
+ const targetDir = join32(servicesDir, name);
8950
+ if (!existsSync31(targetDir)) {
8951
+ const firstParty = join32(servicesDir, FIRST_PARTY_PLUGINS_DIR, name);
8952
+ if (existsSync31(firstParty)) {
8826
8953
  throw new Error(
8827
8954
  `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.`
8828
8955
  );
@@ -8830,9 +8957,9 @@ async function runPluginUninstall(name, options, deps) {
8830
8957
  throw new Error(`Plugin '${name}' is not installed at services/${name}/.`);
8831
8958
  }
8832
8959
  const version = readInstalledVersion(targetDir);
8833
- const modulesDir = join31(options.cwd, "modules", "plugins", name);
8960
+ const modulesDir = join32(options.cwd, "modules", "plugins", name);
8834
8961
  const stagePaths = [`services/${name}`];
8835
- if (existsSync30(modulesDir)) {
8962
+ if (existsSync31(modulesDir)) {
8836
8963
  stagePaths.push(`modules/plugins/${name}`);
8837
8964
  }
8838
8965
  if (options.dryRun) {
@@ -8854,7 +8981,7 @@ async function runPluginUninstall(name, options, deps) {
8854
8981
  }
8855
8982
  rmSync8(targetDir, { recursive: true, force: true });
8856
8983
  log.success(`Removed services/${name}/`);
8857
- if (existsSync30(modulesDir)) {
8984
+ if (existsSync31(modulesDir)) {
8858
8985
  rmSync8(modulesDir, { recursive: true, force: true });
8859
8986
  log.success(`Removed modules/plugins/${name}/`);
8860
8987
  const wiring = syncPluginTerraform(options.cwd);
@@ -8891,10 +9018,10 @@ async function runPluginUninstall(name, options, deps) {
8891
9018
  }
8892
9019
  }
8893
9020
  function readInstalledVersion(targetDir) {
8894
- const manifestPath = join31(targetDir, "biffo.plugin.json");
8895
- if (!existsSync30(manifestPath)) return void 0;
9021
+ const manifestPath = join32(targetDir, "biffo.plugin.json");
9022
+ if (!existsSync31(manifestPath)) return void 0;
8896
9023
  try {
8897
- return validateManifest(JSON.parse(readFileSync22(manifestPath, "utf8"))).version;
9024
+ return validateManifest(JSON.parse(readFileSync23(manifestPath, "utf8"))).version;
8898
9025
  } catch {
8899
9026
  return void 0;
8900
9027
  }
@@ -8927,8 +9054,8 @@ function printDryRun5(name, version, stagePaths, keepData) {
8927
9054
  }
8928
9055
 
8929
9056
  // src/commands/plugin-upgrade.ts
8930
- import { cpSync as cpSync4, existsSync as existsSync31, mkdirSync as mkdirSync10, readFileSync as readFileSync23, rmSync as rmSync9 } from "fs";
8931
- import { join as join32, relative as relative5, resolve as resolve16 } from "path";
9057
+ import { cpSync as cpSync4, existsSync as existsSync32, mkdirSync as mkdirSync10, readFileSync as readFileSync24, rmSync as rmSync9 } from "fs";
9058
+ import { join as join33, relative as relative5, resolve as resolve16 } from "path";
8932
9059
  import chalk19 from "chalk";
8933
9060
  import { Command as Command19 } from "commander";
8934
9061
  import inquirer7 from "inquirer";
@@ -8953,14 +9080,14 @@ var pluginUpgradeCommand = new Command19("upgrade").description(
8953
9080
  });
8954
9081
  async function runPluginUpgrade(target, options, deps) {
8955
9082
  const { name, minor } = parsePluginTarget(target);
8956
- const servicesDir = join32(options.cwd, "services");
8957
- if (!existsSync31(servicesDir)) {
9083
+ const servicesDir = join33(options.cwd, "services");
9084
+ if (!existsSync32(servicesDir)) {
8958
9085
  throw new Error(
8959
9086
  `${servicesDir} does not exist \u2014 is ${options.cwd} the root of a Biffo project checkout?`
8960
9087
  );
8961
9088
  }
8962
- const targetDir = join32(servicesDir, name);
8963
- if (!existsSync31(targetDir)) {
9089
+ const targetDir = join33(servicesDir, name);
9090
+ if (!existsSync32(targetDir)) {
8964
9091
  throw new Error(
8965
9092
  `Plugin '${name}' is not installed at services/${name}/. Use 'biffo plugin install ${name}@${minor}' instead.`
8966
9093
  );
@@ -8974,7 +9101,7 @@ async function runPluginUpgrade(target, options, deps) {
8974
9101
  `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.`
8975
9102
  );
8976
9103
  }
8977
- const modulesDir = join32(options.cwd, "modules", "plugins", entry.name);
9104
+ const modulesDir = join33(options.cwd, "modules", "plugins", entry.name);
8978
9105
  if (options.dryRun) {
8979
9106
  printDryRun6(entry, currentVersion);
8980
9107
  return;
@@ -9007,11 +9134,11 @@ async function runPluginUpgrade(target, options, deps) {
9007
9134
  cpSync4(tmpDir, targetDir, { recursive: true });
9008
9135
  log.success(`Upgraded plugin source at services/${entry.name}/`);
9009
9136
  const stagePaths = [`services/${entry.name}`];
9010
- if (existsSync31(modulesDir)) {
9137
+ if (existsSync32(modulesDir)) {
9011
9138
  rmSync9(modulesDir, { recursive: true, force: true });
9012
9139
  }
9013
- const tfSourceDir = join32(targetDir, "terraform");
9014
- if (existsSync31(tfSourceDir)) {
9140
+ const tfSourceDir = join33(targetDir, "terraform");
9141
+ if (existsSync32(tfSourceDir)) {
9015
9142
  mkdirSync10(modulesDir, { recursive: true });
9016
9143
  cpSync4(tfSourceDir, modulesDir, { recursive: true });
9017
9144
  stagePaths.push(`modules/plugins/${entry.name}`);
@@ -9045,10 +9172,10 @@ async function runPluginUpgrade(target, options, deps) {
9045
9172
  }
9046
9173
  }
9047
9174
  function readInstalledVersion2(targetDir) {
9048
- const manifestPath = join32(targetDir, "biffo.plugin.json");
9049
- if (!existsSync31(manifestPath)) return void 0;
9175
+ const manifestPath = join33(targetDir, "biffo.plugin.json");
9176
+ if (!existsSync32(manifestPath)) return void 0;
9050
9177
  try {
9051
- return validateManifest(JSON.parse(readFileSync23(manifestPath, "utf8"))).version;
9178
+ return validateManifest(JSON.parse(readFileSync24(manifestPath, "utf8"))).version;
9052
9179
  } catch {
9053
9180
  return void 0;
9054
9181
  }
@@ -9090,7 +9217,7 @@ pluginCommand.addCommand(pluginInfoCommand);
9090
9217
  import { Command as Command22 } from "commander";
9091
9218
 
9092
9219
  // src/commands/sibling-check-identity.ts
9093
- import { existsSync as existsSync32, readFileSync as readFileSync24 } from "fs";
9220
+ import { existsSync as existsSync33, readFileSync as readFileSync25 } from "fs";
9094
9221
  import { resolve as resolve17 } from "path";
9095
9222
  import chalk20 from "chalk";
9096
9223
  import { Command as Command21 } from "commander";
@@ -9284,7 +9411,7 @@ async function fetchPublishedIdentity(portalUrl) {
9284
9411
  }
9285
9412
  async function resolveConfig4(options) {
9286
9413
  if (options.config) {
9287
- const raw = JSON.parse(readFileSync24(resolve17(options.config), "utf8"));
9414
+ const raw = JSON.parse(readFileSync25(resolve17(options.config), "utf8"));
9288
9415
  const result = BiffoConfigSchema.safeParse(raw);
9289
9416
  if (!result.success) {
9290
9417
  log.error(`Invalid config at ${options.config}:`);
@@ -9304,8 +9431,8 @@ async function resolveConfig4(options) {
9304
9431
  return cfg;
9305
9432
  }
9306
9433
  const localConfigPath = resolve17(process.cwd(), "biffo.config.json");
9307
- if (existsSync32(localConfigPath)) {
9308
- const raw = JSON.parse(readFileSync24(localConfigPath, "utf8"));
9434
+ if (existsSync33(localConfigPath)) {
9435
+ const raw = JSON.parse(readFileSync25(localConfigPath, "utf8"));
9309
9436
  const result = BiffoConfigSchema.safeParse(raw);
9310
9437
  if (result.success) return result.data;
9311
9438
  if (isTemplatePlaceholderConfig(raw)) {
@@ -9351,21 +9478,21 @@ siblingCommand.addCommand(siblingCheckIdentityCommand);
9351
9478
  import { Command as Command23 } from "commander";
9352
9479
 
9353
9480
  // src/scripts/check-adr-numbering.ts
9354
- import { existsSync as existsSync34 } from "fs";
9355
- import { join as join34 } from "path";
9481
+ import { existsSync as existsSync35 } from "fs";
9482
+ import { join as join35 } from "path";
9356
9483
  import { execa as execa5 } from "execa";
9357
9484
 
9358
9485
  // src/lib/adr-numbering-guard.ts
9359
- import { existsSync as existsSync33, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "fs";
9360
- import { join as join33 } from "path";
9486
+ import { existsSync as existsSync34, readdirSync as readdirSync13, readFileSync as readFileSync26 } from "fs";
9487
+ import { join as join34 } from "path";
9361
9488
  var ADR_FILENAME = /^(\d{4})-.+\.md$/;
9362
9489
  var ALLOWLIST_FILENAME = ".numbering-allowlist";
9363
9490
  var TEMPLATE_ADR_RESERVED_UPTO = "0099";
9364
9491
  function readAdrNumberingAllowlist(adrDir) {
9365
- const path = join33(adrDir, ALLOWLIST_FILENAME);
9366
- if (!existsSync33(path)) return /* @__PURE__ */ new Set();
9492
+ const path = join34(adrDir, ALLOWLIST_FILENAME);
9493
+ if (!existsSync34(path)) return /* @__PURE__ */ new Set();
9367
9494
  const numbers = /* @__PURE__ */ new Set();
9368
- for (const rawLine of readFileSync25(path, "utf8").split("\n")) {
9495
+ for (const rawLine of readFileSync26(path, "utf8").split("\n")) {
9369
9496
  const line = rawLine.split("#")[0].trim();
9370
9497
  if (line) numbers.add(line);
9371
9498
  }
@@ -9373,7 +9500,7 @@ function readAdrNumberingAllowlist(adrDir) {
9373
9500
  }
9374
9501
  function adrNumbersIn(adrDir) {
9375
9502
  const claims = /* @__PURE__ */ new Map();
9376
- if (!existsSync33(adrDir)) return claims;
9503
+ if (!existsSync34(adrDir)) return claims;
9377
9504
  for (const entry of readdirSync13(adrDir).sort()) {
9378
9505
  const match = ADR_FILENAME.exec(entry);
9379
9506
  if (!match) continue;
@@ -9428,8 +9555,8 @@ function formatAdrReservedRangeViolations(violations, reservedUpTo = TEMPLATE_AD
9428
9555
  // src/scripts/check-adr-numbering.ts
9429
9556
  async function runAdrNumberingCheck() {
9430
9557
  const root = (await execa5("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
9431
- const adrDir = join34(root, "docs", "ADR");
9432
- if (!existsSync34(adrDir)) {
9558
+ const adrDir = join35(root, "docs", "ADR");
9559
+ if (!existsSync35(adrDir)) {
9433
9560
  console.log("\u2713 ADR numbering guard: no docs/ADR/ directory \u2014 nothing to compare");
9434
9561
  return;
9435
9562
  }
@@ -9757,8 +9884,8 @@ async function runOwnershipCheck(argv) {
9757
9884
  const { stdout } = await execa7("git", ["diff", "--cached", "--name-status"], { cwd: root });
9758
9885
  ({ changed: changedFiles, deleted: deletedFiles } = parseNameStatus(stdout));
9759
9886
  if (messageFile) {
9760
- const { readFileSync: readFileSync28, existsSync: existsSync40 } = await import("fs");
9761
- if (existsSync40(messageFile)) commitMessage = readFileSync28(messageFile, "utf8");
9887
+ const { readFileSync: readFileSync29, existsSync: existsSync41 } = await import("fs");
9888
+ if (existsSync41(messageFile)) commitMessage = readFileSync29(messageFile, "utf8");
9762
9889
  }
9763
9890
  } else {
9764
9891
  const base = process.env["GITHUB_BASE_REF"] ?? args[0];
@@ -9859,33 +9986,33 @@ ${BOLD}If the divergence is deliberate${OFF}
9859
9986
  }
9860
9987
 
9861
9988
  // src/scripts/check-plugin-collisions.ts
9862
- import { existsSync as existsSync36 } from "fs";
9863
- import { join as join36 } from "path";
9989
+ import { existsSync as existsSync37 } from "fs";
9990
+ import { join as join37 } from "path";
9864
9991
  import { execa as execa8 } from "execa";
9865
9992
 
9866
9993
  // src/lib/plugin-collision-guard.ts
9867
- import { existsSync as existsSync35, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
9868
- import { join as join35 } from "path";
9994
+ import { existsSync as existsSync36, readdirSync as readdirSync14, statSync as statSync7 } from "fs";
9995
+ import { join as join36 } from "path";
9869
9996
  var PYTEST_SPECIAL = /* @__PURE__ */ new Set(["conftest.py"]);
9870
9997
  var IGNORED_DIRS = /* @__PURE__ */ new Set([".venv", "node_modules", "__pycache__", ".git", "dist", "build"]);
9871
9998
  function subdirectories(dir) {
9872
- if (!existsSync35(dir)) return [];
9999
+ if (!existsSync36(dir)) return [];
9873
10000
  return readdirSync14(dir).filter((entry) => {
9874
10001
  if (IGNORED_DIRS.has(entry) || entry.startsWith(".")) return false;
9875
10002
  try {
9876
- return statSync7(join35(dir, entry)).isDirectory();
10003
+ return statSync7(join36(dir, entry)).isDirectory();
9877
10004
  } catch {
9878
10005
  return false;
9879
10006
  }
9880
10007
  });
9881
10008
  }
9882
10009
  function regularPackagesOf(pluginDir2) {
9883
- return subdirectories(pluginDir2).filter((name) => existsSync35(join35(pluginDir2, name, "__init__.py"))).sort();
10010
+ return subdirectories(pluginDir2).filter((name) => existsSync36(join36(pluginDir2, name, "__init__.py"))).sort();
9884
10011
  }
9885
10012
  function bareTestModulesOf(pluginDir2) {
9886
- const testsDir = join35(pluginDir2, "tests");
9887
- if (!existsSync35(testsDir)) return [];
9888
- if (existsSync35(join35(testsDir, "__init__.py"))) return [];
10013
+ const testsDir = join36(pluginDir2, "tests");
10014
+ if (!existsSync36(testsDir)) return [];
10015
+ if (existsSync36(join36(testsDir, "__init__.py"))) return [];
9889
10016
  return readdirSync14(testsDir).filter((f) => f.endsWith(".py") && !PYTEST_SPECIAL.has(f)).sort();
9890
10017
  }
9891
10018
  function findCollisions(servicesDir, pluginDirs) {
@@ -9894,7 +10021,7 @@ function findCollisions(servicesDir, pluginDirs) {
9894
10021
  const gather = (kind, namesOf) => {
9895
10022
  const claims = /* @__PURE__ */ new Map();
9896
10023
  for (const plugin of plugins) {
9897
- for (const name of namesOf(join35(servicesDir, plugin))) {
10024
+ for (const name of namesOf(join36(servicesDir, plugin))) {
9898
10025
  claims.set(name, [...claims.get(name) ?? [], plugin]);
9899
10026
  }
9900
10027
  }
@@ -9932,8 +10059,8 @@ function formatCollisions(collisions) {
9932
10059
  // src/scripts/check-plugin-collisions.ts
9933
10060
  async function runPluginCollisionCheck() {
9934
10061
  const root = (await execa8("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
9935
- const servicesDir = join36(root, "services");
9936
- if (!existsSync36(servicesDir)) {
10062
+ const servicesDir = join37(root, "services");
10063
+ if (!existsSync37(servicesDir)) {
9937
10064
  console.log("\u2713 plugin collision guard: no services/ directory \u2014 nothing to compare");
9938
10065
  return;
9939
10066
  }
@@ -9953,8 +10080,8 @@ async function runPluginCollisionCheck() {
9953
10080
  import { execa as execa9 } from "execa";
9954
10081
 
9955
10082
  // src/lib/plugin-terraform-guard.ts
9956
- import { existsSync as existsSync37, readFileSync as readFileSync26, readdirSync as readdirSync15 } from "fs";
9957
- import { dirname as dirname9, join as join37, relative as relative6, sep as sep3 } from "path";
10083
+ import { existsSync as existsSync38, readFileSync as readFileSync27, readdirSync as readdirSync15 } from "fs";
10084
+ import { dirname as dirname9, join as join38, relative as relative6, sep as sep3 } from "path";
9958
10085
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".worktrees", "dist", ".venv", "__pycache__"]);
9959
10086
  var PLUGIN_MANIFEST_FILE2 = "biffo.plugin.json";
9960
10087
  function findPluginManifests(root) {
@@ -9969,9 +10096,9 @@ function findPluginManifests(root) {
9969
10096
  for (const entry of entries) {
9970
10097
  if (entry.isDirectory()) {
9971
10098
  if (SKIP_DIRS.has(entry.name)) continue;
9972
- walk(join37(dir, entry.name));
10099
+ walk(join38(dir, entry.name));
9973
10100
  } else if (entry.isFile() && entry.name === PLUGIN_MANIFEST_FILE2) {
9974
- found.push(relative6(root, join37(dir, entry.name)).split(sep3).join("/"));
10101
+ found.push(relative6(root, join38(dir, entry.name)).split(sep3).join("/"));
9975
10102
  }
9976
10103
  }
9977
10104
  };
@@ -9981,7 +10108,7 @@ function findPluginManifests(root) {
9981
10108
  function readSubscriptions(absManifestPath) {
9982
10109
  let parsed;
9983
10110
  try {
9984
- parsed = JSON.parse(readFileSync26(absManifestPath, "utf8"));
10111
+ parsed = JSON.parse(readFileSync27(absManifestPath, "utf8"));
9985
10112
  } catch {
9986
10113
  return null;
9987
10114
  }
@@ -9996,14 +10123,14 @@ function readSubscriptions(absManifestPath) {
9996
10123
  }
9997
10124
  function checkPluginTerraform(root) {
9998
10125
  const violations = [];
9999
- const coreManifest = existsSync37(join37(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
10126
+ const coreManifest = existsSync38(join38(root, CORE_MANIFEST_FILE)) ? readCoreManifest(root) : null;
10000
10127
  for (const manifest of findPluginManifests(root)) {
10001
10128
  if (coreManifest && !isTemplateOwned(manifest, coreManifest)) continue;
10002
- const absManifest = join37(root, manifest);
10129
+ const absManifest = join38(root, manifest);
10003
10130
  const subscriptions = readSubscriptions(absManifest);
10004
10131
  if (subscriptions === null) continue;
10005
10132
  const pluginDir2 = dirname9(absManifest);
10006
- if (existsSync37(join37(pluginDir2, "terraform"))) continue;
10133
+ if (existsSync38(join38(pluginDir2, "terraform"))) continue;
10007
10134
  const relPluginDir = relative6(root, pluginDir2).split(sep3).join("/");
10008
10135
  violations.push({
10009
10136
  manifest,
@@ -10199,8 +10326,8 @@ function rawArgsAfter(subcommand) {
10199
10326
  }
10200
10327
 
10201
10328
  // src/commands/doctor.ts
10202
- import { existsSync as existsSync38, readFileSync as readFileSync27 } from "fs";
10203
- import { join as join38, resolve as resolve18 } from "path";
10329
+ import { existsSync as existsSync39, readFileSync as readFileSync28 } from "fs";
10330
+ import { join as join39, resolve as resolve18 } from "path";
10204
10331
  import chalk21 from "chalk";
10205
10332
  import { Command as Command24 } from "commander";
10206
10333
 
@@ -10375,10 +10502,10 @@ async function runDoctor(options, deps = { git: new GitAdapter() }) {
10375
10502
  return runDoctorChecks(facts);
10376
10503
  }
10377
10504
  function readLocalCoreVersion(cwd) {
10378
- const path = join38(cwd, INSTANCE_CORE_FILE);
10379
- if (!existsSync38(path)) return null;
10505
+ const path = join39(cwd, INSTANCE_CORE_FILE);
10506
+ if (!existsSync39(path)) return null;
10380
10507
  try {
10381
- return parseCoreRecord(readFileSync27(path, "utf8"));
10508
+ return parseCoreRecord(readFileSync28(path, "utf8"));
10382
10509
  } catch {
10383
10510
  return null;
10384
10511
  }
@@ -10393,10 +10520,10 @@ function parseCoreRecord(contents) {
10393
10520
  }
10394
10521
  }
10395
10522
  function readFossil(cwd) {
10396
- const path = join38(cwd, CORE_VERSION_FILE);
10397
- if (!existsSync38(path)) return null;
10523
+ const path = join39(cwd, CORE_VERSION_FILE);
10524
+ if (!existsSync39(path)) return null;
10398
10525
  try {
10399
- const value = readFileSync27(path, "utf8").trim();
10526
+ const value = readFileSync28(path, "utf8").trim();
10400
10527
  return value === "" ? null : value;
10401
10528
  } catch {
10402
10529
  return null;
@@ -10845,13 +10972,13 @@ import { fileURLToPath as fileURLToPath6 } from "url";
10845
10972
  import { Command as Command26 } from "commander";
10846
10973
 
10847
10974
  // src/lib/packaged-scripts.ts
10848
- import { existsSync as existsSync39 } from "fs";
10849
- import { dirname as dirname10, join as join39 } from "path";
10975
+ import { existsSync as existsSync40 } from "fs";
10976
+ import { dirname as dirname10, join as join40 } from "path";
10850
10977
  function findPackagedScript(startDir, relativePath) {
10851
10978
  let dir = startDir;
10852
10979
  for (; ; ) {
10853
- const candidate = join39(dir, relativePath);
10854
- if (existsSync39(candidate)) return candidate;
10980
+ const candidate = join40(dir, relativePath);
10981
+ if (existsSync40(candidate)) return candidate;
10855
10982
  const parent = dirname10(dir);
10856
10983
  if (parent === dir) return null;
10857
10984
  dir = parent;