@intentius/behold 0.29.3 → 0.29.5

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/cli.js CHANGED
@@ -2209,11 +2209,133 @@ var init_scratch = __esm({
2209
2209
  }
2210
2210
  });
2211
2211
 
2212
+ // src/terragucci-components.ts
2213
+ import { readFileSync as readFileSync3 } from "node:fs";
2214
+ import { join as join3 } from "node:path";
2215
+ function isTerragucciComponents(v) {
2216
+ if (typeof v !== "object" || v === null) return false;
2217
+ const d = v;
2218
+ if (d.schema !== TERRAGUCCI_COMPONENTS_SCHEMA) return false;
2219
+ const roots = d.roots;
2220
+ if (typeof roots !== "object" || roots === null || Array.isArray(roots)) return false;
2221
+ for (const r of Object.values(roots)) {
2222
+ if (typeof r !== "object" || r === null || typeof r.dir !== "string") return false;
2223
+ }
2224
+ if (!Array.isArray(d.components)) return false;
2225
+ for (const c of d.components) {
2226
+ const comp = c;
2227
+ if (typeof comp?.name !== "string" || !isStrings(comp.dependsOn) || !Array.isArray(comp.deploy)) return false;
2228
+ for (const p of comp.deploy) {
2229
+ const phase2 = p;
2230
+ if (typeof phase2?.phase !== "string" || !Array.isArray(phase2.steps)) return false;
2231
+ if (!phase2.steps.every((s) => typeof s?.kind === "string")) return false;
2232
+ }
2233
+ }
2234
+ const pipeline = d.pipeline;
2235
+ if (typeof pipeline !== "object" || pipeline === null || !Array.isArray(pipeline.jobs)) return false;
2236
+ for (const j of pipeline.jobs) {
2237
+ const job = j;
2238
+ if (typeof job?.name !== "string" || !isStrings(job.needs) || !isStrings(job.applies)) return false;
2239
+ if (job.wave !== void 0 && typeof job.wave !== "number") return false;
2240
+ }
2241
+ return true;
2242
+ }
2243
+ function readTerragucciComponents(dir) {
2244
+ let text;
2245
+ try {
2246
+ text = readFileSync3(join3(dir, TERRAGUCCI_COMPONENTS_FILE), "utf8");
2247
+ } catch {
2248
+ return void 0;
2249
+ }
2250
+ try {
2251
+ const parsed = JSON.parse(text);
2252
+ return isTerragucciComponents(parsed) ? parsed : void 0;
2253
+ } catch {
2254
+ return void 0;
2255
+ }
2256
+ }
2257
+ function declaredRootScan(decl) {
2258
+ const norm2 = (d) => d.replace(/^\.\/+/, "").replace(/\/+$/, "") || ".";
2259
+ return { roots: Object.entries(decl.roots).map(([name, r]) => ({ name, dir: norm2(r.dir) })), skipped: [] };
2260
+ }
2261
+ function terragucciComponentModule(decl) {
2262
+ const lines2 = [
2263
+ "// Generated by behold (#571) from terragucci.components.json, so chant can",
2264
+ "// draw this repo's roots as components. Written OUTSIDE the repo and",
2265
+ "// rewritten on each read; the repo is untouched.",
2266
+ ""
2267
+ ];
2268
+ decl.components.forEach((c, i) => {
2269
+ const component = {
2270
+ name: c.name,
2271
+ dependsOn: c.dependsOn,
2272
+ deploy: c.deploy.map((p) => ({
2273
+ phase: p.phase,
2274
+ steps: p.steps.map((s) => s.kind === "terraform-apply" && s.noRollback === void 0 ? { ...s, noRollback: SCAFFOLD_NO_ROLLBACK } : s)
2275
+ }))
2276
+ };
2277
+ lines2.push(`export const component${i} = ${JSON.stringify(component)};`);
2278
+ });
2279
+ lines2.push("");
2280
+ return lines2.join("\n");
2281
+ }
2282
+ function componentRoots(c) {
2283
+ return c.deploy.flatMap((p) => p.steps.filter((s) => s.kind === "terraform-apply" && typeof s.root === "string").map((s) => s.root));
2284
+ }
2285
+ function componentJobs(decl) {
2286
+ const out = {};
2287
+ for (const c of decl.components) {
2288
+ const keys = /* @__PURE__ */ new Set([c.name, ...componentRoots(c)]);
2289
+ const jobs = decl.pipeline.jobs.filter((j) => j.applies.some((a) => keys.has(a)));
2290
+ if (!jobs.length) continue;
2291
+ const primary = jobs.find((j) => j.wave !== void 0) ?? jobs[0];
2292
+ const others = jobs.filter((j) => j !== primary).map((j) => j.name);
2293
+ out[c.name] = {
2294
+ component: c.name,
2295
+ jobName: primary.name,
2296
+ needs: primary.needs,
2297
+ ...primary.wave !== void 0 ? { wave: primary.wave } : {},
2298
+ ...others.length ? { alsoIn: others } : {}
2299
+ };
2300
+ }
2301
+ return out;
2302
+ }
2303
+ function waveTitles(ir, decl) {
2304
+ const byWave = ir.groups?.byWave ?? {};
2305
+ const jobs = componentJobs(decl);
2306
+ const order = decl.pipeline.jobs.map((j) => j.name);
2307
+ const out = {};
2308
+ for (const [key, members] of Object.entries(byWave)) {
2309
+ const names = new Set(members.map((m) => jobs[m]?.jobName).filter((n) => !!n));
2310
+ if (names.size) out[key] = order.filter((n) => names.has(n)).join(" + ");
2311
+ }
2312
+ return out;
2313
+ }
2314
+ function componentRootMap(decl) {
2315
+ const out = {};
2316
+ for (const c of decl.components) {
2317
+ const root = componentRoots(c)[0];
2318
+ if (root) out[c.name] = root;
2319
+ }
2320
+ return out;
2321
+ }
2322
+ var TERRAGUCCI_COMPONENTS_FILE, TERRAGUCCI_COMPONENTS_SCHEMA, TERRAGUCCI_COMPONENT_MODULE, isStrings, SCAFFOLD_NO_ROLLBACK;
2323
+ var init_terragucci_components = __esm({
2324
+ "src/terragucci-components.ts"() {
2325
+ "use strict";
2326
+ TERRAGUCCI_COMPONENTS_FILE = "terragucci.components.json";
2327
+ TERRAGUCCI_COMPONENTS_SCHEMA = "terragucci.components/v1";
2328
+ TERRAGUCCI_COMPONENT_MODULE = "terragucci.component.ts";
2329
+ isStrings = (v) => Array.isArray(v) && v.every((s) => typeof s === "string");
2330
+ SCAFFOLD_NO_ROLLBACK = "applied by terragucci's pipeline; behold only draws this";
2331
+ }
2332
+ });
2333
+
2212
2334
  // src/terraform-member.ts
2213
2335
  import { createHash } from "node:crypto";
2214
2336
  import { createRequire } from "node:module";
2215
- import { existsSync as existsSync3, lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, readlinkSync, realpathSync, symlinkSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2216
- import { basename, dirname as dirname2, join as join3, relative, resolve, sep } from "node:path";
2337
+ import { existsSync as existsSync3, lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync as readdirSync2, readlinkSync, realpathSync, symlinkSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2338
+ import { basename, dirname as dirname2, join as join4, relative, resolve, sep } from "node:path";
2217
2339
  import { tmpdir } from "node:os";
2218
2340
  import { fileURLToPath } from "node:url";
2219
2341
  function notARoot(rel, sources) {
@@ -2232,7 +2354,7 @@ function tfSources(dir) {
2232
2354
  const out = [];
2233
2355
  for (const name of names) {
2234
2356
  try {
2235
- out.push(readFileSync3(join3(dir, name), "utf8"));
2357
+ out.push(readFileSync4(join4(dir, name), "utf8"));
2236
2358
  } catch {
2237
2359
  }
2238
2360
  }
@@ -2254,7 +2376,7 @@ function discoverTerraformRoots(estate) {
2254
2376
  }
2255
2377
  for (const e of entries) {
2256
2378
  if (!e.isDirectory() || SKIP_DIRS.has(e.name) || e.name.startsWith(".")) continue;
2257
- walk(join3(dir, e.name), depth + 1);
2379
+ walk(join4(dir, e.name), depth + 1);
2258
2380
  }
2259
2381
  };
2260
2382
  walk(root, 0);
@@ -2283,9 +2405,9 @@ function terraformRootsNoteShort(scan) {
2283
2405
  const roots = `${scan.roots.length} root${scan.roots.length === 1 ? "" : "s"}`;
2284
2406
  return scan.skipped.length ? `${roots} \xB7 ${scan.skipped.length} skipped` : roots;
2285
2407
  }
2286
- function declaredPeerRange(pkg, manifestDir = join3(beholdDir(), "..")) {
2408
+ function declaredPeerRange(pkg, manifestDir = join4(beholdDir(), "..")) {
2287
2409
  try {
2288
- const manifest = JSON.parse(readFileSync3(join3(manifestDir, "package.json"), "utf8"));
2410
+ const manifest = JSON.parse(readFileSync4(join4(manifestDir, "package.json"), "utf8"));
2289
2411
  return manifest.peerDependencies?.[pkg];
2290
2412
  } catch {
2291
2413
  return void 0;
@@ -2295,7 +2417,7 @@ function resolvedVersion(pkg, from = beholdDir()) {
2295
2417
  return resolvedPackage(pkg, from)?.version;
2296
2418
  }
2297
2419
  function resolvedPackage(pkg, from = beholdDir()) {
2298
- const req = createRequire(join3(resolve(from), "noop.js"));
2420
+ const req = createRequire(join4(resolve(from), "noop.js"));
2299
2421
  let entry;
2300
2422
  try {
2301
2423
  entry = req.resolve(pkg);
@@ -2305,7 +2427,7 @@ function resolvedPackage(pkg, from = beholdDir()) {
2305
2427
  let dir = dirname2(entry);
2306
2428
  for (; ; ) {
2307
2429
  try {
2308
- const manifest = JSON.parse(readFileSync3(join3(dir, "package.json"), "utf8"));
2430
+ const manifest = JSON.parse(readFileSync4(join4(dir, "package.json"), "utf8"));
2309
2431
  if (manifest.name === pkg) return { dir, version: manifest.version ?? "" };
2310
2432
  } catch {
2311
2433
  }
@@ -2332,7 +2454,7 @@ function terraformReaderState(from = beholdDir()) {
2332
2454
  };
2333
2455
  }
2334
2456
  function beholdNodeModules(from = beholdDir()) {
2335
- const req = createRequire(join3(resolve(from), "noop.js"));
2457
+ const req = createRequire(join4(resolve(from), "noop.js"));
2336
2458
  let entry;
2337
2459
  try {
2338
2460
  entry = req.resolve("@intentius/chant");
@@ -2355,7 +2477,7 @@ function terraformScratchDir(estate, tmp = tmpdir(), flavour = "tf") {
2355
2477
  root = realpathSync(root);
2356
2478
  } catch {
2357
2479
  }
2358
- const dir = join3(root, name);
2480
+ const dir = join4(root, name);
2359
2481
  if (dir === target || dir.startsWith(target + sep)) {
2360
2482
  throw new Error(`scratch discipline: the generated reader config for ${target} would land inside the estate (${dir})`);
2361
2483
  }
@@ -2392,26 +2514,37 @@ function relink(path, target) {
2392
2514
  }
2393
2515
  symlinkSync(target, path);
2394
2516
  }
2395
- function writeTerraformScratchProject(estate, scan, tmp = tmpdir(), flavour = "tf") {
2517
+ function writeTerraformScratchProject(estate, scan, tmp = tmpdir(), flavour = "tf", components) {
2396
2518
  const target = resolve(estate);
2397
2519
  const dir = terraformScratchDir(target, tmp, flavour);
2398
2520
  mkdirSync2(dir, { recursive: true });
2399
- relink(join3(dir, ESTATE_LINK), target);
2521
+ relink(join4(dir, ESTATE_LINK), target);
2400
2522
  const modules = beholdNodeModules();
2401
- if (modules) relink(join3(dir, "node_modules"), modules);
2523
+ if (modules) relink(join4(dir, "node_modules"), modules);
2402
2524
  const write = (path, text) => {
2403
- if (!existsSync3(path) || readFileSync3(path, "utf8") !== text) writeFileSync2(path, text);
2525
+ if (!existsSync3(path) || readFileSync4(path, "utf8") !== text) writeFileSync2(path, text);
2404
2526
  };
2405
- write(join3(dir, "package.json"), SCRATCH_PACKAGE_JSON);
2406
- write(join3(dir, "chant.config.ts"), terraformScratchConfig(scan, ESTATE_LINK, flavour));
2527
+ write(join4(dir, "package.json"), SCRATCH_PACKAGE_JSON);
2528
+ write(join4(dir, "chant.config.ts"), terraformScratchConfig(scan, ESTATE_LINK, flavour));
2529
+ const module = join4(dir, TERRAGUCCI_COMPONENT_MODULE);
2530
+ if (components) write(module, terragucciComponentModule(components));
2531
+ else if (existsSync3(module)) write(module, "// terragucci.components.json is gone; no components.\nexport {};\n");
2407
2532
  return dir;
2408
2533
  }
2534
+ function terraformMemberScan(dir) {
2535
+ const components = readTerragucciComponents(dir);
2536
+ return components ? { scan: declaredRootScan(components), components } : { scan: discoverTerraformRoots(dir) };
2537
+ }
2538
+ function terraformMemberProject(dir) {
2539
+ const { scan, components } = terraformMemberScan(dir);
2540
+ return { project: writeTerraformScratchProject(dir, scan, void 0, "tf", components), ...components ? { components } : {} };
2541
+ }
2409
2542
  function terraformReadDetail(detail) {
2410
2543
  return Math.max(detail ?? 2, 2);
2411
2544
  }
2412
2545
  async function readTerraformMember(dir, opts, state = terraformReaderState()) {
2413
2546
  if (state.refusal) throw new TerraformReadError(state.refusal, dir);
2414
- const scan = discoverTerraformRoots(dir);
2547
+ const { scan, components } = terraformMemberScan(dir);
2415
2548
  if (!scan.roots.length) {
2416
2549
  throw new TerraformReadError(
2417
2550
  {
@@ -2422,7 +2555,7 @@ async function readTerraformMember(dir, opts, state = terraformReaderState()) {
2422
2555
  dir
2423
2556
  );
2424
2557
  }
2425
- const project = writeTerraformScratchProject(dir, scan);
2558
+ const project = writeTerraformScratchProject(dir, scan, void 0, "tf", components);
2426
2559
  const { live: _live, overlay: _overlay, env: _env, ...source2 } = opts;
2427
2560
  const detail = terraformReadDetail(source2.detail);
2428
2561
  const { graphIr: graphIr2 } = await Promise.resolve().then(() => (init_chant(), chant_exports));
@@ -2434,6 +2567,7 @@ var init_terraform_member = __esm({
2434
2567
  "use strict";
2435
2568
  init_terraform_lens();
2436
2569
  init_scratch();
2570
+ init_terragucci_components();
2437
2571
  TERRAFORM_LEXICON_PKG = "@intentius/chant-lexicon-terraform";
2438
2572
  HCL_PARSER_PKG = "@cdktn/hcl2json";
2439
2573
  ROOT_SCAN_DEPTH = 3;
@@ -2975,8 +3109,8 @@ var init_member_kind = __esm({
2975
3109
  });
2976
3110
 
2977
3111
  // src/project.ts
2978
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
2979
- import { join as join4 } from "node:path";
3112
+ import { readFileSync as readFileSync5, existsSync as existsSync4 } from "node:fs";
3113
+ import { join as join5 } from "node:path";
2980
3114
  import { pathToFileURL } from "node:url";
2981
3115
  function readBehaviour(raw) {
2982
3116
  const b = raw.behaviour;
@@ -2997,7 +3131,7 @@ function readMember(v) {
2997
3131
  }
2998
3132
  function chantConfigPath(projectDir) {
2999
3133
  for (const name of CONFIG_NAMES) {
3000
- const p = join4(projectDir, name);
3134
+ const p = join5(projectDir, name);
3001
3135
  if (existsSync4(p)) return p;
3002
3136
  }
3003
3137
  return void 0;
@@ -3091,7 +3225,7 @@ async function detectProject(projectDir) {
3091
3225
  if (info.environments.length || info.lexicons.length || info.sourceDir || info.stacks?.length) return info;
3092
3226
  } catch {
3093
3227
  }
3094
- const content = readFileSync4(path, "utf8");
3228
+ const content = readFileSync5(path, "utf8");
3095
3229
  const sourceDir = parseStringLiteral(content, "sourceDir");
3096
3230
  const environments = parseEnvironmentNames(content);
3097
3231
  return {
@@ -3131,10 +3265,10 @@ function readExecutor(cfg) {
3131
3265
  return Object.keys(out).length ? out : void 0;
3132
3266
  }
3133
3267
  function loadBeholdConfig(projectDir) {
3134
- const path = join4(projectDir, BEHOLD_CONFIG_NAME);
3268
+ const path = join5(projectDir, BEHOLD_CONFIG_NAME);
3135
3269
  if (!existsSync4(path)) return {};
3136
3270
  try {
3137
- const raw = JSON.parse(readFileSync4(path, "utf8"));
3271
+ const raw = JSON.parse(readFileSync5(path, "utf8"));
3138
3272
  const tiers = readTiers(raw);
3139
3273
  const members = Array.isArray(raw.members) ? raw.members.map(readMember).filter((m) => !!m) : [];
3140
3274
  const executor = readExecutor(raw);
@@ -3151,7 +3285,7 @@ function loadBeholdConfig(projectDir) {
3151
3285
  }
3152
3286
  function readWorkspaces(projectDir) {
3153
3287
  try {
3154
- const raw = JSON.parse(readFileSync4(join4(projectDir, "package.json"), "utf8"));
3288
+ const raw = JSON.parse(readFileSync5(join5(projectDir, "package.json"), "utf8"));
3155
3289
  const ws = Array.isArray(raw.workspaces) ? raw.workspaces : raw.workspaces?.packages;
3156
3290
  return Array.isArray(ws) ? ws.filter((w) => typeof w === "string") : [];
3157
3291
  } catch {
@@ -3176,12 +3310,12 @@ function detectProjectShape(projectDir) {
3176
3310
  invalid.push({ dir: m.dir, invalid: `member kind "${m.kind}" has no reader in this behold` });
3177
3311
  continue;
3178
3312
  }
3179
- if (spec2.probe(join4(projectDir, m.dir))) members.push(m);
3313
+ if (spec2.probe(join5(projectDir, m.dir))) members.push(m);
3180
3314
  else invalid.push({ dir: m.dir, invalid: `declared as ${m.kind}, but ${m.dir} has no ${spec2.expects}` });
3181
3315
  }
3182
3316
  } else {
3183
3317
  for (const dir of readWorkspaces(projectDir)) {
3184
- const kind = memberKindOf(join4(projectDir, dir));
3318
+ const kind = memberKindOf(join5(projectDir, dir));
3185
3319
  if (kind) members.push({ dir, kind });
3186
3320
  }
3187
3321
  }
@@ -3213,7 +3347,7 @@ var init_project = __esm({
3213
3347
  // src/member-source.ts
3214
3348
  import { createHash as createHash2 } from "node:crypto";
3215
3349
  import { readdirSync as readdirSync3, statSync } from "node:fs";
3216
- import { join as join5, relative as relative2, resolve as resolve2 } from "node:path";
3350
+ import { join as join6, relative as relative2, resolve as resolve2 } from "node:path";
3217
3351
  function setStampExclusions(next) {
3218
3352
  exclusions = new Map([...next].map(([k, v]) => [resolve2(k), new Set(v.map((d) => resolve2(d)))]));
3219
3353
  }
@@ -3226,7 +3360,7 @@ function memberSourceStamp(dir) {
3226
3360
  const entries = readdirSync3(at2, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1);
3227
3361
  for (const e of entries) {
3228
3362
  if (SKIP.has(e.name)) continue;
3229
- const path = join5(at2, e.name);
3363
+ const path = join6(at2, e.name);
3230
3364
  if (e.isDirectory()) {
3231
3365
  if (!skipDirs?.has(path)) walk(path);
3232
3366
  continue;
@@ -4779,15 +4913,15 @@ var init_yaml = __esm({
4779
4913
  });
4780
4914
 
4781
4915
  // src/carve-manifest.ts
4782
- import { readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync2 } from "node:fs";
4783
- import { dirname as dirname3, isAbsolute, join as join6, relative as relative3, resolve as resolve3, sep as sep2 } from "node:path";
4916
+ import { readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
4917
+ import { dirname as dirname3, isAbsolute, join as join7, relative as relative3, resolve as resolve3, sep as sep2 } from "node:path";
4784
4918
  function parseCarveManifest(value) {
4785
4919
  if (!isRecord4(value)) return null;
4786
4920
  if (value.version !== 1 && value.version !== 2) return null;
4787
4921
  if (typeof value.target !== "string" || !value.target) return null;
4788
4922
  return value;
4789
4923
  }
4790
- function readCarveManifest(path, readFile2 = (p) => readFileSync5(p, "utf8")) {
4924
+ function readCarveManifest(path, readFile2 = (p) => readFileSync6(p, "utf8")) {
4791
4925
  let text;
4792
4926
  try {
4793
4927
  text = readFile2(path);
@@ -4865,7 +4999,7 @@ function listCarveManifests(dir, io = nodeCarveIo, depth = MANIFEST_SCAN_DEPTH)
4865
4999
  return;
4866
5000
  }
4867
5001
  for (const name of entries) {
4868
- const full = join6(d, name);
5002
+ const full = join7(d, name);
4869
5003
  if (name.endsWith(CARVE_MANIFEST_SUFFIX)) {
4870
5004
  out.push(full);
4871
5005
  continue;
@@ -5096,7 +5230,7 @@ var init_carve_manifest = __esm({
5096
5230
  return false;
5097
5231
  }
5098
5232
  },
5099
- readFile: (p) => readFileSync5(p, "utf8")
5233
+ readFile: (p) => readFileSync6(p, "utf8")
5100
5234
  };
5101
5235
  MANIFEST_SCAN_DEPTH = 2;
5102
5236
  SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", "dist", "cdk.out", ".terraform", ".git"]);
@@ -5207,10 +5341,10 @@ __export(chant_exports, {
5207
5341
  stripAnsi: () => stripAnsi
5208
5342
  });
5209
5343
  import { spawn as spawn2 } from "node:child_process";
5210
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
5344
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
5211
5345
  import { createRequire as createRequire2 } from "node:module";
5212
5346
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5213
- import { dirname as dirname4, join as join7, resolve as resolve5 } from "node:path";
5347
+ import { dirname as dirname4, join as join8, resolve as resolve5 } from "node:path";
5214
5348
  import { createHash as createHash3 } from "node:crypto";
5215
5349
  function envOverridesFor(opts) {
5216
5350
  const overrides = { ...opts.spawnEnv ?? {} };
@@ -5250,7 +5384,7 @@ function packageManifestFrom(req, name) {
5250
5384
  let dir = dirname4(entry);
5251
5385
  for (; ; ) {
5252
5386
  try {
5253
- const pkg = createRequire2(import.meta.url)(join7(dir, "package.json"));
5387
+ const pkg = createRequire2(import.meta.url)(join8(dir, "package.json"));
5254
5388
  if (pkg.name === name) return { root: dir, manifest: pkg };
5255
5389
  } catch {
5256
5390
  }
@@ -5264,10 +5398,10 @@ function chantPackageFrom(req) {
5264
5398
  const found = packageManifestFrom(req, "@intentius/chant");
5265
5399
  if (!found) return void 0;
5266
5400
  const { root, manifest } = found;
5267
- return { root, bin: join7(root, manifest.bin?.chant ?? "bin/chant"), ...manifest.version ? { version: manifest.version } : {} };
5401
+ return { root, bin: join8(root, manifest.bin?.chant ?? "bin/chant"), ...manifest.version ? { version: manifest.version } : {} };
5268
5402
  }
5269
5403
  function resolveLexicons(projectDir, lexicons) {
5270
- const req = createRequire2(join7(resolve5(projectDir), "noop.js"));
5404
+ const req = createRequire2(join8(resolve5(projectDir), "noop.js"));
5271
5405
  return lexicons.map((lexicon) => {
5272
5406
  const pkg = `@intentius/chant-lexicon-${lexicon}`;
5273
5407
  const found = packageManifestFrom(req, pkg);
@@ -5280,7 +5414,7 @@ function chantBinFrom(req) {
5280
5414
  function resolveChant(projectDir) {
5281
5415
  const own = chantPackageFrom(createRequire2(import.meta.url));
5282
5416
  if (projectDir) {
5283
- const fromProject = chantPackageFrom(createRequire2(join7(resolve5(projectDir), "noop.js")));
5417
+ const fromProject = chantPackageFrom(createRequire2(join8(resolve5(projectDir), "noop.js")));
5284
5418
  if (fromProject) return { source: fromProject.root === own?.root ? "behold" : "project", ...fromProject };
5285
5419
  }
5286
5420
  if (own) return { source: "behold", ...own };
@@ -5289,7 +5423,7 @@ function resolveChant(projectDir) {
5289
5423
  function chantFloor() {
5290
5424
  try {
5291
5425
  const here = dirname4(fileURLToPath2(import.meta.url));
5292
- const pkg = JSON.parse(readFileSync6(join7(here, "..", "package.json"), "utf8"));
5426
+ const pkg = JSON.parse(readFileSync7(join8(here, "..", "package.json"), "utf8"));
5293
5427
  return pkg.dependencies?.["@intentius/chant"]?.replace(/^[^0-9]*/, "") || void 0;
5294
5428
  } catch {
5295
5429
  return void 0;
@@ -5449,7 +5583,7 @@ function runCommandStream(cmd, args, cwd, onLine) {
5449
5583
  return { pid: proc.pid ?? -1, kill: () => proc.kill(), done };
5450
5584
  }
5451
5585
  function legacyGraphPath(projectDir) {
5452
- const src = join7(projectDir, "src");
5586
+ const src = join8(projectDir, "src");
5453
5587
  return existsSync5(src) ? src : projectDir;
5454
5588
  }
5455
5589
  async function graphPath(projectDir, opts = {}) {
@@ -5475,7 +5609,7 @@ async function graphIr(projectDir, opts = {}) {
5475
5609
  return dropForeignDeclarations(ir, projectDir);
5476
5610
  }
5477
5611
  async function clusterRootGraphIr(projectDir, opts = {}) {
5478
- if (!existsSync5(join7(projectDir, "cluster"))) return void 0;
5612
+ if (!existsSync5(join8(projectDir, "cluster"))) return void 0;
5479
5613
  const { live: _live, overlay: _overlay, env: _env, ...sourceOnly } = opts;
5480
5614
  try {
5481
5615
  return await runChantJson(graphArgs("cluster", "ir", sourceOnly, false), projectDir, envOverridesFor(sourceOnly));
@@ -8904,8 +9038,8 @@ __export(tree_exports, {
8904
9038
  workingTree: () => workingTree
8905
9039
  });
8906
9040
  import { execFileSync as execFileSync2 } from "node:child_process";
8907
- import { readdirSync as readdirSync10, readFileSync as readFileSync19, statSync as statSync8 } from "node:fs";
8908
- import { join as join26 } from "node:path";
9041
+ import { readdirSync as readdirSync10, readFileSync as readFileSync20, statSync as statSync8 } from "node:fs";
9042
+ import { join as join27 } from "node:path";
8909
9043
  function joinPath(...parts) {
8910
9044
  return parts.filter((p) => p !== "" && p !== ".").join("/");
8911
9045
  }
@@ -8914,7 +9048,7 @@ function workingTree(root) {
8914
9048
  label: "",
8915
9049
  stat(path) {
8916
9050
  try {
8917
- const s = statSync8(join26(root, path));
9051
+ const s = statSync8(join27(root, path));
8918
9052
  return s.isDirectory() ? "dir" : s.isFile() ? "file" : void 0;
8919
9053
  } catch {
8920
9054
  return void 0;
@@ -8923,7 +9057,7 @@ function workingTree(root) {
8923
9057
  list(path) {
8924
9058
  let dirents;
8925
9059
  try {
8926
- dirents = readdirSync10(join26(root, path), { withFileTypes: true });
9060
+ dirents = readdirSync10(join27(root, path), { withFileTypes: true });
8927
9061
  } catch {
8928
9062
  return void 0;
8929
9063
  }
@@ -8932,7 +9066,7 @@ function workingTree(root) {
8932
9066
  let type = d.isDirectory() ? "dir" : d.isFile() ? "file" : void 0;
8933
9067
  if (!type && d.isSymbolicLink()) {
8934
9068
  try {
8935
- const s = statSync8(join26(root, path, d.name));
9069
+ const s = statSync8(join27(root, path, d.name));
8936
9070
  type = s.isDirectory() ? "dir" : s.isFile() ? "file" : void 0;
8937
9071
  } catch {
8938
9072
  }
@@ -8942,10 +9076,10 @@ function workingTree(root) {
8942
9076
  return out;
8943
9077
  },
8944
9078
  read(path) {
8945
- return readFileSync19(join26(root, path), "utf-8");
9079
+ return readFileSync20(join27(root, path), "utf-8");
8946
9080
  },
8947
9081
  bytes(path) {
8948
- return readFileSync19(join26(root, path));
9082
+ return readFileSync20(join27(root, path));
8949
9083
  }
8950
9084
  };
8951
9085
  }
@@ -9547,12 +9681,12 @@ var init_identity_gate_rule = __esm({
9547
9681
  });
9548
9682
 
9549
9683
  // node_modules/@intentius/chant/src/workspace/class-data.ts
9550
- import { existsSync as existsSync17, readFileSync as readFileSync20, statSync as statSync9 } from "node:fs";
9551
- import { dirname as dirname7, join as join27, posix, resolve as resolve14 } from "node:path";
9684
+ import { existsSync as existsSync17, readFileSync as readFileSync21, statSync as statSync9 } from "node:fs";
9685
+ import { dirname as dirname7, join as join28, posix, resolve as resolve14 } from "node:path";
9552
9686
  function diskFiles(dir) {
9553
9687
  return (path) => {
9554
9688
  try {
9555
- return readFileSync20(join27(dir, ...path.split("/")), "utf-8");
9689
+ return readFileSync21(join28(dir, ...path.split("/")), "utf-8");
9556
9690
  } catch {
9557
9691
  return void 0;
9558
9692
  }
@@ -9560,9 +9694,9 @@ function diskFiles(dir) {
9560
9694
  }
9561
9695
  function findInstalledPackage(name, fromDir) {
9562
9696
  for (let dir = resolve14(fromDir); ; dir = dirname7(dir)) {
9563
- const candidate = join27(dir, "node_modules", ...name.split("/"));
9697
+ const candidate = join28(dir, "node_modules", ...name.split("/"));
9564
9698
  try {
9565
- if (statSync9(candidate).isDirectory() && existsSync17(join27(candidate, "package.json"))) return candidate;
9699
+ if (statSync9(candidate).isDirectory() && existsSync17(join28(candidate, "package.json"))) return candidate;
9566
9700
  } catch {
9567
9701
  }
9568
9702
  if (dirname7(dir) === dir) return void 0;
@@ -10012,10 +10146,10 @@ var init_jsonc = __esm({
10012
10146
  });
10013
10147
 
10014
10148
  // node_modules/@intentius/chant/src/package-version.ts
10015
- import { readFileSync as readFileSync21 } from "node:fs";
10149
+ import { readFileSync as readFileSync22 } from "node:fs";
10016
10150
  import { fileURLToPath as fileURLToPath4 } from "node:url";
10017
10151
  function readChantPackageVersion() {
10018
- return JSON.parse(readFileSync21(fileURLToPath4(new URL("../package.json", import.meta.url)), "utf-8")).version;
10152
+ return JSON.parse(readFileSync22(fileURLToPath4(new URL("../package.json", import.meta.url)), "utf-8")).version;
10019
10153
  }
10020
10154
  var init_package_version = __esm({
10021
10155
  "node_modules/@intentius/chant/src/package-version.ts"() {
@@ -10814,8 +10948,8 @@ var init_declaration = __esm({
10814
10948
  });
10815
10949
 
10816
10950
  // src/cli.ts
10817
- import { resolve as resolve33, dirname as dirname21, join as join50 } from "node:path";
10818
- import { realpathSync as realpathSync3, existsSync as existsSync30, readFileSync as readFileSync38, rmSync as rmSync4 } from "node:fs";
10951
+ import { resolve as resolve33, dirname as dirname21, join as join51 } from "node:path";
10952
+ import { realpathSync as realpathSync3, existsSync as existsSync30, readFileSync as readFileSync39, rmSync as rmSync4 } from "node:fs";
10819
10953
  import { fileURLToPath as fileURLToPath12 } from "node:url";
10820
10954
  import { spawn as spawn9, spawnSync as spawnSync4 } from "node:child_process";
10821
10955
 
@@ -10873,8 +11007,8 @@ import { serve } from "@hono/node-server";
10873
11007
  import { fileURLToPath as fileURLToPath10 } from "node:url";
10874
11008
  import { execFile as execFile5 } from "node:child_process";
10875
11009
  import { promisify as promisify3 } from "node:util";
10876
- import { existsSync as existsSync27, readFileSync as readFileSync34 } from "node:fs";
10877
- import { basename as basename5, dirname as dirname18, join as join46, relative as relative10, resolve as resolve30, sep as sep9 } from "node:path";
11010
+ import { existsSync as existsSync27, readFileSync as readFileSync35 } from "node:fs";
11011
+ import { basename as basename5, dirname as dirname18, join as join47, relative as relative10, resolve as resolve30, sep as sep9 } from "node:path";
10878
11012
 
10879
11013
  // src/recents.ts
10880
11014
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
@@ -10962,8 +11096,8 @@ function mergeClusterRoot(ir, clusterIr, running) {
10962
11096
  }
10963
11097
 
10964
11098
  // src/helm-releases.ts
10965
- import { existsSync as existsSync6, readdirSync as readdirSync5, readFileSync as readFileSync7, statSync as statSync3 } from "node:fs";
10966
- import { join as join8 } from "node:path";
11099
+ import { existsSync as existsSync6, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
11100
+ import { join as join9 } from "node:path";
10967
11101
  var RELEASE_KEY = /^release\/([^/]+)\/(.+)$/;
10968
11102
  function declaredChartNames(ir) {
10969
11103
  const names = /* @__PURE__ */ new Set();
@@ -10983,12 +11117,12 @@ function matchesDeclaredChart(chart, declared) {
10983
11117
  }
10984
11118
  function discoverReleaseUnits(projectDir) {
10985
11119
  const owners = /* @__PURE__ */ new Map();
10986
- const roots = [join8(projectDir, "src"), join8(projectDir, "ops")];
11120
+ const roots = [join9(projectDir, "src"), join9(projectDir, "ops")];
10987
11121
  const files = [];
10988
11122
  const walk = (dir, depth) => {
10989
11123
  if (depth > 4 || !existsSync6(dir)) return;
10990
11124
  for (const f of readdirSync5(dir)) {
10991
- const p = join8(dir, f);
11125
+ const p = join9(dir, f);
10992
11126
  let s;
10993
11127
  try {
10994
11128
  s = statSync3(p);
@@ -11003,7 +11137,7 @@ function discoverReleaseUnits(projectDir) {
11003
11137
  for (const file of files) {
11004
11138
  let content;
11005
11139
  try {
11006
- content = readFileSync7(file, "utf8");
11140
+ content = readFileSync8(file, "utf8");
11007
11141
  } catch {
11008
11142
  continue;
11009
11143
  }
@@ -11058,8 +11192,8 @@ function synthesizeHelmReleases(ir, observed, owners) {
11058
11192
  init_yaml();
11059
11193
  init_chant();
11060
11194
  import { execFileSync, spawn as spawn3 } from "node:child_process";
11061
- import { readdirSync as readdirSync6, readFileSync as readFileSync8 } from "node:fs";
11062
- import { join as join9 } from "node:path";
11195
+ import { readdirSync as readdirSync6, readFileSync as readFileSync9 } from "node:fs";
11196
+ import { join as join10 } from "node:path";
11063
11197
 
11064
11198
  // src/ci-run.ts
11065
11199
  function pipelineProgress(pipeline) {
@@ -11155,7 +11289,7 @@ function workflowsDir(projectDir) {
11155
11289
  root = execFileSync("git", ["-C", projectDir, "rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || projectDir;
11156
11290
  } catch {
11157
11291
  }
11158
- return join9(root, ".github", "workflows");
11292
+ return join10(root, ".github", "workflows");
11159
11293
  }
11160
11294
  function committedWorkflows(projectDir) {
11161
11295
  const dir = workflowsDir(projectDir);
@@ -11168,7 +11302,7 @@ function committedWorkflows(projectDir) {
11168
11302
  const out = [];
11169
11303
  for (const f of files) {
11170
11304
  try {
11171
- out.push(parseWorkflow(f, readFileSync8(join9(dir, f), "utf8")));
11305
+ out.push(parseWorkflow(f, readFileSync9(join10(dir, f), "utf8")));
11172
11306
  } catch {
11173
11307
  }
11174
11308
  }
@@ -11358,22 +11492,22 @@ function joinCiProgress(ir, state) {
11358
11492
  }
11359
11493
 
11360
11494
  // src/ci-run-store.ts
11361
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync3, rmSync } from "node:fs";
11495
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync3, rmSync } from "node:fs";
11362
11496
  import { homedir as homedir2 } from "node:os";
11363
- import { join as join10, resolve as resolve6 } from "node:path";
11497
+ import { join as join11, resolve as resolve6 } from "node:path";
11364
11498
  function slug(projectDir) {
11365
11499
  return resolve6(projectDir).replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(-80);
11366
11500
  }
11367
11501
  function defaultRoot() {
11368
- return process.env.BEHOLD_CI_RUN_DIR ?? join10(homedir2(), ".behold", "ci-runs");
11502
+ return process.env.BEHOLD_CI_RUN_DIR ?? join11(homedir2(), ".behold", "ci-runs");
11369
11503
  }
11370
11504
  function dispatchedRunPath(projectDir, root = defaultRoot()) {
11371
- return join10(root, `${slug(projectDir)}.json`);
11505
+ return join11(root, `${slug(projectDir)}.json`);
11372
11506
  }
11373
11507
  function saveDispatchedRun(run3, root = defaultRoot()) {
11374
11508
  try {
11375
11509
  const path = dispatchedRunPath(run3.projectDir, root);
11376
- mkdirSync3(join10(path, ".."), { recursive: true });
11510
+ mkdirSync3(join11(path, ".."), { recursive: true });
11377
11511
  writeFileSync3(path, JSON.stringify({ version: 1, ...run3 }, null, 2) + "\n");
11378
11512
  } catch {
11379
11513
  }
@@ -11382,7 +11516,7 @@ function readDispatchedRun(projectDir, root) {
11382
11516
  try {
11383
11517
  const path = dispatchedRunPath(projectDir, root);
11384
11518
  if (!existsSync7(path)) return void 0;
11385
- const parsed = JSON.parse(readFileSync9(path, "utf8"));
11519
+ const parsed = JSON.parse(readFileSync10(path, "utf8"));
11386
11520
  if (parsed?.version !== 1 || typeof parsed.runId !== "number") return void 0;
11387
11521
  if (resolve6(parsed.projectDir) !== resolve6(projectDir)) return void 0;
11388
11522
  return parsed;
@@ -12500,7 +12634,7 @@ init_carve_manifest();
12500
12634
  init_carve_manifest();
12501
12635
  init_chant();
12502
12636
  import { readdirSync as readdirSync7, statSync as statSync4 } from "node:fs";
12503
- import { join as join11 } from "node:path";
12637
+ import { join as join12 } from "node:path";
12504
12638
 
12505
12639
  // src/member-ir.ts
12506
12640
  init_member_source();
@@ -12582,7 +12716,7 @@ function hasAnyCarveManifest(dir, io = fsIo) {
12582
12716
  for (const name of entries) {
12583
12717
  if (name.endsWith(CARVE_MANIFEST_SUFFIX)) return true;
12584
12718
  if (name.startsWith(".") || SKIP2.has(name)) continue;
12585
- const full = join11(d, name);
12719
+ const full = join12(d, name);
12586
12720
  if (io.isDirectory(full) && walk(full)) return true;
12587
12721
  }
12588
12722
  return false;
@@ -12624,7 +12758,7 @@ async function carveStatesUnder(dirs) {
12624
12758
  init_read_scheduler();
12625
12759
  init_chant();
12626
12760
  import { statSync as statSync5 } from "node:fs";
12627
- import { join as join13, resolve as resolve10, sep as sep4 } from "node:path";
12761
+ import { join as join14, resolve as resolve10, sep as sep4 } from "node:path";
12628
12762
  import { composeStacks, shortStackNames } from "@intentius/pinhole";
12629
12763
 
12630
12764
  // src/overlay-ir.ts
@@ -12783,7 +12917,7 @@ init_chant();
12783
12917
  init_floor();
12784
12918
  init_member_source();
12785
12919
  import { existsSync as existsSync8 } from "node:fs";
12786
- import { join as join12, resolve as resolve9 } from "node:path";
12920
+ import { join as join13, resolve as resolve9 } from "node:path";
12787
12921
 
12788
12922
  // src/workspace-reader.ts
12789
12923
  init_chant();
@@ -12855,7 +12989,7 @@ function rootReader(root, env) {
12855
12989
  // src/workspace.ts
12856
12990
  var DECLARATION_FILES = ["chant.workspace.json", "chant.workspace.jsonc"];
12857
12991
  function findDeclaration(dir) {
12858
- const found = DECLARATION_FILES.filter((f) => existsSync8(join12(dir, f)));
12992
+ const found = DECLARATION_FILES.filter((f) => existsSync8(join13(dir, f)));
12859
12993
  if (found.length > 1) return { ambiguous: true };
12860
12994
  return found.length === 1 ? { file: found[0] } : void 0;
12861
12995
  }
@@ -13250,7 +13384,7 @@ function pathAlignment(declared, dir, isDir = realIsDir) {
13250
13384
  const have = root.split(sep4).filter(Boolean);
13251
13385
  for (let k = Math.min(want.length, have.length); k >= 1; k--) {
13252
13386
  if (!have.slice(-k).every((s, i) => s === want[i])) continue;
13253
- if (!isDir(join13(root, ...want.slice(k)))) continue;
13387
+ if (!isDir(join14(root, ...want.slice(k)))) continue;
13254
13388
  return k;
13255
13389
  }
13256
13390
  return 0;
@@ -13280,10 +13414,10 @@ function joinNamespaceBindings(members, isDir = realIsDir) {
13280
13414
  if (!winner || tied) continue;
13281
13415
  const prev = best.get(winner.dir);
13282
13416
  if (prev === "ambiguous") continue;
13283
- const join51 = { dir: winner.dir, namespace: b.namespace, path: b.path, declaredBy: b.declaredBy };
13284
- if (!prev) best.set(winner.dir, { score: winner.score, join: join51 });
13417
+ const join52 = { dir: winner.dir, namespace: b.namespace, path: b.path, declaredBy: b.declaredBy };
13418
+ if (!prev) best.set(winner.dir, { score: winner.score, join: join52 });
13285
13419
  else if (prev.join.namespace !== b.namespace) best.set(winner.dir, "ambiguous");
13286
- else if (winner.score > prev.score) best.set(winner.dir, { score: winner.score, join: join51 });
13420
+ else if (winner.score > prev.score) best.set(winner.dir, { score: winner.score, join: join52 });
13287
13421
  }
13288
13422
  return [...best.values()].filter((v) => v !== "ambiguous").map((v) => v.join);
13289
13423
  }
@@ -14013,7 +14147,7 @@ function projectHelmLogical(ir) {
14013
14147
 
14014
14148
  // src/logical-kustomize.ts
14015
14149
  import { existsSync as existsSync9 } from "node:fs";
14016
- import { join as join14 } from "node:path";
14150
+ import { join as join15 } from "node:path";
14017
14151
  function overlayBoxTitle(name) {
14018
14152
  return `overlay ${name}`;
14019
14153
  }
@@ -14033,7 +14167,7 @@ function kustomizationRoot(bases, dir, exists) {
14033
14167
  let current = dir;
14034
14168
  for (let i = 0; i < 4 && current; i++) {
14035
14169
  for (const base of bases) {
14036
- if (exists(join14(base, current, "kustomization.yaml")) || exists(join14(base, current, "kustomization.yml"))) {
14170
+ if (exists(join15(base, current, "kustomization.yaml")) || exists(join15(base, current, "kustomization.yml"))) {
14037
14171
  return current;
14038
14172
  }
14039
14173
  }
@@ -14738,6 +14872,32 @@ function resourcesByComponent(ir, known) {
14738
14872
  }
14739
14873
  return byComponent;
14740
14874
  }
14875
+ var TERRAFORM_ESTATE_BLOCKS = /* @__PURE__ */ new Set(["resource", "data", "module"]);
14876
+ function terraformBlockOf(n) {
14877
+ const block = n.attrs?.block;
14878
+ if (typeof block === "string") return block;
14879
+ return n.kind.startsWith("Terraform::") ? n.kind.slice("Terraform::".length).toLowerCase() : void 0;
14880
+ }
14881
+ function resourcesByRoot(ir, rootOf2) {
14882
+ const byComponent = {};
14883
+ const componentsOf = /* @__PURE__ */ new Map();
14884
+ for (const [component, root] of Object.entries(rootOf2)) {
14885
+ byComponent[component] = [];
14886
+ componentsOf.set(root, [...componentsOf.get(root) ?? [], component]);
14887
+ }
14888
+ for (const n of ir.nodes) {
14889
+ const root = n.attrs?.root;
14890
+ const block = terraformBlockOf(n);
14891
+ if (typeof root !== "string" || block !== void 0 && !TERRAFORM_ESTATE_BLOCKS.has(block)) continue;
14892
+ for (const component of componentsOf.get(root) ?? []) {
14893
+ byComponent[component].push({ id: n.id, kind: n.kind, lexicon: n.lexicon, physicalId: n.physicalId, ownership: n.ownership });
14894
+ }
14895
+ }
14896
+ return byComponent;
14897
+ }
14898
+
14899
+ // src/server.ts
14900
+ init_terragucci_components();
14741
14901
 
14742
14902
  // src/reconcile.ts
14743
14903
  var DISRUPTION_SEVERITY = {
@@ -14817,17 +14977,17 @@ function summarizePlan(plan, byComponent, nonResource) {
14817
14977
  import {
14818
14978
  layoutIr,
14819
14979
  layoutArchitecture,
14820
- renderSvg,
14980
+ renderSvg as pinholeRenderSvg,
14821
14981
  renderMorphHtml,
14822
14982
  cardSizes,
14823
14983
  registerPack
14824
14984
  } from "@intentius/pinhole";
14825
14985
 
14826
14986
  // src/icon-packs.ts
14827
- import { readFileSync as readFileSync10 } from "node:fs";
14828
- import { dirname as dirname5, join as join15 } from "node:path";
14987
+ import { readFileSync as readFileSync11 } from "node:fs";
14988
+ import { dirname as dirname5, join as join16 } from "node:path";
14829
14989
  import { fileURLToPath as fileURLToPath3 } from "node:url";
14830
- var ICON_ROOT = join15(dirname5(fileURLToPath3(import.meta.url)), "..", "web", "icons");
14990
+ var ICON_ROOT = join16(dirname5(fileURLToPath3(import.meta.url)), "..", "web", "icons");
14831
14991
  var cache4 = /* @__PURE__ */ new Map();
14832
14992
  function unprefix(body) {
14833
14993
  return body.replace(/\s(?:inkscape|sodipodi):[\w-]+\s*=\s*(["'])[\s\S]*?\1/g, "").replace(/\sxlink:href\s*=/g, " href=");
@@ -14849,7 +15009,7 @@ function plate(glyph) {
14849
15009
  function loadIcon(rel) {
14850
15010
  const hit = cache4.get(rel);
14851
15011
  if (hit) return hit;
14852
- const raw = readFileSync10(join15(ICON_ROOT, `${rel}.svg`), "utf8");
15012
+ const raw = readFileSync11(join16(ICON_ROOT, `${rel}.svg`), "utf8");
14853
15013
  const open = raw.indexOf("<svg");
14854
15014
  const openEnd = raw.indexOf(">", open);
14855
15015
  const close = raw.lastIndexOf("</svg");
@@ -15754,7 +15914,332 @@ function stackOrderNoteShort(graph) {
15754
15914
  return bits.join(" \xB7 ");
15755
15915
  }
15756
15916
 
15917
+ // src/edge-route.ts
15918
+ var NUM = "(-?[\\d.]+(?:e[-+]?\\d+)?)";
15919
+ var CARD_PAD = 12;
15920
+ var LABEL_PAD = 3;
15921
+ var GUTTER = 24;
15922
+ var STUB = 16;
15923
+ var RADIUS = 8;
15924
+ var PORT_GAP = 16;
15925
+ var r1 = (n) => Math.round(n * 10) / 10;
15926
+ function svgGeometry(svg) {
15927
+ const cards = /* @__PURE__ */ new Map();
15928
+ const card = new RegExp(`<g data-node-id="([^"]*)"[^>]*><rect x="${NUM}" y="${NUM}" width="${NUM}" height="${NUM}"`, "g");
15929
+ for (const m of svg.matchAll(card)) {
15930
+ if (!cards.has(m[1])) cards.set(m[1], { x: +m[2], y: +m[3], w: +m[4], h: +m[5] });
15931
+ }
15932
+ const rich = new RegExp(`<foreignObject x="${NUM}" y="${NUM}" width="${NUM}" height="${NUM}" data-node-id="([^"]*)"`, "g");
15933
+ for (const m of svg.matchAll(rich)) {
15934
+ if (!cards.has(m[5])) cards.set(m[5], { x: +m[1], y: +m[2], w: +m[3], h: +m[4] });
15935
+ }
15936
+ const labels = [];
15937
+ const box = new RegExp(`<rect(?: data-group-id="[^"]*")? x="${NUM}" y="${NUM}" width="${NUM}" height="${NUM}" rx="16"[^>]*/>((?:<text [^>]*>[^<]*</text>)?)`, "g");
15938
+ for (const m of svg.matchAll(box)) {
15939
+ const [x, y, w] = [+m[1], +m[2], +m[3]];
15940
+ const title = /<text x="(-?[\d.]+)" y="(-?[\d.]+)"[^>]*>([^<]*)<\/text>/.exec(m[5] ?? "");
15941
+ if (title) {
15942
+ const chars = title[3].replace(/&[a-z]+;|&#\d+;/g, "x").length;
15943
+ labels.push({ x: +title[1] - 4, y: +title[2] - 14, w: chars * 7.8 + 8, h: 20 });
15944
+ }
15945
+ labels.push({ x: x + w - 44, y: y + 6, w: 38, h: 26 });
15946
+ }
15947
+ const badge = /<text x="(-?[\d.]+)" y="(-?[\d.]+)" text-anchor="end"[^>]*font-size="11" font-weight="600">([^<]*)<\/text>/g;
15948
+ for (const m of svg.matchAll(badge)) {
15949
+ const chars = m[3].replace(/&[a-z]+;|&#\d+;/g, "x").length;
15950
+ labels.push({ x: +m[1] - chars * 7 - 4, y: +m[2] - 13, w: chars * 7 + 8, h: 18 });
15951
+ }
15952
+ return { cards, labels };
15953
+ }
15954
+ var inflate = (r, p) => ({ x: r.x - p, y: r.y - p, w: r.w + 2 * p, h: r.h + 2 * p });
15955
+ var overlaps = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
15956
+ function segmentHits(p, q, r) {
15957
+ let t0 = 0;
15958
+ let t1 = 1;
15959
+ const dx = q.x - p.x;
15960
+ const dy = q.y - p.y;
15961
+ const clip2 = (den, num3) => {
15962
+ if (den === 0) return num3 >= 0;
15963
+ const t2 = num3 / den;
15964
+ if (den < 0) {
15965
+ if (t2 > t1) return false;
15966
+ if (t2 > t0) t0 = t2;
15967
+ } else {
15968
+ if (t2 < t0) return false;
15969
+ if (t2 < t1) t1 = t2;
15970
+ }
15971
+ return true;
15972
+ };
15973
+ const e = 1e-6;
15974
+ if (!clip2(-dx, p.x - (r.x + e))) return false;
15975
+ if (!clip2(dx, r.x + r.w - e - p.x)) return false;
15976
+ if (!clip2(-dy, p.y - (r.y + e))) return false;
15977
+ if (!clip2(dy, r.y + r.h - e - p.y)) return false;
15978
+ return t1 > t0;
15979
+ }
15980
+ var cubicAt = (p0, p1, p2, p3, t2) => {
15981
+ const u = 1 - t2;
15982
+ const k = (a, b, c, d) => u * u * u * a + 3 * u * u * t2 * b + 3 * u * t2 * t2 * c + t2 * t2 * t2 * d;
15983
+ return { x: k(p0.x, p1.x, p2.x, p3.x), y: k(p0.y, p1.y, p2.y, p3.y) };
15984
+ };
15985
+ function frameOf(dir) {
15986
+ switch (dir) {
15987
+ case "down":
15988
+ return { to: (p) => p, from: (p) => p, rect: (r) => r };
15989
+ case "up":
15990
+ return { to: (p) => ({ x: p.x, y: -p.y }), from: (p) => ({ x: p.x, y: -p.y }), rect: (r) => ({ x: r.x, y: -(r.y + r.h), w: r.w, h: r.h }) };
15991
+ case "right":
15992
+ return { to: (p) => ({ x: p.y, y: p.x }), from: (p) => ({ x: p.y, y: p.x }), rect: (r) => ({ x: r.y, y: r.x, w: r.h, h: r.w }) };
15993
+ case "left":
15994
+ return { to: (p) => ({ x: p.y, y: -p.x }), from: (p) => ({ x: -p.y, y: p.x }), rect: (r) => ({ x: r.y, y: -(r.x + r.w), w: r.h, h: r.w }) };
15995
+ }
15996
+ }
15997
+ var EXIT = {
15998
+ down: ["bottom", "top"],
15999
+ up: ["top", "bottom"],
16000
+ right: ["right", "left"],
16001
+ left: ["left", "right"]
16002
+ };
16003
+ function directionOf2(a, b) {
16004
+ const below = b.y - (a.y + a.h);
16005
+ const above = a.y - (b.y + b.h);
16006
+ const right = b.x - (a.x + a.w);
16007
+ const left = a.x - (b.x + b.w);
16008
+ const v = Math.max(below, above);
16009
+ const h = Math.max(right, left);
16010
+ if (v < 0 && h < 0) return null;
16011
+ if (v >= h) return below >= above ? "down" : "up";
16012
+ return right >= left ? "right" : "left";
16013
+ }
16014
+ var portOn = (r, side, off) => {
16015
+ switch (side) {
16016
+ case "bottom":
16017
+ return { x: r.x + r.w / 2 + off, y: r.y + r.h };
16018
+ case "top":
16019
+ return { x: r.x + r.w / 2 + off, y: r.y };
16020
+ case "left":
16021
+ return { x: r.x, y: r.y + r.h / 2 + off };
16022
+ case "right":
16023
+ return { x: r.x + r.w, y: r.y + r.h / 2 + off };
16024
+ }
16025
+ };
16026
+ function roundedPath(pts) {
16027
+ const clean = pts.filter((p, i) => i === 0 || Math.hypot(p.x - pts[i - 1].x, p.y - pts[i - 1].y) > 0.01);
16028
+ let d = `M ${r1(clean[0].x)} ${r1(clean[0].y)}`;
16029
+ for (let i = 1; i < clean.length - 1; i++) {
16030
+ const [p, c, n] = [clean[i - 1], clean[i], clean[i + 1]];
16031
+ const l1 = Math.hypot(c.x - p.x, c.y - p.y);
16032
+ const l2 = Math.hypot(n.x - c.x, n.y - c.y);
16033
+ const r = Math.min(RADIUS, l1 / 2, l2 / 2);
16034
+ const a = { x: c.x - (c.x - p.x) / l1 * r, y: c.y - (c.y - p.y) / l1 * r };
16035
+ const b = { x: c.x + (n.x - c.x) / l2 * r, y: c.y + (n.y - c.y) / l2 * r };
16036
+ d += ` L ${r1(a.x)} ${r1(a.y)} Q ${r1(c.x)} ${r1(c.y)} ${r1(b.x)} ${r1(b.y)}`;
16037
+ }
16038
+ const last = clean[clean.length - 1];
16039
+ return `${d} L ${r1(last.x)} ${r1(last.y)}`;
16040
+ }
16041
+ function polylineCrossings(pts, blocks) {
16042
+ let n = 0;
16043
+ for (const r of blocks) {
16044
+ for (let i = 1; i < pts.length; i++) {
16045
+ if (segmentHits(pts[i - 1], pts[i], r)) {
16046
+ n++;
16047
+ break;
16048
+ }
16049
+ }
16050
+ }
16051
+ return n;
16052
+ }
16053
+ function routeEdge(input) {
16054
+ const dir = directionOf2(input.from, input.to);
16055
+ if (!dir) return null;
16056
+ const f = frameOf(dir);
16057
+ const [outSide, inSide] = EXIT[dir];
16058
+ const a = f.to(portOn(input.from, outSide, input.fromOffset ?? 0));
16059
+ const b = f.to(portOn(input.to, inSide, input.toOffset ?? 0));
16060
+ const A = f.rect(input.from);
16061
+ const B = f.rect(input.to);
16062
+ const blocks = [
16063
+ ...input.obstacles.map((r) => inflate(f.rect(r), CARD_PAD)),
16064
+ ...(input.labels ?? []).map((r) => inflate(f.rect(r), LABEL_PAD)),
16065
+ inflate(A, -0.5),
16066
+ inflate(B, -0.5)
16067
+ ];
16068
+ const back = (pts2) => pts2.map((p) => f.from(p));
16069
+ const mid = (a.y + b.y) / 2;
16070
+ const c1 = { x: a.x, y: mid };
16071
+ const c2 = { x: b.x, y: mid };
16072
+ const n = Math.max(8, Math.ceil(Math.hypot(b.x - a.x, b.y - a.y) / 4));
16073
+ const curve = [];
16074
+ for (let s = 0; s <= n; s++) curve.push(cubicAt(a, c1, c2, b, s / n));
16075
+ const curveHits = polylineCrossings(curve, blocks);
16076
+ if (curveHits === 0) {
16077
+ const [p0, p1, p2, p3] = back([a, c1, c2, b]);
16078
+ return {
16079
+ d: `M ${r1(p0.x)} ${r1(p0.y)} C ${r1(p1.x)} ${r1(p1.y)}, ${r1(p2.x)} ${r1(p2.y)}, ${r1(p3.x)} ${r1(p3.y)}`,
16080
+ points: back(curve),
16081
+ curved: true,
16082
+ crossings: 0
16083
+ };
16084
+ }
16085
+ const stub = Math.max(4, Math.min(STUB, (b.y - a.y) / 3));
16086
+ const y1 = a.y + stub;
16087
+ const y2 = b.y - stub;
16088
+ const candidates = [];
16089
+ for (const ym of [mid, y1, y2]) candidates.push([a, { x: a.x, y: ym }, { x: b.x, y: ym }, b]);
16090
+ const gutters = /* @__PURE__ */ new Set();
16091
+ const lane = input.fromOffset ?? 0;
16092
+ for (const r of [...blocks.slice(0, -2), inflate(A, CARD_PAD), inflate(B, CARD_PAD)]) {
16093
+ if (r.y + r.h < Math.min(a.y, b.y) - 200 || r.y > Math.max(a.y, b.y) + 200) continue;
16094
+ for (const g of [r.x - GUTTER + CARD_PAD, r.x + r.w + GUTTER - CARD_PAD]) {
16095
+ gutters.add(g + lane);
16096
+ gutters.add(g);
16097
+ }
16098
+ }
16099
+ for (const g of gutters) candidates.push([a, { x: a.x, y: y1 }, { x: g, y: y1 }, { x: g, y: y2 }, { x: b.x, y: y2 }, b]);
16100
+ const length = (pts2) => pts2.slice(1).reduce((s, p, i) => s + Math.abs(p.x - pts2[i].x) + Math.abs(p.y - pts2[i].y), 0);
16101
+ candidates.sort((p, q) => length(p) - length(q));
16102
+ let best = null;
16103
+ for (const pts2 of candidates) {
16104
+ const hits3 = polylineCrossings(pts2, blocks);
16105
+ if (hits3 === 0) {
16106
+ best = { pts: pts2, hits: hits3 };
16107
+ break;
16108
+ }
16109
+ if (!best || hits3 < best.hits) best = { pts: pts2, hits: hits3 };
16110
+ }
16111
+ if (!best || best.hits >= curveHits) {
16112
+ const [p0, p1, p2, p3] = back([a, c1, c2, b]);
16113
+ return {
16114
+ d: `M ${r1(p0.x)} ${r1(p0.y)} C ${r1(p1.x)} ${r1(p1.y)}, ${r1(p2.x)} ${r1(p2.y)}, ${r1(p3.x)} ${r1(p3.y)}`,
16115
+ points: back(curve),
16116
+ curved: true,
16117
+ crossings: curveHits
16118
+ };
16119
+ }
16120
+ const pts = back(best.pts);
16121
+ return { d: roundedPath(pts), points: pts, curved: false, crossings: best.hits };
16122
+ }
16123
+ function chipSpot(points, w, h, blocks) {
16124
+ if (points.length < 2) return null;
16125
+ const cum = [0];
16126
+ for (let i = 1; i < points.length; i++) cum.push(cum[i - 1] + Math.hypot(points[i].x - points[i - 1].x, points[i].y - points[i - 1].y));
16127
+ const total = cum[cum.length - 1];
16128
+ const at2 = (s) => {
16129
+ let i = 1;
16130
+ while (i < cum.length - 1 && cum[i] < s) i++;
16131
+ const seg = cum[i] - cum[i - 1] || 1;
16132
+ const t2 = (s - cum[i - 1]) / seg;
16133
+ return { x: points[i - 1].x + (points[i].x - points[i - 1].x) * t2, y: points[i - 1].y + (points[i].y - points[i - 1].y) * t2 };
16134
+ };
16135
+ const beside = [{ x: 0, y: 0 }, { x: w / 2 + 4, y: 0 }, { x: -(w / 2 + 4), y: 0 }, { x: 0, y: h / 2 + 4 }, { x: 0, y: -(h / 2 + 4) }];
16136
+ for (const nudge of beside) {
16137
+ for (const frac2 of [0.5, 0.4, 0.6, 0.3, 0.7, 0.2, 0.8, 0.12, 0.88]) {
16138
+ const on = at2(total * frac2);
16139
+ const c = { x: on.x + nudge.x, y: on.y + nudge.y };
16140
+ const chip = { x: c.x - w / 2, y: c.y - h / 2, w, h };
16141
+ if (!blocks.some((r) => overlaps(chip, r))) return c;
16142
+ }
16143
+ }
16144
+ return null;
16145
+ }
16146
+ var PORT_REACH = 40;
16147
+ function clearOfLabels(card, side, offsets, labels) {
16148
+ const along = side === "top" || side === "bottom";
16149
+ const len = along ? card.w : card.h;
16150
+ const margin = 14;
16151
+ const lo = -len / 2 + margin - Math.min(...offsets);
16152
+ const hi = len / 2 - margin - Math.max(...offsets);
16153
+ const probe2 = (shift) => offsets.every((off) => {
16154
+ const p = portOn(card, side, off + shift);
16155
+ const reach = along ? { x: p.x - 2, y: side === "top" ? p.y - PORT_REACH : p.y, w: 4, h: PORT_REACH } : { x: side === "left" ? p.x - PORT_REACH : p.x, y: p.y - 2, w: PORT_REACH, h: 4 };
16156
+ return !labels.some((l) => overlaps(reach, inflate(l, LABEL_PAD)));
16157
+ });
16158
+ if (probe2(0)) return 0;
16159
+ for (let step = 4; step <= len; step += 4) {
16160
+ if (step <= hi && probe2(step)) return step;
16161
+ if (-step >= lo && probe2(-step)) return -step;
16162
+ }
16163
+ return 0;
16164
+ }
16165
+ var EDGE = new RegExp(
16166
+ // The line group pinhole's `Canvas.edge` writes, then (optionally) the chip
16167
+ // group `Canvas.edgeLabel` writes straight after it.
16168
+ `<g data-edge-from="([^"]*)" data-edge-to="([^"]*)"([^>]*)><path class="pin-edge-line([^"]*)" d="([^"]*)"([^>]*)/><path d="([^"]*)"([^>]*)/></g>(?:<g data-edge-from="([^"]*)" data-edge-to="([^"]*)"([^>]*)><rect x="${NUM}" y="${NUM}" width="${NUM}" height="${NUM}"([^>]*)/><text x="${NUM}" y="${NUM}")?`,
16169
+ "g"
16170
+ );
16171
+ function routeEdges(svg) {
16172
+ if (!svg.includes("pin-edge-line")) return svg;
16173
+ const geo = svgGeometry(svg);
16174
+ const found = [...svg.matchAll(EDGE)];
16175
+ if (!found.length) return svg;
16176
+ const dirs = found.map((m) => {
16177
+ const a = geo.cards.get(m[1]);
16178
+ const b = geo.cards.get(m[2]);
16179
+ return a && b && m[1] !== m[2] ? directionOf2(a, b) : null;
16180
+ });
16181
+ const sides = /* @__PURE__ */ new Map();
16182
+ const ends = found.map((m, i) => {
16183
+ const dir = dirs[i];
16184
+ if (!dir) return null;
16185
+ const [outSide, inSide] = EXIT[dir];
16186
+ const a = geo.cards.get(m[1]);
16187
+ const b = geo.cards.get(m[2]);
16188
+ const horiz = dir === "down" || dir === "up";
16189
+ const centre = (r) => horiz ? r.x + r.w / 2 : r.y + r.h / 2;
16190
+ const pair = [
16191
+ { edge: i, card: m[1], side: outSide, toward: centre(b), offset: 0 },
16192
+ { edge: i, card: m[2], side: inSide, toward: centre(a), offset: 0 }
16193
+ ];
16194
+ for (const e of pair) {
16195
+ const key = `${e.card}\0${e.side}`;
16196
+ const list3 = sides.get(key) ?? [];
16197
+ list3.push(e);
16198
+ sides.set(key, list3);
16199
+ }
16200
+ return pair;
16201
+ });
16202
+ for (const list3 of sides.values()) {
16203
+ const card = geo.cards.get(list3[0].card);
16204
+ const side = list3[0].side;
16205
+ const len = side === "top" || side === "bottom" ? card.w : card.h;
16206
+ if (list3.length > 1) {
16207
+ const gap = Math.min(PORT_GAP, len * 0.7 / (list3.length - 1));
16208
+ list3.sort((p, q) => p.toward - q.toward || p.edge - q.edge);
16209
+ list3.forEach((e, k2) => e.offset = (k2 - (list3.length - 1) / 2) * gap);
16210
+ }
16211
+ const shift = clearOfLabels(card, side, list3.map((e) => e.offset), geo.labels);
16212
+ if (shift) list3.forEach((e) => e.offset += shift);
16213
+ }
16214
+ const cardRects = [...geo.cards.entries()];
16215
+ const chips = [];
16216
+ let k = -1;
16217
+ return svg.replace(EDGE, (whole, ...g) => {
16218
+ k++;
16219
+ const pair = ends[k];
16220
+ if (!pair) return whole;
16221
+ const [from, to] = [g[0], g[1]];
16222
+ const obstacles = cardRects.filter(([id]) => id !== from && id !== to).map(([, r]) => r);
16223
+ const route = routeEdge({ from: geo.cards.get(from), to: geo.cards.get(to), fromOffset: pair[0].offset, toOffset: pair[1].offset, obstacles, labels: geo.labels });
16224
+ if (!route) return whole;
16225
+ let out = `<g data-edge-from="${from}" data-edge-to="${to}"${g[2]}><path class="pin-edge-line${g[3]}" d="${route.d}"${g[5]}/><path d="${route.d}"${g[7]}/></g>`;
16226
+ if (g[8] === void 0) return out;
16227
+ const [rx, ry, rw, rh, tx, ty] = [g[11], g[12], g[13], g[14], g[16], g[17]].map(Number);
16228
+ const blocks = [...[...geo.cards.values()].map((r) => inflate(r, 2)), ...geo.labels, ...chips];
16229
+ const spot = chipSpot(route.points, rw, rh, blocks);
16230
+ const c = spot ?? { x: rx + rw / 2, y: ry + rh / 2 };
16231
+ const dx = c.x - (rx + rw / 2);
16232
+ const dy = c.y - (ry + rh / 2);
16233
+ chips.push({ x: rx + dx - 2, y: ry + dy - 2, w: rw + 4, h: rh + 4 });
16234
+ out += `<g data-edge-from="${g[8]}" data-edge-to="${g[9]}"${g[10]}><rect x="${r1(rx + dx)}" y="${r1(ry + dy)}" width="${g[13]}" height="${g[14]}"${g[15]}/><text x="${r1(tx + dx)}" y="${r1(ty + dy)}"`;
16235
+ return out;
16236
+ });
16237
+ }
16238
+
15757
16239
  // src/render.ts
16240
+ function renderSvg(...args) {
16241
+ return routeEdges(pinholeRenderSvg(...args));
16242
+ }
15758
16243
  registerPack({ lexicon: "k8s", iconFor: k8sIconFor });
15759
16244
  registerPack({ lexicon: "helm", iconFor: helmIconFor });
15760
16245
  registerPack({ lexicon: SQL_LEXICON, iconFor: sqlIconFor, fields: sqlCardFields });
@@ -15856,6 +16341,12 @@ function renderGraph(irIn, opts = {}) {
15856
16341
  box.title = title === box.id && box.parent !== void 0 && title.startsWith(`${box.parent}/`) ? title.slice(box.parent.length + 1) : title;
15857
16342
  }
15858
16343
  }
16344
+ if (boxKey === "byWave" && opts.waveTitles) {
16345
+ for (const box of layout.groups ?? []) {
16346
+ const title = box.id === void 0 ? void 0 : opts.waveTitles[box.id];
16347
+ if (title !== void 0) box.title = title;
16348
+ }
16349
+ }
15859
16350
  const svg = renderSvg(ir, layout, {
15860
16351
  fit: true,
15861
16352
  hideTitle: true,
@@ -16604,7 +17095,7 @@ function radializeLayout(layout, groupOf, size = /* @__PURE__ */ new Map()) {
16604
17095
  n.y += (k * 7 % 5 - 2) * 0.4;
16605
17096
  });
16606
17097
  for (let iter = 0; iter < 2500; iter++) {
16607
- let overlaps = 0;
17098
+ let overlaps2 = 0;
16608
17099
  for (let a = 0; a < nodes.length; a++) {
16609
17100
  for (let b = a + 1; b < nodes.length; b++) {
16610
17101
  const dx = nodes[b].x - nodes[a].x;
@@ -16614,7 +17105,7 @@ function radializeLayout(layout, groupOf, size = /* @__PURE__ */ new Map()) {
16614
17105
  const ox = sepW - Math.abs(dx);
16615
17106
  const oy = sepH - Math.abs(dy);
16616
17107
  if (ox <= 0 || oy <= 0) continue;
16617
- overlaps++;
17108
+ overlaps2++;
16618
17109
  const d = Math.hypot(dx, dy) || 0.01;
16619
17110
  const push = Math.min(ox, oy) * 0.75 + 2;
16620
17111
  const ux = dx / d;
@@ -16625,7 +17116,7 @@ function radializeLayout(layout, groupOf, size = /* @__PURE__ */ new Map()) {
16625
17116
  nodes[b].y += uy * push;
16626
17117
  }
16627
17118
  }
16628
- if (overlaps === 0) break;
17119
+ if (overlaps2 === 0) break;
16629
17120
  }
16630
17121
  const minX = Math.min(...nodes.map((n) => n.x - wOf(n) / 2));
16631
17122
  const minY = Math.min(...nodes.map((n) => n.y - hOf(n) / 2));
@@ -16653,12 +17144,12 @@ init_carve_manifest();
16653
17144
 
16654
17145
  // src/carve-actions.ts
16655
17146
  init_chant();
16656
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync13, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
16657
- import { isAbsolute as isAbsolute3, join as join18, relative as relative4, resolve as resolve11, sep as sep5 } from "node:path";
17147
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync14, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
17148
+ import { isAbsolute as isAbsolute3, join as join19, relative as relative4, resolve as resolve11, sep as sep5 } from "node:path";
16658
17149
 
16659
17150
  // src/layout.ts
16660
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync11, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync4, accessSync, constants } from "node:fs";
16661
- import { join as join16 } from "node:path";
17151
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync12, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync4, accessSync, constants } from "node:fs";
17152
+ import { join as join17 } from "node:path";
16662
17153
  var LAYOUT_DIR = ".behold";
16663
17154
  var LAYOUT_FILE = "layout.json";
16664
17155
  var MAX_BODY_BYTES = 64 * 1024;
@@ -16667,7 +17158,7 @@ var MAX_IDS_PER_LENS = 2e3;
16667
17158
  var MAX_LENSES = 64;
16668
17159
  var MAX_ID_LENGTH = 512;
16669
17160
  var MAX_LENS_LENGTH = 128;
16670
- var NUM = ["dx", "dy", "dw", "dh"];
17161
+ var NUM2 = ["dx", "dy", "dw", "dh"];
16671
17162
  function slug2(s) {
16672
17163
  return String(s ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
16673
17164
  }
@@ -16684,7 +17175,7 @@ function normalizeDeltas(raw) {
16684
17175
  for (const [id, d] of Object.entries(raw)) {
16685
17176
  if (!id || id.length > MAX_ID_LENGTH || id === "__proto__" || !d || typeof d !== "object") continue;
16686
17177
  const clean = {};
16687
- for (const k of NUM) {
17178
+ for (const k of NUM2) {
16688
17179
  const v = Number(d[k]);
16689
17180
  if (Number.isFinite(v) && v !== 0) clean[k] = v;
16690
17181
  }
@@ -16699,7 +17190,7 @@ function lensFromQuery(params) {
16699
17190
  return [slug2(zoom), params.get("radial") === "1" ? "radial" : null, stack ? slug2(`stack-${stack}`) : null].filter(Boolean).join("+");
16700
17191
  }
16701
17192
  function layoutPath(projectDir) {
16702
- return join16(projectDir, LAYOUT_DIR, LAYOUT_FILE);
17193
+ return join17(projectDir, LAYOUT_DIR, LAYOUT_FILE);
16703
17194
  }
16704
17195
  function unwritableReason(projectDir) {
16705
17196
  if (!projectDir || !existsSync10(projectDir)) return `no such project directory: ${projectDir || "(none)"}`;
@@ -16715,7 +17206,7 @@ function readLayoutFile(projectDir) {
16715
17206
  const file = layoutPath(projectDir);
16716
17207
  try {
16717
17208
  if (!existsSync10(file)) return empty;
16718
- const raw = readFileSync11(file, "utf8");
17209
+ const raw = readFileSync12(file, "utf8");
16719
17210
  if (raw.length > MAX_FILE_BYTES) return empty;
16720
17211
  const parsed = JSON.parse(raw);
16721
17212
  if (!parsed || typeof parsed !== "object" || !parsed.lenses || typeof parsed.lenses !== "object") return empty;
@@ -16755,7 +17246,7 @@ function writeLens(projectDir, lens, deltas) {
16755
17246
  }
16756
17247
  const body = JSON.stringify({ version: 1, lenses }, null, 2) + "\n";
16757
17248
  if (body.length > MAX_FILE_BYTES) throw new LayoutTooLarge(`a layout sidecar is capped at ${MAX_FILE_BYTES} bytes`);
16758
- mkdirSync4(join16(projectDir, LAYOUT_DIR), { recursive: true });
17249
+ mkdirSync4(join17(projectDir, LAYOUT_DIR), { recursive: true });
16759
17250
  const tmp = `${file}.tmp`;
16760
17251
  writeFileSync4(tmp, body, "utf8");
16761
17252
  renameSync(tmp, file);
@@ -16841,8 +17332,8 @@ function reanchorEdges(svg, moved) {
16841
17332
  // src/carve-live.ts
16842
17333
  init_scratch();
16843
17334
  import { spawn as spawn4 } from "node:child_process";
16844
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync5, existsSync as existsSync11 } from "node:fs";
16845
- import { join as join17 } from "node:path";
17335
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync5, existsSync as existsSync11 } from "node:fs";
17336
+ import { join as join18 } from "node:path";
16846
17337
  var LIVE_CONTAINER = "behold-carve-floci";
16847
17338
  var LIVE_PORT = 4602;
16848
17339
  var LIVE_IMAGE = "floci/floci:1.5.34";
@@ -16902,9 +17393,9 @@ async function teardownScratchFloci() {
16902
17393
  await capture("docker", ["rm", "-f", LIVE_CONTAINER], process.cwd()).catch(() => void 0);
16903
17394
  }
16904
17395
  async function applyIntoFloci(fromDir, step) {
16905
- const template = join17(fromDir, "floci-override.tf.disabled");
17396
+ const template = join18(fromDir, "floci-override.tf.disabled");
16906
17397
  if (!existsSync11(template)) return `no floci-override.tf.disabled in ${fromDir} \u2014 this estate wasn't authored for the live tier`;
16907
- writeFileSync5(join17(fromDir, "floci_override.tf"), armOverride(readFileSync12(template, "utf8"), LIVE_PORT));
17398
+ writeFileSync5(join18(fromDir, "floci_override.tf"), armOverride(readFileSync13(template, "utf8"), LIVE_PORT));
16908
17399
  const init = await step("terraform", ["init", "-input=false", "-no-color"], fromDir);
16909
17400
  if (init !== 0) return `terraform init exited ${init} (provider downloads need network)`;
16910
17401
  const apply = await step(
@@ -16954,7 +17445,7 @@ async function runCarveObserve(demo, select) {
16954
17445
  "run `behold demo carve --live` (needs docker + terraform)"
16955
17446
  );
16956
17447
  }
16957
- if (!existsSync12(join18(demo.out, "src"))) {
17448
+ if (!existsSync12(join19(demo.out, "src"))) {
16958
17449
  return refuse6("carve-action", "nothing has been emitted yet", "run Emit first \u2014 observe reads the carveout emit writes");
16959
17450
  }
16960
17451
  const args = ["lifecycle", "diff", "prod", "--live", "--json"];
@@ -17052,7 +17543,7 @@ function readArtifacts(demo, dir, filter) {
17052
17543
  for (const name of entries) {
17053
17544
  if (out.length >= MAX_ARTIFACTS) return;
17054
17545
  if (name === "node_modules" || name.startsWith(".")) continue;
17055
- const full = join18(d, name);
17546
+ const full = join19(d, name);
17056
17547
  let st;
17057
17548
  try {
17058
17549
  st = statSync6(full);
@@ -17067,7 +17558,7 @@ function readArtifacts(demo, dir, filter) {
17067
17558
  if (!filter(rel)) continue;
17068
17559
  let text = "";
17069
17560
  try {
17070
- text = readFileSync13(full, "utf8");
17561
+ text = readFileSync14(full, "utf8");
17071
17562
  } catch {
17072
17563
  continue;
17073
17564
  }
@@ -17099,7 +17590,7 @@ async function runCarveEmit(demo, select) {
17099
17590
  const block = carveWriteBlock(demo);
17100
17591
  if (block) return refuse6("read-only", block, "Start the walkthrough with `behold demo carve`.");
17101
17592
  mkdirSync5(demo.out, { recursive: true });
17102
- const reportFile = join18(demo.out, `${carveSlug(select)}-boundary.json`);
17593
+ const reportFile = join19(demo.out, `${carveSlug(select)}-boundary.json`);
17103
17594
  const args = [
17104
17595
  "carve",
17105
17596
  "emit",
@@ -17128,10 +17619,10 @@ async function runCarveEmit(demo, select) {
17128
17619
  }
17129
17620
  let boundary = null;
17130
17621
  try {
17131
- boundary = JSON.parse(readFileSync13(reportFile, "utf8"));
17622
+ boundary = JSON.parse(readFileSync14(reportFile, "utf8"));
17132
17623
  } catch {
17133
17624
  }
17134
- const lintPath = relative4(demo.project, join18(demo.out, "src")).split(sep5).join("/");
17625
+ const lintPath = relative4(demo.project, join19(demo.out, "src")).split(sep5).join("/");
17135
17626
  const lintArgs = ["lint", lintPath];
17136
17627
  const lint = await runChantRaw(lintArgs, demo.project).catch((err) => ({
17137
17628
  code: 127,
@@ -17194,8 +17685,8 @@ async function runCarveBridge(demo, select) {
17194
17685
  }
17195
17686
 
17196
17687
  // src/ops.ts
17197
- import { readdirSync as readdirSync9, readFileSync as readFileSync14, existsSync as existsSync13 } from "node:fs";
17198
- import { join as join19 } from "node:path";
17688
+ import { readdirSync as readdirSync9, readFileSync as readFileSync15, existsSync as existsSync13 } from "node:fs";
17689
+ import { join as join20 } from "node:path";
17199
17690
  var APPLY_TARGET_LEXICON = {
17200
17691
  cloudformation: "aws",
17201
17692
  kubectl: "k8s",
@@ -17217,11 +17708,11 @@ function discoverOps(projectDir) {
17217
17708
  const seen = /* @__PURE__ */ new Set();
17218
17709
  const out = [];
17219
17710
  for (const sub of ["ops", "src", "."]) {
17220
- const dir = join19(projectDir, sub);
17711
+ const dir = join20(projectDir, sub);
17221
17712
  if (!existsSync13(dir)) continue;
17222
17713
  for (const f of readdirSync9(dir)) {
17223
17714
  if (!f.endsWith(".op.ts")) continue;
17224
- const content = readFileSync14(join19(dir, f), "utf8");
17715
+ const content = readFileSync15(join20(dir, f), "utf8");
17225
17716
  const name = content.match(/name:\s*["'`]([^"'`]+)["'`]/)?.[1];
17226
17717
  if (!name || seen.has(name)) continue;
17227
17718
  seen.add(name);
@@ -17244,13 +17735,13 @@ function discoverOps(projectDir) {
17244
17735
  return out.sort((a, b) => a.name.localeCompare(b.name));
17245
17736
  }
17246
17737
  function opIrPath(projectDir, name) {
17247
- return join19(projectDir, "dist", "ops", name, "op.json");
17738
+ return join20(projectDir, "dist", "ops", name, "op.json");
17248
17739
  }
17249
17740
  function discoverOpIrs(projectDirs) {
17250
17741
  const seen = /* @__PURE__ */ new Set();
17251
17742
  const out = [];
17252
17743
  for (const dir of projectDirs) {
17253
- const root = join19(dir, "dist", "ops");
17744
+ const root = join20(dir, "dist", "ops");
17254
17745
  if (!existsSync13(root)) continue;
17255
17746
  for (const name of readdirSync9(root)) {
17256
17747
  if (seen.has(name)) continue;
@@ -17490,11 +17981,11 @@ var RAN = { ok: "ok", fail: "failed", skipped: "skipped" };
17490
17981
  function tookLabel(ms) {
17491
17982
  return ms < 1e3 ? `${ms}ms` : `${(ms / 1e3).toFixed(1)}s`;
17492
17983
  }
17493
- function paintPlayhead(ir, join51, gate) {
17494
- if (!join51.byNode.size && !join51.at) return ir;
17984
+ function paintPlayhead(ir, join52, gate) {
17985
+ if (!join52.byNode.size && !join52.at) return ir;
17495
17986
  const nodes = ir.nodes.map((node) => {
17496
- const run3 = join51.byNode.get(node.id);
17497
- const here = join51.at === node.id;
17987
+ const run3 = join52.byNode.get(node.id);
17988
+ const here = join52.at === node.id;
17498
17989
  if (!run3 && !here) return node;
17499
17990
  const isGate = node.attrs?._step === "gate";
17500
17991
  const attrs = { ...node.attrs };
@@ -17509,7 +18000,7 @@ function paintPlayhead(ir, join51, gate) {
17509
18000
  if (here) {
17510
18001
  attrs._playhead = true;
17511
18002
  attrs._status = isGate && gate ? "warn" : "accent";
17512
- attrs.run = isGate && gate ? `waiting for approval since ${gate.since}` : join51.atReason ?? "here";
18003
+ attrs.run = isGate && gate ? `waiting for approval since ${gate.since}` : join52.atReason ?? "here";
17513
18004
  if (isGate && gate?.description) attrs.gateDescription = gate.description;
17514
18005
  }
17515
18006
  return { ...node, attrs };
@@ -17531,11 +18022,11 @@ function pendingGateCard(op, gate, ir) {
17531
18022
  }
17532
18023
  };
17533
18024
  }
17534
- function playheadNote(state, join51) {
18025
+ function playheadNote(state, join52) {
17535
18026
  if (!state.op) {
17536
18027
  return "No run state: nothing has been run from this behold session, so every step paints as declared.";
17537
18028
  }
17538
- const settled = join51 ? join51.byNode.size : state.records.length;
18029
+ const settled = join52 ? join52.byNode.size : state.records.length;
17539
18030
  const bits = [];
17540
18031
  const head = state.status === "idle" ? `Run state for ${state.op}: no per-step records \u2014 this behold session didn't start the run, so only the gate query answers` : `Run: ${state.op} (${state.mode === "temporal" ? "durable" : "local executor"}) \u2014 ${settled} step${settled === 1 ? "" : "s"} settled`;
17541
18032
  if (state.status === "running") {
@@ -17551,9 +18042,9 @@ function playheadNote(state, join51) {
17551
18042
  } else if (state.gateNote) {
17552
18043
  bits.push(`gate state unavailable \u2014 ${state.gateNote}`);
17553
18044
  }
17554
- if (join51?.unplaced.length) {
18045
+ if (join52?.unplaced.length) {
17555
18046
  bits.push(
17556
- `${join51.unplaced.length} record${join51.unplaced.length === 1 ? "" : "s"} matched no declared step and ${join51.unplaced.length === 1 ? "is" : "are"} not painted`
18047
+ `${join52.unplaced.length} record${join52.unplaced.length === 1 ? "" : "s"} matched no declared step and ${join52.unplaced.length === 1 ? "is" : "are"} not painted`
17557
18048
  );
17558
18049
  }
17559
18050
  if (!bits.length) return `${head}.`;
@@ -18224,8 +18715,8 @@ var OpRunner = class {
18224
18715
  };
18225
18716
 
18226
18717
  // src/public-mode.ts
18227
- import { readFileSync as readFileSync15 } from "node:fs";
18228
- import { join as join20 } from "node:path";
18718
+ import { readFileSync as readFileSync16 } from "node:fs";
18719
+ import { join as join21 } from "node:path";
18229
18720
  var ReadOnlyRunner = class {
18230
18721
  running = null;
18231
18722
  applyProgress = initialApplyProgress;
@@ -18288,7 +18779,7 @@ function publicReadRefusal(path, query) {
18288
18779
  }
18289
18780
  var PUBLIC_FLAG = "<script>window.__BEHOLD_PUBLIC__ = true;</script>";
18290
18781
  function publicPage(webRoot2) {
18291
- const html = readFileSync15(join20(webRoot2, "index.html"), "utf8");
18782
+ const html = readFileSync16(join21(webRoot2, "index.html"), "utf8");
18292
18783
  return html.replace("</head>", ` ${PUBLIC_FLAG}
18293
18784
  </head>`);
18294
18785
  }
@@ -18305,8 +18796,8 @@ function publicRoutes(app, webRoot2) {
18305
18796
 
18306
18797
  // src/substrates.ts
18307
18798
  import { spawn as spawn5 } from "node:child_process";
18308
- import { existsSync as existsSync14, readFileSync as readFileSync16 } from "node:fs";
18309
- import { join as join21 } from "node:path";
18799
+ import { existsSync as existsSync14, readFileSync as readFileSync17 } from "node:fs";
18800
+ import { join as join22 } from "node:path";
18310
18801
  import { platform } from "node:os";
18311
18802
  var DOCKER_DOWN_DETAIL = "docker is down";
18312
18803
  function probe(cmd, args) {
@@ -18335,11 +18826,11 @@ async function dockerRunning(run3, nameFilter) {
18335
18826
  return out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
18336
18827
  }
18337
18828
  function scriptBringUp(projectDir, relPath, label) {
18338
- return existsSync14(join21(projectDir, relPath)) ? { label, cmd: "bash", args: [relPath] } : void 0;
18829
+ return existsSync14(join22(projectDir, relPath)) ? { label, cmd: "bash", args: [relPath] } : void 0;
18339
18830
  }
18340
18831
  function projectLexicons(projectDir) {
18341
18832
  try {
18342
- const src = readFileSync16(join21(projectDir, "chant.config.ts"), "utf-8");
18833
+ const src = readFileSync17(join22(projectDir, "chant.config.ts"), "utf-8");
18343
18834
  const m = src.match(/lexicons\s*:\s*\[([^\]]*)\]/);
18344
18835
  if (!m) return [];
18345
18836
  return [...m[1].matchAll(/["']([^"']+)["']/g)].map((x) => x[1]);
@@ -18391,7 +18882,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
18391
18882
  ["forgejo", "Forgejo", ".forgejo", "test/forgejo-runtime-e2e.sh"]
18392
18883
  ];
18393
18884
  for (const [name, label, marker, script] of forges) {
18394
- if (!existsSync14(join21(projectDir, marker))) continue;
18885
+ if (!existsSync14(join22(projectDir, marker))) continue;
18395
18886
  const c = docker ? await dockerRunning(run3, name) : [];
18396
18887
  const d = dep(c.length > 0, "container up", "on-demand (pipeline run)");
18397
18888
  subs.push({
@@ -18425,7 +18916,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
18425
18916
  detail: endpoint ? `targeting ${endpoint}` : "real Fly (FLY_FLAPS_BASE_URL unset)"
18426
18917
  });
18427
18918
  }
18428
- if (existsSync14(join21(projectDir, ".github", "workflows"))) {
18919
+ if (existsSync14(join22(projectDir, ".github", "workflows"))) {
18429
18920
  const gh = await run3("gh", ["auth", "status"]);
18430
18921
  const ready = gh.code === 0;
18431
18922
  subs.push({
@@ -18438,7 +18929,7 @@ async function detectSubstrates(projectDir, preview = false, boundContext, probe
18438
18929
  if (lexicons.includes("temporal")) {
18439
18930
  let hasProfiles = false;
18440
18931
  try {
18441
- hasProfiles = /temporal\s*:\s*\{[\s\S]{0,400}?profiles\s*:/.test(readFileSync16(join21(projectDir, "chant.config.ts"), "utf-8"));
18932
+ hasProfiles = /temporal\s*:\s*\{[\s\S]{0,400}?profiles\s*:/.test(readFileSync17(join22(projectDir, "chant.config.ts"), "utf-8"));
18442
18933
  } catch {
18443
18934
  }
18444
18935
  subs.push({
@@ -18581,8 +19072,8 @@ function statusVocabulary(kinds) {
18581
19072
  }
18582
19073
 
18583
19074
  // src/behaviour.ts
18584
- import { readFileSync as readFileSync17 } from "node:fs";
18585
- import { join as join22 } from "node:path";
19075
+ import { readFileSync as readFileSync18 } from "node:fs";
19076
+ import { join as join23 } from "node:path";
18586
19077
  var rec5 = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
18587
19078
  var str6 = (v) => typeof v === "string" && v.trim().length > 0 ? v : void 0;
18588
19079
  var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
@@ -18689,7 +19180,7 @@ var FIRST_TOTAL_SHOWN = "two members stated an estate total; the first is shown"
18689
19180
  var behaviourFileName = (env) => `behaviour.${env}.json`;
18690
19181
  var realRead = (path) => {
18691
19182
  try {
18692
- return readFileSync17(path, "utf8");
19183
+ return readFileSync18(path, "utf8");
18693
19184
  } catch {
18694
19185
  return void 0;
18695
19186
  }
@@ -18747,7 +19238,7 @@ function attachBehaviour(ir, members, env, readFile2 = realRead) {
18747
19238
  }
18748
19239
  const file = behaviourFileName(env);
18749
19240
  looked.push(`${label}/${file}`);
18750
- const raw = readFile2(join22(m.dir, file));
19241
+ const raw = readFile2(join23(m.dir, file));
18751
19242
  if (raw === void 0) {
18752
19243
  sources.set(label, "none");
18753
19244
  continue;
@@ -19183,7 +19674,7 @@ init_choudoufu_live();
19183
19674
  init_choudoufu_member();
19184
19675
  import { mkdtempSync, rmSync as rmSync3 } from "node:fs";
19185
19676
  import { tmpdir as tmpdir2 } from "node:os";
19186
- import { join as join23 } from "node:path";
19677
+ import { join as join24 } from "node:path";
19187
19678
  var isRecord10 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
19188
19679
  var refuse7 = (error, remedy) => ({ ok: false, refusal: { error, code: "choudoufu-live-check", remedy } });
19189
19680
  var CREDS2 = "Give behold the ambient read-only AWS credentials the estate's `plan` needs (AWS_PROFILE, or AWS_ENDPOINT_URL for an emulator), then re-check live with plan.";
@@ -19257,8 +19748,8 @@ var planArgv = (out) => ({
19257
19748
  show: ["show", "-json", out]
19258
19749
  });
19259
19750
  async function readChoudoufuPlan(dir, run3 = captureChoudoufu) {
19260
- const scratch = mkdtempSync(join23(tmpdir2(), "behold-choudoufu-plan-"));
19261
- const file = join23(scratch, "plan.tfplan");
19751
+ const scratch = mkdtempSync(join24(tmpdir2(), "behold-choudoufu-plan-"));
19752
+ const file = join24(scratch, "plan.tfplan");
19262
19753
  const argv = planArgv(file);
19263
19754
  try {
19264
19755
  const planRun = await run3(argv.plan, dir);
@@ -19290,8 +19781,8 @@ init_choudoufu_refs();
19290
19781
  // src/choudoufu-moves.ts
19291
19782
  init_choudoufu_member();
19292
19783
  init_choudoufu_live();
19293
- import { existsSync as existsSync15, readFileSync as readFileSync18 } from "node:fs";
19294
- import { join as join24, relative as relative5 } from "node:path";
19784
+ import { existsSync as existsSync15, readFileSync as readFileSync19 } from "node:fs";
19785
+ import { join as join25, relative as relative5 } from "node:path";
19295
19786
  var isRecord11 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
19296
19787
  var refuse8 = (error, remedy) => ({ ok: false, refusal: { error, code: "choudoufu-live-check", remedy } });
19297
19788
  var HOW_TO_GET_ONE2 = "A plan is the live-mv workbench's carve.json: `{from, estates[], moves[{address, from, to}]}` \u2014 the workbench's plan phase writes one, or write it by hand.";
@@ -19311,7 +19802,7 @@ function parseCarvePlan(value) {
19311
19802
  }
19312
19803
  };
19313
19804
  }
19314
- function readCarvePlan(path, readFile2 = (p) => readFileSync18(p, "utf8")) {
19805
+ function readCarvePlan(path, readFile2 = (p) => readFileSync19(p, "utf8")) {
19315
19806
  let text;
19316
19807
  try {
19317
19808
  text = readFile2(path);
@@ -19327,7 +19818,7 @@ function readCarvePlan(path, readFile2 = (p) => readFileSync18(p, "utf8")) {
19327
19818
  function discoverCarvePlans(memberDirs, base) {
19328
19819
  const out = [];
19329
19820
  for (const dir of memberDirs) {
19330
- const p = join24(dir, "carve.json");
19821
+ const p = join25(dir, "carve.json");
19331
19822
  if (existsSync15(p)) out.push(relative5(base, p));
19332
19823
  }
19333
19824
  return out;
@@ -19558,24 +20049,24 @@ function getRuntime() {
19558
20049
 
19559
20050
  // node_modules/@intentius/chant/src/lifecycle/member-ledger.ts
19560
20051
  import { existsSync as existsSync18 } from "node:fs";
19561
- import { dirname as dirname8, join as join28, relative as relative6, resolve as resolve15, sep as sep6 } from "node:path";
20052
+ import { dirname as dirname8, join as join29, relative as relative6, resolve as resolve15, sep as sep6 } from "node:path";
19562
20053
 
19563
20054
  // node_modules/@intentius/chant/src/project-root.ts
19564
20055
  import { existsSync as existsSync16 } from "fs";
19565
- import { dirname as dirname6, join as join25, resolve as resolve13 } from "path";
20056
+ import { dirname as dirname6, join as join26, resolve as resolve13 } from "path";
19566
20057
  var WORKSPACE_DECLARATION_FILES = ["chant.workspace.json", "chant.workspace.jsonc"];
19567
20058
  function findWorkspaceRoot(startDir) {
19568
20059
  const start = resolve13(startDir);
19569
20060
  let top;
19570
20061
  for (let dir = start; ; dir = dirname6(dir)) {
19571
- if (existsSync16(join25(dir, ".git"))) {
20062
+ if (existsSync16(join26(dir, ".git"))) {
19572
20063
  top = dir;
19573
20064
  break;
19574
20065
  }
19575
20066
  if (dirname6(dir) === dir) break;
19576
20067
  }
19577
20068
  for (let dir = start; ; dir = dirname6(dir)) {
19578
- const [json2, jsonc] = WORKSPACE_DECLARATION_FILES.map((name) => join25(dir, name));
20069
+ const [json2, jsonc] = WORKSPACE_DECLARATION_FILES.map((name) => join26(dir, name));
19579
20070
  const hasJson = existsSync16(json2);
19580
20071
  const hasJsonc = existsSync16(jsonc);
19581
20072
  if (hasJson && hasJsonc) return { dir, file: json2, conflicting: jsonc };
@@ -19637,7 +20128,7 @@ async function ledgerIn(root, dir) {
19637
20128
  }
19638
20129
  let outer = FLAT;
19639
20130
  const parent = dirname8(root);
19640
- if (parent !== root && !existsSync18(join28(root, ".git"))) {
20131
+ if (parent !== root && !existsSync18(join29(root, ".git"))) {
19641
20132
  const outerFound = findWorkspaceRoot(parent);
19642
20133
  if (outerFound) outer = await ledgerIn(outerFound.dir, root);
19643
20134
  }
@@ -20192,7 +20683,7 @@ function decideOverride(input) {
20192
20683
 
20193
20684
  // src/terragucci-repo.ts
20194
20685
  import { existsSync as existsSync19 } from "node:fs";
20195
- import { dirname as dirname9, join as join29, resolve as resolve16 } from "node:path";
20686
+ import { dirname as dirname9, join as join30, resolve as resolve16 } from "node:path";
20196
20687
  var TERRAGUCCI_CONFIG_NAMES = ["terragucci.yml", "terragucci.yaml", "terragucci.json", "terragucci.ts"];
20197
20688
  function terragucciConfig(dir) {
20198
20689
  return walkUp(dir, TERRAGUCCI_CONFIG_NAMES);
@@ -20201,10 +20692,10 @@ function walkUp(dir, names) {
20201
20692
  let at2 = resolve16(dir);
20202
20693
  for (; ; ) {
20203
20694
  for (const name of names) {
20204
- const file = join29(at2, name);
20695
+ const file = join30(at2, name);
20205
20696
  if (existsSync19(file)) return file;
20206
20697
  }
20207
- if (existsSync19(join29(at2, ".git"))) return void 0;
20698
+ if (existsSync19(join30(at2, ".git"))) return void 0;
20208
20699
  const up = dirname9(at2);
20209
20700
  if (up === at2) return void 0;
20210
20701
  at2 = up;
@@ -20638,6 +21129,7 @@ function allowedHostsFrom(flag = [], env = process.env.BEHOLD_ALLOWED_HOSTS) {
20638
21129
  // src/server.ts
20639
21130
  init_member_kind();
20640
21131
  init_terraform_member();
21132
+ init_terraform_lens();
20641
21133
 
20642
21134
  // src/doctor.ts
20643
21135
  init_chant();
@@ -21007,7 +21499,7 @@ init_foreign();
21007
21499
 
21008
21500
  // src/events.ts
21009
21501
  import { watch, existsSync as existsSync20 } from "node:fs";
21010
- import { join as join30 } from "node:path";
21502
+ import { join as join31 } from "node:path";
21011
21503
  var Broadcaster = class {
21012
21504
  listeners = /* @__PURE__ */ new Set();
21013
21505
  subscribe(fn) {
@@ -21025,7 +21517,7 @@ var Broadcaster = class {
21025
21517
  };
21026
21518
  var IGNORE = /(^|[\\/])(node_modules|dist|\.git)([\\/]|$)/;
21027
21519
  function watchSource(projectDir, onChange, debounceMs = 200) {
21028
- const dir = existsSync20(join30(projectDir, "src")) ? join30(projectDir, "src") : projectDir;
21520
+ const dir = existsSync20(join31(projectDir, "src")) ? join31(projectDir, "src") : projectDir;
21029
21521
  let timer;
21030
21522
  const watcher = watch(dir, { recursive: true }, (_event, file) => {
21031
21523
  const name = typeof file === "string" ? file : "";
@@ -21328,13 +21820,13 @@ async function emulatorDown(projectDir) {
21328
21820
 
21329
21821
  // src/terragucci-view.ts
21330
21822
  import { execFileSync as execFileSync3 } from "node:child_process";
21331
- import { dirname as dirname13, join as join39, relative as relative8, sep as sep7 } from "node:path";
21823
+ import { dirname as dirname13, join as join40, relative as relative8, sep as sep7 } from "node:path";
21332
21824
  init_terraform_member();
21333
21825
 
21334
21826
  // src/terragucci-reports.ts
21335
- import { readFileSync as readFileSync23, statSync as statSync10 } from "node:fs";
21827
+ import { readFileSync as readFileSync24, statSync as statSync10 } from "node:fs";
21336
21828
  import { createRequire as createRequire4 } from "node:module";
21337
- import { dirname as dirname10, join as join33, resolve as resolve20 } from "node:path";
21829
+ import { dirname as dirname10, join as join34, resolve as resolve20 } from "node:path";
21338
21830
  import { fileURLToPath as fileURLToPath5 } from "node:url";
21339
21831
  import { Ajv2020 } from "ajv/dist/2020.js";
21340
21832
 
@@ -21343,12 +21835,12 @@ import { execFile as execFile4 } from "node:child_process";
21343
21835
  import { createHash as createHash8 } from "node:crypto";
21344
21836
  import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync11 } from "node:fs";
21345
21837
  import { tmpdir as tmpdir3 } from "node:os";
21346
- import { basename as basename3, isAbsolute as isAbsolute4, join as join32, resolve as resolve19 } from "node:path";
21838
+ import { basename as basename3, isAbsolute as isAbsolute4, join as join33, resolve as resolve19 } from "node:path";
21347
21839
 
21348
21840
  // node_modules/@intentius/chant/src/gate-decision.ts
21349
21841
  import { spawnSync as spawnSync2 } from "node:child_process";
21350
- import { readFileSync as readFileSync22 } from "node:fs";
21351
- import { join as join31, posix as posix3 } from "node:path";
21842
+ import { readFileSync as readFileSync23 } from "node:fs";
21843
+ import { join as join32, posix as posix3 } from "node:path";
21352
21844
 
21353
21845
  // node_modules/@intentius/chant/src/lifecycle/gate-applied.ts
21354
21846
  function appliedGatePath(op) {
@@ -21457,7 +21949,7 @@ function pinnedClassesAt(repo, base, pins, packages) {
21457
21949
  }
21458
21950
  let installed2;
21459
21951
  try {
21460
- installed2 = JSON.parse(readFileSync22(join31(dir, "package.json"), "utf-8")).version;
21952
+ installed2 = JSON.parse(readFileSync23(join32(dir, "package.json"), "utf-8")).version;
21461
21953
  } catch {
21462
21954
  installed2 = void 0;
21463
21955
  }
@@ -22052,7 +22544,7 @@ var realGit = (args, o = {}) => new Promise((res) => {
22052
22544
  });
22053
22545
  var redact = (s) => s.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1");
22054
22546
  function cacheDir(origin, root = tmpdir3()) {
22055
- return join32(root, `${CACHE_PREFIX}${createHash8("sha256").update(origin).digest("hex").slice(0, 16)}`);
22547
+ return join33(root, `${CACHE_PREFIX}${createHash8("sha256").update(origin).digest("hex").slice(0, 16)}`);
22056
22548
  }
22057
22549
  var inflight = /* @__PURE__ */ new Map();
22058
22550
  function serially(key, fn) {
@@ -22071,7 +22563,7 @@ function fetchable(url, checkout) {
22071
22563
  async function fetchIntoCache(git3, origin, cache6) {
22072
22564
  assertScratch(basename3(cache6));
22073
22565
  return serially(cache6, async () => {
22074
- if (!existsSync21(join32(cache6, "HEAD"))) {
22566
+ if (!existsSync21(join33(cache6, "HEAD"))) {
22075
22567
  if (existsSync21(cache6) && readdirSync11(cache6).length > 0) return { ok: false, error: `${cache6} exists and is not behold's lifecycle cache; behold leaves it alone` };
22076
22568
  mkdirSync6(cache6, { recursive: true });
22077
22569
  const init = await git3(["init", "--bare", "-q", cache6]);
@@ -22259,7 +22751,7 @@ function structural(name, doc) {
22259
22751
  var compiledSchemas = /* @__PURE__ */ new Map();
22260
22752
  function schemaStamp(dist) {
22261
22753
  try {
22262
- const files = [join33(dist, "..", "package.json"), ...Object.values(SCHEMA_FILES).map((f) => join33(dist, f))];
22754
+ const files = [join34(dist, "..", "package.json"), ...Object.values(SCHEMA_FILES).map((f) => join34(dist, f))];
22263
22755
  return [dist, ...files.map((f) => {
22264
22756
  const st = statSync10(f);
22265
22757
  return `${st.mtimeMs}:${st.size}`;
@@ -22269,10 +22761,10 @@ function schemaStamp(dist) {
22269
22761
  }
22270
22762
  }
22271
22763
  function compileSchemas(dist) {
22272
- const version = JSON.parse(readFileSync23(join33(dist, "..", "package.json"), "utf8")).version ?? "";
22764
+ const version = JSON.parse(readFileSync24(join34(dist, "..", "package.json"), "utf8")).version ?? "";
22273
22765
  const ajv = new Ajv2020({ strict: false, allErrors: false });
22274
22766
  const compiled4 = Object.fromEntries(
22275
- Object.keys(SCHEMA_FILES).map((n) => [n, ajv.compile(JSON.parse(readFileSync23(join33(dist, SCHEMA_FILES[n]), "utf8")))])
22767
+ Object.keys(SCHEMA_FILES).map((n) => [n, ajv.compile(JSON.parse(readFileSync24(join34(dist, SCHEMA_FILES[n]), "utf8")))])
22276
22768
  );
22277
22769
  return {
22278
22770
  validation: { by: "schema", terragucci: version },
@@ -22289,7 +22781,7 @@ function terragucciValidator(dirs = []) {
22289
22781
  for (const from of [...dirs, dirname10(fileURLToPath5(import.meta.url))]) {
22290
22782
  let entry;
22291
22783
  try {
22292
- entry = createRequire4(join33(resolve20(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json");
22784
+ entry = createRequire4(join34(resolve20(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json");
22293
22785
  } catch {
22294
22786
  continue;
22295
22787
  }
@@ -22630,11 +23122,11 @@ function joinTerragucci(read, nodes, roots) {
22630
23122
  // src/terragucci-source.ts
22631
23123
  import { spawn as spawn7 } from "node:child_process";
22632
23124
  import { readFile } from "node:fs/promises";
22633
- import { join as join34, resolve as resolve21 } from "node:path";
23125
+ import { join as join35, resolve as resolve21 } from "node:path";
22634
23126
 
22635
23127
  // src/s3-object.ts
22636
23128
  import { createHash as createHash9, createHmac } from "node:crypto";
22637
- import { readFileSync as readFileSync24 } from "node:fs";
23129
+ import { readFileSync as readFileSync25 } from "node:fs";
22638
23130
  var S3Error = class extends Error {
22639
23131
  /** The HTTP status S3 answered, when it answered. */
22640
23132
  status;
@@ -22681,7 +23173,7 @@ function s3FromEnv(bucket, env = process.env) {
22681
23173
  async function assumeRole(w, fetchFn) {
22682
23174
  let token;
22683
23175
  try {
22684
- token = readFileSync24(w.tokenFile, "utf8").trim();
23176
+ token = readFileSync25(w.tokenFile, "utf8").trim();
22685
23177
  } catch (e) {
22686
23178
  throw new S3Error(`cannot read the OIDC token in ${w.tokenFile}: ${e.message}`);
22687
23179
  }
@@ -22878,7 +23370,7 @@ function terragucciSource(spec2, opts = {}) {
22878
23370
  location: dir,
22879
23371
  async read(key) {
22880
23372
  try {
22881
- return await readFile(join34(dir, ...trim(key).split("/")), "utf8");
23373
+ return await readFile(join35(dir, ...trim(key).split("/")), "utf8");
22882
23374
  } catch (e) {
22883
23375
  if (e.code === "ENOENT") return void 0;
22884
23376
  throw e;
@@ -22888,9 +23380,9 @@ function terragucciSource(spec2, opts = {}) {
22888
23380
  }
22889
23381
 
22890
23382
  // src/terragucci-timeline.ts
22891
- import { readFileSync as readFileSync25, statSync as statSync11 } from "node:fs";
23383
+ import { readFileSync as readFileSync26, statSync as statSync11 } from "node:fs";
22892
23384
  import { createRequire as createRequire5 } from "node:module";
22893
- import { dirname as dirname11, join as join35, resolve as resolve22 } from "node:path";
23385
+ import { dirname as dirname11, join as join36, resolve as resolve22 } from "node:path";
22894
23386
  import { fileURLToPath as fileURLToPath6 } from "node:url";
22895
23387
  import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
22896
23388
  var AUDIT_SCHEMA = "terragucci.audit/v1";
@@ -22931,12 +23423,12 @@ function auditValidator(dirs = []) {
22931
23423
  for (const from of [...dirs, dirname11(fileURLToPath6(import.meta.url))]) {
22932
23424
  let dist;
22933
23425
  try {
22934
- dist = dirname11(createRequire5(join35(resolve22(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json"));
23426
+ dist = dirname11(createRequire5(join36(resolve22(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json"));
22935
23427
  } catch {
22936
23428
  continue;
22937
23429
  }
22938
- const pkg = join35(dist, "..", "package.json");
22939
- const file = join35(dist, AUDIT_SCHEMA_FILE);
23430
+ const pkg = join36(dist, "..", "package.json");
23431
+ const file = join36(dist, AUDIT_SCHEMA_FILE);
22940
23432
  let stamp;
22941
23433
  try {
22942
23434
  stamp = [dist, ...[pkg, file].map((f) => {
@@ -22950,8 +23442,8 @@ function auditValidator(dirs = []) {
22950
23442
  if (hit) return hit;
22951
23443
  let v;
22952
23444
  try {
22953
- const version = JSON.parse(readFileSync25(pkg, "utf8")).version ?? "";
22954
- v = compileAuditSchema(JSON.parse(readFileSync25(file, "utf8")), version);
23445
+ const version = JSON.parse(readFileSync26(pkg, "utf8")).version ?? "";
23446
+ v = compileAuditSchema(JSON.parse(readFileSync26(file, "utf8")), version);
22955
23447
  } catch {
22956
23448
  continue;
22957
23449
  }
@@ -23147,7 +23639,7 @@ function terragucciTimelineRoute(app, source2, opts = {}) {
23147
23639
  // src/terragucci-poll.ts
23148
23640
  import { createHash as createHash10 } from "node:crypto";
23149
23641
  import { stat } from "node:fs/promises";
23150
- import { join as join36, resolve as resolve23 } from "node:path";
23642
+ import { join as join37, resolve as resolve23 } from "node:path";
23151
23643
  import { streamSSE } from "hono/streaming";
23152
23644
  var TERRAGUCCI_POLL_SECS = 30;
23153
23645
  var INDEX_KEY = "index.json";
@@ -23198,7 +23690,7 @@ function indexProbe(spec2, opts = {}) {
23198
23690
  return verdict2(prev, etag);
23199
23691
  };
23200
23692
  }
23201
- const file = join36(resolve23(opts.cwd ?? process.cwd(), spec2), name);
23693
+ const file = join37(resolve23(opts.cwd ?? process.cwd(), spec2), name);
23202
23694
  return async (prev) => {
23203
23695
  try {
23204
23696
  const s = await stat(file);
@@ -23353,9 +23845,9 @@ function terragucciLaneRoutes(app, o, base, forget) {
23353
23845
  }
23354
23846
 
23355
23847
  // src/terragucci-progress.ts
23356
- import { readFileSync as readFileSync26, statSync as statSync12 } from "node:fs";
23848
+ import { readFileSync as readFileSync27, statSync as statSync12 } from "node:fs";
23357
23849
  import { createRequire as createRequire6 } from "node:module";
23358
- import { dirname as dirname12, join as join37, resolve as resolve24 } from "node:path";
23850
+ import { dirname as dirname12, join as join38, resolve as resolve24 } from "node:path";
23359
23851
  import { fileURLToPath as fileURLToPath7 } from "node:url";
23360
23852
  import { Ajv2020 as Ajv20203 } from "ajv/dist/2020.js";
23361
23853
  var RUN_SCHEMA = "terragucci.run/v1";
@@ -23400,12 +23892,12 @@ function runValidator(dirs = []) {
23400
23892
  for (const from of [...dirs, dirname12(fileURLToPath7(import.meta.url))]) {
23401
23893
  let dist;
23402
23894
  try {
23403
- dist = dirname12(createRequire6(join37(resolve24(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json"));
23895
+ dist = dirname12(createRequire6(join38(resolve24(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json"));
23404
23896
  } catch {
23405
23897
  continue;
23406
23898
  }
23407
- const pkg = join37(dist, "..", "package.json");
23408
- const file = join37(dist, RUN_SCHEMA_FILE);
23899
+ const pkg = join38(dist, "..", "package.json");
23900
+ const file = join38(dist, RUN_SCHEMA_FILE);
23409
23901
  let stamp;
23410
23902
  try {
23411
23903
  stamp = [dist, ...[pkg, file].map((f) => {
@@ -23419,8 +23911,8 @@ function runValidator(dirs = []) {
23419
23911
  if (hit) return hit;
23420
23912
  let v;
23421
23913
  try {
23422
- const version = JSON.parse(readFileSync26(pkg, "utf8")).version ?? "";
23423
- v = compileRunSchema(JSON.parse(readFileSync26(file, "utf8")), version);
23914
+ const version = JSON.parse(readFileSync27(pkg, "utf8")).version ?? "";
23915
+ v = compileRunSchema(JSON.parse(readFileSync27(file, "utf8")), version);
23424
23916
  } catch {
23425
23917
  continue;
23426
23918
  }
@@ -23713,11 +24205,12 @@ function terragucciProgressRoute(app, progress) {
23713
24205
  }
23714
24206
 
23715
24207
  // src/terragucci-demo.ts
23716
- import { readFileSync as readFileSync27 } from "node:fs";
23717
- import { join as join38 } from "node:path";
24208
+ import { readFileSync as readFileSync28 } from "node:fs";
24209
+ import { join as join39 } from "node:path";
23718
24210
  var DEMO_KEY = "demo.json";
23719
24211
  var DEMO_READ_TTL_MS = 2e3;
23720
24212
  var DEMO_STEPS = ["pull-request", "planned", "merged", "applying", "waiting", "approved", "failed", "resumed", "applied", "reset", "stalled"];
24213
+ var DEMO_ABOUT_FIELDS = ["changes", "watch", "shows"];
23721
24214
  var UTC = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
23722
24215
  var isRecord17 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
23723
24216
  var str13 = (v) => typeof v === "string";
@@ -23737,6 +24230,10 @@ function demoProblem(doc) {
23737
24230
  }
23738
24231
  if (doc.pull !== null && !int(doc.pull)) return "pull is neither an integer nor null";
23739
24232
  if (doc.commit !== null && !str13(doc.commit)) return "commit is neither a string nor null";
24233
+ if (doc.about !== void 0 && doc.about !== null) {
24234
+ if (!isRecord17(doc.about)) return "about is neither an object nor null";
24235
+ for (const f of DEMO_ABOUT_FIELDS) if (!str13(doc.about[f]) || !doc.about[f].trim()) return `about.${f} is not a non-empty string`;
24236
+ }
23740
24237
  return void 0;
23741
24238
  }
23742
24239
  async function readDemo(source2, now = /* @__PURE__ */ new Date()) {
@@ -23771,7 +24268,8 @@ async function readDemo(source2, now = /* @__PURE__ */ new Date()) {
23771
24268
  started_at: d.started_at,
23772
24269
  next: d.next ? { action: d.next.action, at: d.next.at } : null,
23773
24270
  pull: d.pull,
23774
- commit: d.commit
24271
+ commit: d.commit,
24272
+ about: d.about ? { changes: d.about.changes, watch: d.about.watch, shows: d.about.shows } : null
23775
24273
  }
23776
24274
  };
23777
24275
  }
@@ -23792,7 +24290,7 @@ function kioskRoute(app, webRoot2, enabled) {
23792
24290
  404
23793
24291
  );
23794
24292
  }
23795
- return new Response(readFileSync27(join38(webRoot2, "kiosk.html"), "utf8"), { headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-cache" } });
24293
+ return new Response(readFileSync28(join39(webRoot2, "kiosk.html"), "utf8"), { headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-cache" } });
23796
24294
  });
23797
24295
  }
23798
24296
 
@@ -23951,7 +24449,7 @@ function checkoutRoots(dirs, base = repoBase(dirs)) {
23951
24449
  for (const m of estateMembers(dirs)) {
23952
24450
  if (m.kind !== "terraform") continue;
23953
24451
  for (const r of discoverTerraformRoots(m.dir).roots) {
23954
- const path = relative8(base, join39(m.dir, r.dir)).split(sep7).join("/") || ".";
24452
+ const path = relative8(base, join40(m.dir, r.dir)).split(sep7).join("/") || ".";
23955
24453
  out.push({ member: m.name, name: r.name, path });
23956
24454
  }
23957
24455
  }
@@ -24511,8 +25009,8 @@ function lifecycleHistoryRoute(app, base, opts = {}) {
24511
25009
  }
24512
25010
 
24513
25011
  // src/lifecycle-releases.ts
24514
- import { existsSync as existsSync22, readFileSync as readFileSync28 } from "node:fs";
24515
- import { join as join40 } from "node:path";
25012
+ import { existsSync as existsSync22, readFileSync as readFileSync29 } from "node:fs";
25013
+ import { join as join41 } from "node:path";
24516
25014
 
24517
25015
  // node_modules/@intentius/chant/src/lifecycle/legacy-digest.ts
24518
25016
  import { resolve as resolve25 } from "node:path";
@@ -24625,10 +25123,10 @@ function memberNote(l) {
24625
25123
  // src/lifecycle-releases.ts
24626
25124
  var WAVES_SPEC_FILE = "yodel-waves.json";
24627
25125
  function waveOrder(checkout) {
24628
- const p = join40(checkout, WAVES_SPEC_FILE);
25126
+ const p = join41(checkout, WAVES_SPEC_FILE);
24629
25127
  if (!existsSync22(p)) return void 0;
24630
25128
  try {
24631
- const doc = JSON.parse(readFileSync28(p, "utf8"));
25129
+ const doc = JSON.parse(readFileSync29(p, "utf8"));
24632
25130
  const names = (doc.waves ?? []).map((w) => w?.name).filter((n) => typeof n === "string" && n.length > 0);
24633
25131
  return names.length ? [...new Set(names)] : void 0;
24634
25132
  } catch {
@@ -24636,7 +25134,7 @@ function waveOrder(checkout) {
24636
25134
  }
24637
25135
  }
24638
25136
  function hasReleaseLedgerHint(checkout) {
24639
- return existsSync22(join40(checkout, "yodel.config.ts")) || existsSync22(join40(checkout, WAVES_SPEC_FILE));
25137
+ return existsSync22(join41(checkout, "yodel.config.ts")) || existsSync22(join41(checkout, WAVES_SPEC_FILE));
24640
25138
  }
24641
25139
  function orderEnvironments(envs, waves) {
24642
25140
  const seen = new Set(envs);
@@ -24678,7 +25176,7 @@ function releaseCells(ledgers, order, sources) {
24678
25176
  }
24679
25177
  async function copyCheckoutRef(git3, checkout, cache6) {
24680
25178
  assertScratch(cache6.split(/[\\/]/).pop());
24681
- if (!existsSync22(join40(cache6, "HEAD"))) {
25179
+ if (!existsSync22(join41(cache6, "HEAD"))) {
24682
25180
  const init = await git3(["init", "--bare", "-q", cache6]);
24683
25181
  if (init.code !== 0) return false;
24684
25182
  }
@@ -24781,8 +25279,8 @@ function releasesRoutes(app, base, opts = {}) {
24781
25279
  }
24782
25280
 
24783
25281
  // src/sql-yodeler-lane.ts
24784
- import { existsSync as existsSync23, readdirSync as readdirSync12, readFileSync as readFileSync29 } from "node:fs";
24785
- import { join as join41 } from "node:path";
25282
+ import { existsSync as existsSync23, readdirSync as readdirSync12, readFileSync as readFileSync30 } from "node:fs";
25283
+ import { join as join42 } from "node:path";
24786
25284
  var YODEL_APPLY_OP = "yodel-apply";
24787
25285
  var YODEL_HEAD_PLANS_DIR = headPlansPath(YODEL_APPLY_OP, "").dir;
24788
25286
  function parseYodelWaves(text, path = WAVES_SPEC_FILE) {
@@ -24814,16 +25312,16 @@ function parseYodelWaves(text, path = WAVES_SPEC_FILE) {
24814
25312
  return { path, waves };
24815
25313
  }
24816
25314
  function readYodelWaves(checkout) {
24817
- const p = join41(checkout, WAVES_SPEC_FILE);
25315
+ const p = join42(checkout, WAVES_SPEC_FILE);
24818
25316
  if (!existsSync23(p)) return void 0;
24819
25317
  try {
24820
- return parseYodelWaves(readFileSync29(p, "utf8"));
25318
+ return parseYodelWaves(readFileSync30(p, "utf8"));
24821
25319
  } catch (e) {
24822
25320
  return { path: WAVES_SPEC_FILE, waves: [], error: e instanceof Error ? e.message : String(e) };
24823
25321
  }
24824
25322
  }
24825
25323
  function migrateOpEnvironments(checkout) {
24826
- const dir = join41(checkout, "ops");
25324
+ const dir = join42(checkout, "ops");
24827
25325
  if (!existsSync23(dir)) return [];
24828
25326
  try {
24829
25327
  return readdirSync12(dir).map((f) => /^migrate-(.+)\.op\.ts$/.exec(f)?.[1]).filter((e) => !!e).sort();
@@ -24946,16 +25444,16 @@ function sqlYodelerLaneRoute(app, checkout, opts = {}) {
24946
25444
 
24947
25445
  // src/terragucci-estate.ts
24948
25446
  init_yaml();
24949
- import { existsSync as existsSync24, readFileSync as readFileSync30, statSync as statSync13 } from "node:fs";
25447
+ import { existsSync as existsSync24, readFileSync as readFileSync31, statSync as statSync13 } from "node:fs";
24950
25448
  import { createRequire as createRequire7 } from "node:module";
24951
- import { dirname as dirname14, extname, join as join42, resolve as resolve26 } from "node:path";
25449
+ import { dirname as dirname14, extname, join as join43, resolve as resolve26 } from "node:path";
24952
25450
  import { fileURLToPath as fileURLToPath8 } from "node:url";
24953
25451
  import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
24954
25452
  var RUN_SCHEMA2 = "terragucci.run/v1";
24955
25453
  function configProjects(file) {
24956
25454
  let text;
24957
25455
  try {
24958
- text = readFileSync30(file, "utf8");
25456
+ text = readFileSync31(file, "utf8");
24959
25457
  } catch {
24960
25458
  return void 0;
24961
25459
  }
@@ -25006,16 +25504,16 @@ function runViewValidator(dirs = []) {
25006
25504
  for (const from of [...dirs, dirname14(fileURLToPath8(import.meta.url))]) {
25007
25505
  let entry;
25008
25506
  try {
25009
- entry = createRequire7(join42(resolve26(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json");
25507
+ entry = createRequire7(join43(resolve26(from), "noop.js")).resolve("@intentius/terragucci/report.schema.json");
25010
25508
  } catch {
25011
25509
  continue;
25012
25510
  }
25013
25511
  const dist = dirname14(entry);
25014
- const schema = join42(dist, "run.schema.json");
25512
+ const schema = join43(dist, "run.schema.json");
25015
25513
  let stamp;
25016
25514
  try {
25017
25515
  const st = statSync13(schema);
25018
- const pkg = statSync13(join42(dist, "..", "package.json"));
25516
+ const pkg = statSync13(join43(dist, "..", "package.json"));
25019
25517
  stamp = `${schema}\0${st.mtimeMs}:${st.size}\0${pkg.mtimeMs}`;
25020
25518
  } catch {
25021
25519
  continue;
@@ -25024,8 +25522,8 @@ function runViewValidator(dirs = []) {
25024
25522
  if (hit) return hit;
25025
25523
  let v;
25026
25524
  try {
25027
- const version = JSON.parse(readFileSync30(join42(dist, "..", "package.json"), "utf8")).version ?? "";
25028
- const validate = new Ajv20204({ strict: false, allErrors: false }).compile(JSON.parse(readFileSync30(schema, "utf8")));
25525
+ const version = JSON.parse(readFileSync31(join43(dist, "..", "package.json"), "utf8")).version ?? "";
25526
+ const validate = new Ajv20204({ strict: false, allErrors: false }).compile(JSON.parse(readFileSync31(schema, "utf8")));
25029
25527
  v = {
25030
25528
  validation: { by: "schema", terragucci: version },
25031
25529
  check(doc) {
@@ -25080,8 +25578,8 @@ function controlEdges(views) {
25080
25578
  function siblingCheckout(project, base, projectOf = checkoutProject) {
25081
25579
  const name = project.split("/").filter(Boolean).pop();
25082
25580
  if (!name) return void 0;
25083
- const dir = join42(dirname14(resolve26(base)), name);
25084
- if (dir === resolve26(base) || !existsSync24(join42(dir, ".git"))) return void 0;
25581
+ const dir = join43(dirname14(resolve26(base)), name);
25582
+ if (dir === resolve26(base) || !existsSync24(join43(dir, ".git"))) return void 0;
25085
25583
  return projectOf(dir) === project ? dir : void 0;
25086
25584
  }
25087
25585
  var shellWord = (s) => /^[A-Za-z0-9_./:@%+=,-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
@@ -25288,9 +25786,9 @@ function controlRepoInfo(dirs) {
25288
25786
  }
25289
25787
 
25290
25788
  // src/yodel-report.ts
25291
- import { readdirSync as readdirSync13, readFileSync as readFileSync31, statSync as statSync14 } from "node:fs";
25789
+ import { readdirSync as readdirSync13, readFileSync as readFileSync32, statSync as statSync14 } from "node:fs";
25292
25790
  import { createRequire as createRequire8 } from "node:module";
25293
- import { basename as basename4, dirname as dirname15, join as join43, resolve as resolve27 } from "node:path";
25791
+ import { basename as basename4, dirname as dirname15, join as join44, resolve as resolve27 } from "node:path";
25294
25792
  import { fileURLToPath as fileURLToPath9 } from "node:url";
25295
25793
  import { Ajv2020 as Ajv20205 } from "ajv/dist/2020.js";
25296
25794
 
@@ -25719,13 +26217,13 @@ var installed = /* @__PURE__ */ new Map();
25719
26217
  function packageRoot(from) {
25720
26218
  let entry;
25721
26219
  try {
25722
- entry = createRequire8(join43(resolve27(from), "noop.js")).resolve("@intentius/sql-yodeler");
26220
+ entry = createRequire8(join44(resolve27(from), "noop.js")).resolve("@intentius/sql-yodeler");
25723
26221
  } catch {
25724
26222
  return void 0;
25725
26223
  }
25726
26224
  for (let d = dirname15(entry); d !== dirname15(d); d = dirname15(d)) {
25727
26225
  try {
25728
- const pkg = JSON.parse(readFileSync31(join43(d, "package.json"), "utf8"));
26226
+ const pkg = JSON.parse(readFileSync32(join44(d, "package.json"), "utf8"));
25729
26227
  if (pkg.name === "@intentius/sql-yodeler") return d;
25730
26228
  } catch {
25731
26229
  }
@@ -25737,11 +26235,11 @@ function yodelValidator(dirs = []) {
25737
26235
  const root = packageRoot(from);
25738
26236
  if (!root) continue;
25739
26237
  try {
25740
- const version = JSON.parse(readFileSync31(join43(root, "package.json"), "utf8")).version ?? "";
26238
+ const version = JSON.parse(readFileSync32(join44(root, "package.json"), "utf8")).version ?? "";
25741
26239
  const key = `${root}\0${version}`;
25742
26240
  let v = installed.get(key);
25743
26241
  if (!v) {
25744
- const load2 = (n) => JSON.parse(readFileSync31(join43(root, "schemas", `${n}.schema.json`), "utf8"));
26242
+ const load2 = (n) => JSON.parse(readFileSync32(join44(root, "schemas", `${n}.schema.json`), "utf8"));
25745
26243
  v = compile({ common: load2("common"), report: load2("report"), drift: load2("drift") }, { by: "schema", from: "package", sqlYodeler: version });
25746
26244
  installed.set(key, v);
25747
26245
  }
@@ -25763,7 +26261,7 @@ function filesOf(source2) {
25763
26261
  return refuse14(`--yodel-report ${source2}: no such file or directory`, "Point --yodel-report at the file `yodel report --json` wrote, or the directory holding it.");
25764
26262
  }
25765
26263
  const dir = st.isDirectory() ? source2 : dirname15(source2);
25766
- const files = readdirSync13(dir).filter((f) => f.endsWith(".json")).sort().map((f) => join43(dir, f));
26264
+ const files = readdirSync13(dir).filter((f) => f.endsWith(".json")).sort().map((f) => join44(dir, f));
25767
26265
  return st.isDirectory() ? { dir, files } : { dir, files, named: resolve27(source2) };
25768
26266
  }
25769
26267
  function readYodelReport(source2, validator2) {
@@ -25775,7 +26273,7 @@ function readYodelReport(source2, validator2) {
25775
26273
  for (const file of files) {
25776
26274
  let doc;
25777
26275
  try {
25778
- doc = JSON.parse(readFileSync31(file, "utf8"));
26276
+ doc = JSON.parse(readFileSync32(file, "utf8"));
25779
26277
  } catch (e) {
25780
26278
  if (file === named) refuse14(`${basename4(file)} is not JSON: ${e instanceof Error ? e.message : String(e)}`, remedy);
25781
26279
  ignored.push(basename4(file));
@@ -25862,15 +26360,15 @@ function yodelReportRoute(app, source2, dirs) {
25862
26360
 
25863
26361
  // src/demo-pins.ts
25864
26362
  init_terraform_member();
25865
- import { existsSync as existsSync25, lstatSync as lstatSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync32, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, writeFileSync as writeFileSync6 } from "node:fs";
25866
- import { dirname as dirname16, join as join44, resolve as resolve28 } from "node:path";
26363
+ import { existsSync as existsSync25, lstatSync as lstatSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync33, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, writeFileSync as writeFileSync6 } from "node:fs";
26364
+ import { dirname as dirname16, join as join45, resolve as resolve28 } from "node:path";
25867
26365
  var isRecord21 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
25868
26366
  function linkDemoPins(work, probes = {}) {
25869
- const file = join44(work, "chant.workspace.json");
26367
+ const file = join45(work, "chant.workspace.json");
25870
26368
  if (!existsSync25(file)) return { ok: true, linked: [] };
25871
26369
  let decl;
25872
26370
  try {
25873
- decl = JSON.parse(readFileSync32(file, "utf8"));
26371
+ decl = JSON.parse(readFileSync33(file, "utf8"));
25874
26372
  } catch {
25875
26373
  return { ok: true, linked: [] };
25876
26374
  }
@@ -25893,7 +26391,7 @@ function linkDemoPins(work, probes = {}) {
25893
26391
  remedy: `Install ${pkg} beside behold, then load the demo again.`
25894
26392
  };
25895
26393
  }
25896
- const link = join44(work, "node_modules", ...pkg.split("/"));
26394
+ const link = join45(work, "node_modules", ...pkg.split("/"));
25897
26395
  if (!present(link)) {
25898
26396
  mkdirSync7(dirname16(link), { recursive: true });
25899
26397
  symlinkSync2(found.dir, link, "junction");
@@ -25928,13 +26426,13 @@ function linksTo(link, target) {
25928
26426
  }
25929
26427
 
25930
26428
  // src/demos.ts
25931
- import { readFileSync as readFileSync33, existsSync as existsSync26, cpSync, mkdirSync as mkdirSync8 } from "node:fs";
25932
- import { dirname as dirname17, isAbsolute as isAbsolute5, join as join45, relative as relative9, resolve as resolve29, sep as sep8 } from "node:path";
26429
+ import { readFileSync as readFileSync34, existsSync as existsSync26, cpSync, mkdirSync as mkdirSync8 } from "node:fs";
26430
+ import { dirname as dirname17, isAbsolute as isAbsolute5, join as join46, relative as relative9, resolve as resolve29, sep as sep8 } from "node:path";
25933
26431
  import { spawn as spawn8, spawnSync as spawnSync3 } from "node:child_process";
25934
26432
  function readCatalog(file, catalog) {
25935
26433
  let raw;
25936
26434
  try {
25937
- raw = JSON.parse(readFileSync33(file, "utf8"));
26435
+ raw = JSON.parse(readFileSync34(file, "utf8"));
25938
26436
  } catch {
25939
26437
  return [];
25940
26438
  }
@@ -25974,10 +26472,10 @@ function validEntry(e) {
25974
26472
  return true;
25975
26473
  }
25976
26474
  function loadDemoRegistry(pkgRoot3) {
25977
- const merged = readCatalog(join45(pkgRoot3, "demos.json"), "demos");
26475
+ const merged = readCatalog(join46(pkgRoot3, "demos.json"), "demos");
25978
26476
  const seen = new Set(merged.map((e) => e.name));
25979
26477
  const named = process.env.BEHOLD_WORKBENCH;
25980
- for (const e of [...readCatalog(join45(pkgRoot3, "workbench.json"), "workbench"), ...named ? readCatalog(resolve29(named), "workbench") : []]) {
26478
+ for (const e of [...readCatalog(join46(pkgRoot3, "workbench.json"), "workbench"), ...named ? readCatalog(resolve29(named), "workbench") : []]) {
25981
26479
  if (seen.has(e.name)) {
25982
26480
  process.stderr.write(`behold: workbench entry "${e.name}" collides with a demo already in the catalog \u2014 dropped
25983
26481
  `);
@@ -26024,7 +26522,7 @@ async function loadDemo(entry, opts) {
26024
26522
  say(`serving ${work} where it is \u2014 nothing copied`);
26025
26523
  } else if (!existsSync26(target)) {
26026
26524
  if (entry.source === "bundled" || entry.source === "local" && local) {
26027
- const from = entry.source === "bundled" ? join45(pkgRoot3, entry.dir) : local;
26525
+ const from = entry.source === "bundled" ? join46(pkgRoot3, entry.dir) : local;
26028
26526
  if (!existsSync26(from)) return { ok: false, error: entry.source === "bundled" ? `this install has no bundled ${entry.dir}` : `${entry.path} is not checked out (looked in ${from})` };
26029
26527
  say(`copying to ${target} (it's yours \u2014 edit it)`);
26030
26528
  try {
@@ -26056,7 +26554,7 @@ async function loadDemo(entry, opts) {
26056
26554
  if (!pins.ok) return { ok: false, error: `${pins.error} ${pins.remedy}` };
26057
26555
  for (const p of pins.linked) say(`linked ${p.pkg} ${p.version} into the copy${p.was ? ` (the declaration pinned ${p.was}; now ${p.version})` : ""}`);
26058
26556
  }
26059
- if (!inPlace && existsSync26(join45(work, "package.json")) && !existsSync26(join45(work, "node_modules"))) {
26557
+ if (!inPlace && existsSync26(join46(work, "package.json")) && !existsSync26(join46(work, "node_modules"))) {
26060
26558
  say("npm install\u2026");
26061
26559
  if (await runStep("npm", ["install"], { cwd: work, shell: process.platform === "win32" }) !== 0) {
26062
26560
  return { ok: false, error: `npm install failed in ${work}` };
@@ -26073,7 +26571,7 @@ async function loadDemo(entry, opts) {
26073
26571
  return { ok: false, error: `setup failed (${entry.setup})` };
26074
26572
  }
26075
26573
  }
26076
- return { ok: true, serveDirs: entry.serve.dirs?.length ? entry.serve.dirs.map((d) => join45(work, d)) : [work] };
26574
+ return { ok: true, serveDirs: entry.serve.dirs?.length ? entry.serve.dirs.map((d) => join46(work, d)) : [work] };
26077
26575
  }
26078
26576
  function runStep(cmd, args, opts = {}) {
26079
26577
  return new Promise((res) => {
@@ -26084,8 +26582,8 @@ function runStep(cmd, args, opts = {}) {
26084
26582
  }
26085
26583
 
26086
26584
  // src/server.ts
26087
- var pkgRoot = join46(dirname18(fileURLToPath10(import.meta.url)), "..");
26088
- var webRoot = join46(pkgRoot, "web");
26585
+ var pkgRoot = join47(dirname18(fileURLToPath10(import.meta.url)), "..");
26586
+ var webRoot = join47(pkgRoot, "web");
26089
26587
  var execFileP = async (cmd, args) => (await promisify3(execFile5)(cmd, args, { encoding: "utf8", timeout: 1e4 })).stdout;
26090
26588
  function optsFromQuery(url, tierEnvVar, projectDir) {
26091
26589
  const q = url.searchParams;
@@ -26136,7 +26634,7 @@ function tierFailure(tier, message2) {
26136
26634
  function beholdVersion() {
26137
26635
  try {
26138
26636
  const here = dirname18(fileURLToPath10(import.meta.url));
26139
- return JSON.parse(readFileSync34(join46(here, "..", "package.json"), "utf8")).version ?? "unknown";
26637
+ return JSON.parse(readFileSync35(join47(here, "..", "package.json"), "utf8")).version ?? "unknown";
26140
26638
  } catch {
26141
26639
  return "unknown";
26142
26640
  }
@@ -26183,7 +26681,7 @@ async function captureFrame(projectDir, env, frames, broadcaster) {
26183
26681
  }
26184
26682
  }
26185
26683
  function carveRoutes(app, reportPath, demo) {
26186
- const load2 = () => readCarveReport(reportPath, (p) => readFileSync34(p, "utf8"));
26684
+ const load2 = () => readCarveReport(reportPath, (p) => readFileSync35(p, "utf8"));
26187
26685
  const carveDirs = [dirname18(reportPath), ...demo ? [demo.out] : []];
26188
26686
  const carveStates = () => readCarveStates(carveDirs, nodeCarveIo, 0);
26189
26687
  const emittedNodes = async (states) => {
@@ -26589,8 +27087,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
26589
27087
  return c.json({ started: true, name, ran: label });
26590
27088
  });
26591
27089
  app.post("/api/local/reset", (c) => {
26592
- const down = join46(cfg.projectDir, "scripts/local/local-down.sh");
26593
- const up = join46(cfg.projectDir, "scripts/local/local-up.sh");
27090
+ const down = join47(cfg.projectDir, "scripts/local/local-down.sh");
27091
+ const up = join47(cfg.projectDir, "scripts/local/local-up.sh");
26594
27092
  if (!existsSync27(down) || !existsSync27(up)) {
26595
27093
  return c.json({ error: "no local-down.sh / local-up.sh in scripts/local \u2014 reset is only for local emulator projects" }, 400);
26596
27094
  }
@@ -26612,7 +27110,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
26612
27110
  const dirs = [];
26613
27111
  const declared = [];
26614
27112
  for (const f of discoverOpIrs(estateDirs)) {
26615
- const parsed = readOpIr(f.path, (p) => readFileSync34(p, "utf8"));
27113
+ const parsed = readOpIr(f.path, (p) => readFileSync35(p, "utf8"));
26616
27114
  if (!parsed.ok) continue;
26617
27115
  const mine = declaredConvergeOps([parsed.ir]);
26618
27116
  if (!mine.length) continue;
@@ -26831,6 +27329,9 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
26831
27329
  // #489: a terragucci repo is applied by its pipeline; the SPA offers no
26832
27330
  // write at all, and the write routes refuse with the same words.
26833
27331
  ...tgConfig() ? { terragucci: { config: tgConfig(), ...terragucciRefusal(tgConfig(), "deploying from behold") } } : {},
27332
+ // #571: the repo declares its roots as components, so the page opens on
27333
+ // the components zoom (web/app.js `bootZoom`).
27334
+ ...servedTerragucciComponents() ? { terragucciComponents: true } : {},
26834
27335
  // #517: a SQL Yodeler repo is migrated by its pipeline; same contract.
26835
27336
  ...(() => {
26836
27337
  const r = guardedRepo();
@@ -26906,7 +27407,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
26906
27407
  const body = await c.req.json().catch(() => ({}));
26907
27408
  const dir = typeof body.dir === "string" && body.dir.trim() ? resolve30(body.dir.trim()) : "";
26908
27409
  if (!dir || !existsSync27(dir)) return c.json({ error: `no such directory: ${dir || "(no dir given)"}` }, 400);
26909
- if (!existsSync27(join46(dir, "chant.config.ts"))) {
27410
+ if (!existsSync27(join47(dir, "chant.config.ts"))) {
26910
27411
  return c.json({ error: `${dir} doesn't look like a chant project \u2014 no chant.config.ts` }, 400);
26911
27412
  }
26912
27413
  switchServedProject([dir]);
@@ -27158,6 +27659,49 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
27158
27659
  ]
27159
27660
  })
27160
27661
  );
27662
+ const servedTerragucciComponents = () => (cfg.projectDirs?.length ?? 1) === 1 && memberKindOf(cfg.projectDir) === "terraform" ? readTerragucciComponents(cfg.projectDir) : void 0;
27663
+ const terragucciResources = async (decl, opts) => {
27664
+ try {
27665
+ const entities = normalizeTerraformNodes(structuredClone(await memberIr(cfg.projectDir, { ...tierTargetOpts(opts), detail: 2 }, terraformVia)));
27666
+ return resourcesByRoot(entities, componentRootMap(decl));
27667
+ } catch {
27668
+ return {};
27669
+ }
27670
+ };
27671
+ const terragucciComponentsGraph = async (decl, opts) => {
27672
+ const state = terraformReaderState();
27673
+ if (state.refusal) throw new TerraformReadError(state.refusal, cfg.projectDir);
27674
+ const { project } = terraformMemberProject(cfg.projectDir);
27675
+ const ir = await componentGraphIr(project, tierTargetOpts(opts));
27676
+ const titles = waveTitles(ir, decl);
27677
+ const { svg } = renderGraph(ir, { waveTitles: titles });
27678
+ const waves = Object.keys(ir.groups.byWave ?? {}).length;
27679
+ const note = `${ir.nodes.length} root${ir.nodes.length === 1 ? "" : "s"} in ${waves} wave${waves === 1 ? "" : "s"}, from ${TERRAGUCCI_COMPONENTS_FILE}`;
27680
+ return {
27681
+ ir,
27682
+ svg,
27683
+ meta: {
27684
+ projectDir: cfg.projectDir,
27685
+ env: null,
27686
+ tier: opts.tier ?? null,
27687
+ target: opts.target ?? null,
27688
+ components: true,
27689
+ note,
27690
+ // The CI facet's source on a terragucci repo: the declared jobs, not a
27691
+ // chant-generated pipeline (`/api/ci`, which a public view refuses).
27692
+ terragucciComponents: {
27693
+ ...decl.pipeline.forge ? { forge: decl.pipeline.forge } : {},
27694
+ ...decl.pipeline.path ? { pipeline: decl.pipeline.path } : {},
27695
+ jobs: Object.values(componentJobs(decl)),
27696
+ // component -> the root it applies: the live marks' join onto the
27697
+ // reports' and run view's roots (web/terragucci-components-live.js).
27698
+ roots: componentRootMap(decl),
27699
+ waveTitles: titles,
27700
+ resources: await terragucciResources(decl, opts)
27701
+ }
27702
+ }
27703
+ };
27704
+ };
27161
27705
  app.get("/api/graph", async (c) => {
27162
27706
  const url = new URL(c.req.url);
27163
27707
  const opts = optsFromQuery(url, tierEnvVar, cfg.projectDir);
@@ -27210,7 +27754,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
27210
27754
  }
27211
27755
  const parsedOps = [];
27212
27756
  for (const f of files) {
27213
- const parsed = readOpIr(f.path, (p) => readFileSync34(p, "utf8"));
27757
+ const parsed = readOpIr(f.path, (p) => readFileSync35(p, "utf8"));
27214
27758
  if (!parsed.ok) return c.json(parsed.refusal, 422);
27215
27759
  parsedOps.push(parsed.ir);
27216
27760
  }
@@ -27223,8 +27767,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
27223
27767
  }
27224
27768
  const runState = runner.runState;
27225
27769
  const ranOp = runState.op ? parsedOps.find((o) => o.name === runState.op) : void 0;
27226
- const join51 = ranOp ? joinRun(ranOp, runState.records, runState.gate) : null;
27227
- const opsIr = join51 ? paintPlayhead(declaredIr, join51, runState.gate) : declaredIr;
27770
+ const join52 = ranOp ? joinRun(ranOp, runState.records, runState.gate) : null;
27771
+ const opsIr = join52 ? paintPlayhead(declaredIr, join52, runState.gate) : declaredIr;
27228
27772
  const { svg: svg2 } = renderGraph(opsIr, { boxes: "byStack" });
27229
27773
  const operator = runner.noteDeclaredConvergeOps(declaredConvergeOps(parsedOps));
27230
27774
  return c.json({
@@ -27237,7 +27781,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
27237
27781
  target: null,
27238
27782
  mode: "ops",
27239
27783
  ops: parsedOps.length,
27240
- note: `${opsNote(parsedOps, opsIr)} ${playheadNote(runState, join51)} ${operatorNote(operator)}`,
27784
+ note: `${opsNote(parsedOps, opsIr)} ${playheadNote(runState, join52)} ${operatorNote(operator)}`,
27241
27785
  // The operator strip's own model: the ConvergeOps this project
27242
27786
  // declares, plus whatever the last status read said. `declared`
27243
27787
  // without a `strip` is the honest starting state — a loop exists and
@@ -27265,6 +27809,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
27265
27809
  // workspace, so behold's own lexicon only speaks for a loose one.
27266
27810
  graph.edges.length === 0 && (multi ? cfg.projectDirs : [cfg.projectDir]).some((d) => memberKindOf(d) === "choudoufu" && !workspaceMemberOf(d)) ? choudoufuLexiconNote() : void 0
27267
27811
  );
27812
+ const tgDecl = components ? servedTerragucciComponents() : void 0;
27813
+ if (tgDecl) return c.json(await terragucciComponentsGraph(tgDecl, opts));
27268
27814
  let ir;
27269
27815
  let mode;
27270
27816
  let metaEnv = cfg.env ?? null;
@@ -27348,7 +27894,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
27348
27894
  srcCompositeEdgesAttached = 0;
27349
27895
  }
27350
27896
  }
27351
- if (!multi && !components && !logical && !opts.lens && !ownKindVia && ir.nodes.length === 0 && !existsSync27(join46(cfg.projectDir, "chant.config.ts"))) {
27897
+ if (!multi && !components && !logical && !opts.lens && !ownKindVia && ir.nodes.length === 0 && !existsSync27(join47(cfg.projectDir, "chant.config.ts"))) {
27352
27898
  return c.json(noProjectError(cfg.projectDir), 404);
27353
27899
  }
27354
27900
  const radial = new URL(c.req.url).searchParams.get("radial") === "1";
@@ -27490,6 +28036,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
27490
28036
  app.get("/api/resources", async (c) => {
27491
28037
  const opts = optsFromQuery(new URL(c.req.url), tierEnvVar, cfg.projectDir);
27492
28038
  const env = opts.env ?? cfg.env;
28039
+ const tgDecl = servedTerragucciComponents();
28040
+ if (tgDecl) return c.json({ byComponent: await terragucciResources(tgDecl, opts) });
27493
28041
  const kind = memberKindOf(cfg.projectDir);
27494
28042
  if (kind && kind !== "chant") return c.json({ byComponent: {} });
27495
28043
  try {
@@ -27985,7 +28533,7 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
27985
28533
  if (!c.req.path.startsWith("/api/") && c.res.ok && !c.res.headers.has("cache-control")) c.res.headers.set("cache-control", "no-cache");
27986
28534
  });
27987
28535
  app.use("/*", serveStatic({ root: rel }));
27988
- app.get("/", serveStatic({ path: join46(rel, "index.html") }));
28536
+ app.get("/", serveStatic({ path: join47(rel, "index.html") }));
27989
28537
  return app;
27990
28538
  }
27991
28539
  async function bootLocalEmulators(dir, who) {
@@ -28014,7 +28562,7 @@ async function bootLocalEmulators(dir, who) {
28014
28562
  async function startServer(cfg) {
28015
28563
  if (cfg.local) cfg.emulators = await bootLocalEmulators(cfg.projectDir, "behold serve --local");
28016
28564
  for (const dir of cfg.projectDirs ?? [cfg.projectDir]) {
28017
- if (existsSync27(join46(dir, "chant.config.ts"))) addRecent(dir);
28565
+ if (existsSync27(join47(dir, "chant.config.ts"))) addRecent(dir);
28018
28566
  }
28019
28567
  const broadcaster = new Broadcaster();
28020
28568
  const frames = new FrameBuffer();
@@ -28188,8 +28736,8 @@ init_chant();
28188
28736
 
28189
28737
  // src/workspace-convert.ts
28190
28738
  init_chant();
28191
- import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as readFileSync35, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
28192
- import { basename as basename6, dirname as dirname19, join as join47, relative as relative11, resolve as resolve31, sep as sep10 } from "node:path";
28739
+ import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as readFileSync36, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
28740
+ import { basename as basename6, dirname as dirname19, join as join48, relative as relative11, resolve as resolve31, sep as sep10 } from "node:path";
28193
28741
  import { shortStackNames as shortStackNames2 } from "@intentius/pinhole";
28194
28742
  init_project();
28195
28743
  init_terraform_member();
@@ -28207,10 +28755,10 @@ function unique(name, taken) {
28207
28755
  var posix4 = (p) => p.split(sep10).join("/");
28208
28756
  function installedVersion(pkg, dir) {
28209
28757
  for (let at2 = resolve31(dir); ; at2 = dirname19(at2)) {
28210
- const manifest = join47(at2, "node_modules", ...pkg.split("/"), "package.json");
28758
+ const manifest = join48(at2, "node_modules", ...pkg.split("/"), "package.json");
28211
28759
  if (existsSync28(manifest)) {
28212
28760
  try {
28213
- const v = JSON.parse(readFileSync35(manifest, "utf8")).version;
28761
+ const v = JSON.parse(readFileSync36(manifest, "utf8")).version;
28214
28762
  return typeof v === "string" ? v : void 0;
28215
28763
  } catch {
28216
28764
  return void 0;
@@ -28221,7 +28769,7 @@ function installedVersion(pkg, dir) {
28221
28769
  }
28222
28770
  function planConversion(rootArg) {
28223
28771
  const root = resolve31(rootArg);
28224
- const existing = DECLARATION_FILES.find((f) => existsSync28(join47(root, f)));
28772
+ const existing = DECLARATION_FILES.find((f) => existsSync28(join48(root, f)));
28225
28773
  if (existing) return { ok: false, error: `${root} already has ${existing}; there is nothing to convert.` };
28226
28774
  const shape = detectProjectShape(root);
28227
28775
  if (shape.kind !== "estate" || !shape.members?.length || shape.membersFrom === "itself") {
@@ -28242,10 +28790,10 @@ function planConversion(rootArg) {
28242
28790
  const scan = discoverTerraformRoots(abs[i]);
28243
28791
  for (const r of scan.roots) {
28244
28792
  const name2 = unique(declarationName(r.name), taken);
28245
- members.push({ name: name2, dir: r.dir === "." ? dir : posix4(join47(dir, r.dir)), kind: "terraform" });
28793
+ members.push({ name: name2, dir: r.dir === "." ? dir : posix4(join48(dir, r.dir)), kind: "terraform" });
28246
28794
  moves.push({ from: `${short}/${r.name}/`, to: `${name2}/${name2}/` });
28247
28795
  }
28248
- for (const s of scan.skipped) notes.push(`${posix4(join47(dir, s.dir))} is not declared: ${s.why}.`);
28796
+ for (const s of scan.skipped) notes.push(`${posix4(join48(dir, s.dir))} is not declared: ${s.why}.`);
28249
28797
  if (scan.roots.length > 1) notes.push(`${dir} held ${scan.roots.length} Terraform roots; each is its own member now, since a chant terraform member is one root.`);
28250
28798
  return;
28251
28799
  }
@@ -28288,7 +28836,7 @@ function moveLayoutIds(file, moves) {
28288
28836
  return { version: 1, lenses };
28289
28837
  }
28290
28838
  function applyConversion(plan) {
28291
- const declarationFile = join47(plan.root, DECLARATION_FILES[0]);
28839
+ const declarationFile = join48(plan.root, DECLARATION_FILES[0]);
28292
28840
  if (existsSync28(declarationFile)) throw new Error(`${declarationFile} appeared while converting; nothing was written`);
28293
28841
  writeFileSync7(declarationFile, `${JSON.stringify(plan.declaration, null, 2)}
28294
28842
  `);
@@ -28299,7 +28847,7 @@ function applyConversion(plan) {
28299
28847
  else if (existsSync28(target)) layout = { skipped: `${target} already exists; the layout in ${plan.layoutFrom} was left alone` };
28300
28848
  else {
28301
28849
  const moved = moveLayoutIds(readLayoutFile(dirname19(dirname19(plan.layoutFrom))), plan.moves);
28302
- mkdirSync9(join47(plan.root, LAYOUT_DIR), { recursive: true });
28850
+ mkdirSync9(join48(plan.root, LAYOUT_DIR), { recursive: true });
28303
28851
  const tmp = `${target}.tmp`;
28304
28852
  writeFileSync7(tmp, `${JSON.stringify(moved, null, 2)}
28305
28853
  `);
@@ -28310,9 +28858,9 @@ function applyConversion(plan) {
28310
28858
  }
28311
28859
 
28312
28860
  // src/export.ts
28313
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, copyFileSync, cpSync as cpSync2, readFileSync as readFileSync36, readdirSync as readdirSync14, existsSync as existsSync29, realpathSync as realpathSync2 } from "node:fs";
28861
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync8, copyFileSync, cpSync as cpSync2, readFileSync as readFileSync37, readdirSync as readdirSync14, existsSync as existsSync29, realpathSync as realpathSync2 } from "node:fs";
28314
28862
  import { homedir as homedir3 } from "node:os";
28315
- import { join as join48, dirname as dirname20, basename as basename7, relative as relative12, resolve as resolve32, sep as sep11 } from "node:path";
28863
+ import { join as join49, dirname as dirname20, basename as basename7, relative as relative12, resolve as resolve32, sep as sep11 } from "node:path";
28316
28864
  import { createHash as createHash11 } from "node:crypto";
28317
28865
  import { fileURLToPath as fileURLToPath11 } from "node:url";
28318
28866
  var LENS_PARAMS = ["collapse", "components", "detail", "env", "logical", "ops", "radial", "stacks", "tier"];
@@ -28369,24 +28917,24 @@ function captureKeys(axes) {
28369
28917
  return [...keys];
28370
28918
  }
28371
28919
  function webDir() {
28372
- return join48(dirname20(fileURLToPath11(import.meta.url)), "..", "web");
28920
+ return join49(dirname20(fileURLToPath11(import.meta.url)), "..", "web");
28373
28921
  }
28374
28922
  function pkgRoot2() {
28375
- return join48(dirname20(fileURLToPath11(import.meta.url)), "..");
28923
+ return join49(dirname20(fileURLToPath11(import.meta.url)), "..");
28376
28924
  }
28377
28925
  var NOTICE_FILES = ["LICENSE", "THIRD_PARTY.md"];
28378
28926
  function copyNotices(outDir, root = pkgRoot2()) {
28379
28927
  const copied = [];
28380
28928
  for (const f of NOTICE_FILES) {
28381
- if (!existsSync29(join48(root, f))) continue;
28382
- copyFileSync(join48(root, f), join48(outDir, f));
28929
+ if (!existsSync29(join49(root, f))) continue;
28930
+ copyFileSync(join49(root, f), join49(outDir, f));
28383
28931
  copied.push(f);
28384
28932
  }
28385
- const lic = join48(root, "licenses");
28933
+ const lic = join49(root, "licenses");
28386
28934
  if (existsSync29(lic)) {
28387
- mkdirSync10(join48(outDir, "licenses"), { recursive: true });
28935
+ mkdirSync10(join49(outDir, "licenses"), { recursive: true });
28388
28936
  for (const f of readdirSync14(lic)) {
28389
- copyFileSync(join48(lic, f), join48(outDir, "licenses", f));
28937
+ copyFileSync(join49(lic, f), join49(outDir, "licenses", f));
28390
28938
  copied.push(`licenses/${f}`);
28391
28939
  }
28392
28940
  }
@@ -28411,7 +28959,7 @@ async function runExport(cfg, outDir, opts = {}) {
28411
28959
  ...proj.sqlYodeler ? { sqlYodeler: true } : {}
28412
28960
  };
28413
28961
  const scrub = exportScrubber(cfg);
28414
- const snapDir = join48(outDir, "snapshots");
28962
+ const snapDir = join49(outDir, "snapshots");
28415
28963
  mkdirSync10(snapDir, { recursive: true });
28416
28964
  const keyToFile = {};
28417
28965
  let ok = 0;
@@ -28420,7 +28968,7 @@ async function runExport(cfg, outDir, opts = {}) {
28420
28968
  const res = await app.request(`${key}${key.includes("?") ? "&" : "?"}layout=1`);
28421
28969
  const body = scrub(shapeSnapshot(key, await res.text(), opts));
28422
28970
  const file = slug3(key, body);
28423
- writeFileSync8(join48(snapDir, file), body);
28971
+ writeFileSync8(join49(snapDir, file), body);
28424
28972
  keyToFile[key] = `snapshots/${file}`;
28425
28973
  if (res.ok) ok++;
28426
28974
  else failed++;
@@ -28442,29 +28990,29 @@ async function runExport(cfg, outDir, opts = {}) {
28442
28990
  axes,
28443
28991
  keyToFile
28444
28992
  };
28445
- writeFileSync8(join48(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
28446
- const html = readFileSync36(join48(webDir(), "index.html"), "utf8").replace(
28993
+ writeFileSync8(join49(outDir, "manifest.json"), JSON.stringify(manifest, null, 2));
28994
+ const html = readFileSync37(join49(webDir(), "index.html"), "utf8").replace(
28447
28995
  /<\/head>/i,
28448
28996
  ` <script>window.__BEHOLD_STATIC__ = true;</script>
28449
28997
  </head>`
28450
28998
  );
28451
- writeFileSync8(join48(outDir, "index.html"), html);
28999
+ writeFileSync8(join49(outDir, "index.html"), html);
28452
29000
  for (const f of readdirSync14(webDir())) {
28453
29001
  if (f === "index.html" || f.endsWith(".test.js")) continue;
28454
- cpSync2(join48(webDir(), f), join48(outDir, f), { recursive: true });
29002
+ cpSync2(join49(webDir(), f), join49(outDir, f), { recursive: true });
28455
29003
  }
28456
- writeFileSync8(join48(outDir, "README.md"), BUNDLE_README);
29004
+ writeFileSync8(join49(outDir, "README.md"), BUNDLE_README);
28457
29005
  copyNotices(outDir);
28458
29006
  const name = workerName(cfg.projectDir, opts.name);
28459
29007
  writeFileSync8(
28460
- join48(outDir, "wrangler.jsonc"),
29008
+ join49(outDir, "wrangler.jsonc"),
28461
29009
  JSON.stringify(
28462
29010
  { $schema: "node_modules/wrangler/config-schema.json", name, compatibility_date: "2025-06-01", assets: { directory: "." } },
28463
29011
  null,
28464
29012
  2
28465
29013
  ) + "\n"
28466
29014
  );
28467
- writeFileSync8(join48(outDir, "manifest.json"), JSON.stringify({ ...manifest, files: bundleFiles(outDir).filter((f) => f !== "manifest.json") }, null, 2));
29015
+ writeFileSync8(join49(outDir, "manifest.json"), JSON.stringify({ ...manifest, files: bundleFiles(outDir).filter((f) => f !== "manifest.json") }, null, 2));
28468
29016
  process.stdout.write(
28469
29017
  `behold export \u2192 ${outDir}
28470
29018
  ${ok} snapshots${failed ? ` (${failed} endpoint error(s) captured as-is)` : ""}
@@ -28561,7 +29109,7 @@ function manifestFiles(text) {
28561
29109
  return Object.values(map).filter((f) => typeof f === "string" && /^snapshots\/[A-Za-z0-9=_.-]+$/.test(f) && !f.includes(".."));
28562
29110
  }
28563
29111
  function bundleFiles(outDir) {
28564
- return readdirSync14(outDir, { recursive: true, withFileTypes: true }).filter((d) => d.isFile()).map((d) => relative12(outDir, join48(d.parentPath, d.name)).split(sep11).join("/")).sort();
29112
+ return readdirSync14(outDir, { recursive: true, withFileTypes: true }).filter((d) => d.isFile()).map((d) => relative12(outDir, join49(d.parentPath, d.name)).split(sep11).join("/")).sort();
28565
29113
  }
28566
29114
  async function publishBundle(outDir, target, client) {
28567
29115
  const files = bundleFiles(outDir);
@@ -28577,7 +29125,7 @@ async function publishBundle(outDir, target, client) {
28577
29125
  }
28578
29126
  const last = ["manifest.json", "index.html"];
28579
29127
  const order = [...files.filter((f) => !last.includes(f)), ...last.filter((f) => files.includes(f))];
28580
- for (const f of order) await client.put(at2(f), readFileSync36(join48(outDir, f)), contentType(f), CONTENT_ADDRESSED.test(f) ? IMMUTABLE : NO_CACHE);
29128
+ for (const f of order) await client.put(at2(f), readFileSync37(join49(outDir, f)), contentType(f), CONTENT_ADDRESSED.test(f) ? IMMUTABLE : NO_CACHE);
28581
29129
  const wrote = new Set(files);
28582
29130
  const removed = [];
28583
29131
  for (const f of previous) {
@@ -28629,8 +29177,8 @@ It's just files \u2014 GitHub Pages, S3, nginx, or Cloudflare Pages
28629
29177
 
28630
29178
  // src/publish-snapshots.ts
28631
29179
  import { execFileSync as execFileSync4 } from "node:child_process";
28632
- import { readFileSync as readFileSync37 } from "node:fs";
28633
- import { join as join49 } from "node:path";
29180
+ import { readFileSync as readFileSync38 } from "node:fs";
29181
+ import { join as join50 } from "node:path";
28634
29182
  var DEFAULT_KEEP = 30;
28635
29183
  var SNAPSHOT_REPORTS_BASE = "../../../";
28636
29184
  var COMMIT = /^[0-9a-f]{7,64}$/;
@@ -28718,7 +29266,7 @@ async function publishSnapshot(outDir, target, commit, client, opts = {}) {
28718
29266
  const keep = opts.keep ?? DEFAULT_KEEP;
28719
29267
  if (!Number.isInteger(keep) || keep < 1) throw new Error(`--keep takes a whole number of at least 1, not ${keep}`);
28720
29268
  const at2 = (f) => `${target.prefix}/${f}`;
28721
- const local = JSON.parse(readFileSync37(join49(outDir, "manifest.json"), "utf8"));
29269
+ const local = JSON.parse(readFileSync38(join50(outDir, "manifest.json"), "utf8"));
28722
29270
  const generated = local.capturedAt ?? (/* @__PURE__ */ new Date()).toISOString();
28723
29271
  const entry = { commit: sha, at: commit.at ?? generated, project: opts.project ?? local.projectDir ?? "", generated };
28724
29272
  const bundle = await publishBundle(outDir, { bucket: target.bucket, prefix: at2(sha) }, client);
@@ -29181,7 +29729,7 @@ function warnIfNotChantProject(dir) {
29181
29729
  if (shape.kind === "estate") {
29182
29730
  process.stderr.write(
29183
29731
  `behold: warning \u2014 ${dir} is an estate root, not a chant project itself.
29184
- Serve its members composed: behold serve ${shape.members.map((m) => join50(dir, m.dir)).join(" ")}
29732
+ Serve its members composed: behold serve ${shape.members.map((m) => join51(dir, m.dir)).join(" ")}
29185
29733
  `
29186
29734
  );
29187
29735
  return;
@@ -29217,7 +29765,7 @@ async function runCarve(rest) {
29217
29765
  process.exit(2);
29218
29766
  }
29219
29767
  const reportPath = resolve33(fileArg);
29220
- const parsed = readCarveReport(reportPath, (p) => readFileSync38(p, "utf8"));
29768
+ const parsed = readCarveReport(reportPath, (p) => readFileSync39(p, "utf8"));
29221
29769
  if (!parsed.ok) {
29222
29770
  process.stderr.write(`behold carve: ${parsed.refusal.error}
29223
29771
  ${parsed.refusal.remedy}
@@ -29283,7 +29831,7 @@ ${check.stdout || check.stderr}
29283
29831
  if (check.code !== 0) process.exitCode = 1;
29284
29832
  }
29285
29833
  async function runDemo(rest) {
29286
- const pkgRoot3 = join50(dirname21(fileURLToPath12(import.meta.url)), "..");
29834
+ const pkgRoot3 = join51(dirname21(fileURLToPath12(import.meta.url)), "..");
29287
29835
  const registry2 = loadDemoRegistry(pkgRoot3);
29288
29836
  let port = 4600;
29289
29837
  let live2 = false;
@@ -29418,7 +29966,7 @@ async function serveCarveDemo(target, carve, port, live2 = false) {
29418
29966
  };
29419
29967
  process.stdout.write("behold demo carve --live \u2192 the tfstate is real now; the advisor reads what terraform wrote.\n");
29420
29968
  }
29421
- if (existsSync30(join50(project, "package.json")) && !existsSync30(join50(project, "node_modules"))) {
29969
+ if (existsSync30(join51(project, "package.json")) && !existsSync30(join51(project, "node_modules"))) {
29422
29970
  process.stdout.write(`behold demo carve \u2192 npm install in ${carve.project}/ (the chant this walkthrough shells)\u2026
29423
29971
  `);
29424
29972
  const code = await spawnStep("npm", ["install"], project);
@@ -29429,7 +29977,7 @@ async function serveCarveDemo(target, carve, port, live2 = false) {
29429
29977
  }
29430
29978
  }
29431
29979
  let degraded;
29432
- if (!existsSync30(join50(target, "node_modules", ...HCL_PARSER_PKG.split("/")))) {
29980
+ if (!existsSync30(join51(target, "node_modules", ...HCL_PARSER_PKG.split("/")))) {
29433
29981
  process.stdout.write(`behold demo carve \u2192 npm install ${HCL_PARSER_PKG} (chant's HCL parser, ~2MB, once)\u2026
29434
29982
  `);
29435
29983
  const code = await spawnStep("npm", ["install", "--no-save", "--no-package-lock", HCL_PARSER_PKG], target);