@intentius/behold 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +52 -23
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -3590,6 +3590,9 @@ var K8S_PLUMBING_KINDS = /* @__PURE__ */ new Set([
3590
3590
  "K8s::Rbac::ClusterRole",
3591
3591
  "K8s::Rbac::ClusterRoleBinding"
3592
3592
  ]);
3593
+ function namespaceBoxKey(ns) {
3594
+ return `namespace ${ns}`;
3595
+ }
3593
3596
  function metaStr(node, key) {
3594
3597
  const meta = node.attrs?.metadata;
3595
3598
  const v = meta?.[key];
@@ -3623,19 +3626,20 @@ function projectK8sLogical(ir, env, boundContext) {
3623
3626
  const cluster = boundManagedCluster(ir.nodes, boundContext);
3624
3627
  const CLUSTER_TITLE = cluster ? cluster.id : env ? `cluster ${env}` : "cluster";
3625
3628
  const CLUSTER_SCOPED2 = "cluster-scoped";
3626
- const namespaceTitle = (ns) => `namespace ${ns}`;
3627
3629
  const byContainer = {};
3628
3630
  const child = (parent, c) => {
3629
3631
  const arr = byContainer[parent] ?? (byContainer[parent] = []);
3630
3632
  if (!arr.includes(c)) arr.push(c);
3631
3633
  };
3632
- for (const ns of [...declaredNamespaces(k8s)].sort()) child(CLUSTER_TITLE, namespaceTitle(ns));
3634
+ const namespaceBoxes = {};
3635
+ const boxFor = (ns) => namespaceBoxes[ns] ??= namespaceBoxKey(ns);
3636
+ for (const ns of [...declaredNamespaces(k8s)].sort()) child(CLUSTER_TITLE, boxFor(ns));
3633
3637
  const headline = k8s.filter((n) => !K8S_PLUMBING_KINDS.has(n.kind));
3634
3638
  let clusterScoped = false;
3635
3639
  for (const n of headline) {
3636
3640
  const ns = metaStr(n, "namespace");
3637
3641
  if (ns) {
3638
- child(namespaceTitle(ns), n.id);
3642
+ child(boxFor(ns), n.id);
3639
3643
  } else {
3640
3644
  clusterScoped = true;
3641
3645
  child(CLUSTER_SCOPED2, n.id);
@@ -3644,7 +3648,7 @@ function projectK8sLogical(ir, env, boundContext) {
3644
3648
  if (clusterScoped) child(CLUSTER_TITLE, CLUSTER_SCOPED2);
3645
3649
  const kept = new Set(headline.map((n) => n.id));
3646
3650
  const edges = ir.edges.filter((e) => kept.has(e.from) && kept.has(e.to));
3647
- return { ir: { nodes: headline, edges, groups: {} }, byContainer };
3651
+ return { ir: { nodes: headline, edges, groups: {} }, byContainer, clusterBox: CLUSTER_TITLE, namespaceBoxes };
3648
3652
  }
3649
3653
 
3650
3654
  // src/logical-helm.ts
@@ -3998,6 +4002,7 @@ function projectTopology(ir, env, boundContext, sourceRoots) {
3998
4002
  const nodes = [];
3999
4003
  const edges = [];
4000
4004
  const byContainer = {};
4005
+ const boxes = { namespaces: {} };
4001
4006
  for (const p of projections) {
4002
4007
  nodes.push(...p.ir.nodes);
4003
4008
  edges.push(...p.ir.edges);
@@ -4005,11 +4010,13 @@ function projectTopology(ir, env, boundContext, sourceRoots) {
4005
4010
  const arr = byContainer[parent] ?? (byContainer[parent] = []);
4006
4011
  for (const c of children) if (!arr.includes(c)) arr.push(c);
4007
4012
  }
4013
+ if (p.clusterBox) boxes.cluster = p.clusterBox;
4014
+ Object.assign(boxes.namespaces, p.namespaceBoxes);
4008
4015
  }
4009
- placeHelmReleases(nodes, byContainer, ir, env, boundContext);
4016
+ placeHelmReleases(nodes, byContainer, boxes);
4010
4017
  nestReleaseBoxes(byContainer);
4011
4018
  retainCrossLensEdges(ir, projections, nodes, edges, byContainer);
4012
- return { ir: { nodes, edges, groups: {} }, byContainer };
4019
+ return { ir: { nodes, edges, groups: {} }, byContainer, namespaceBoxes: boxes.namespaces };
4013
4020
  }
4014
4021
  function retainCrossLensEdges(ir, projections, nodes, edges, byContainer) {
4015
4022
  const cards = new Set(nodes.map((n) => n.id));
@@ -4044,23 +4051,26 @@ function retainCrossLensEdges(ir, projections, nodes, edges, byContainer) {
4044
4051
  edges.push(e);
4045
4052
  }
4046
4053
  }
4047
- function placeHelmReleases(nodes, byContainer, ir, env, boundContext) {
4054
+ function placeHelmReleases(nodes, byContainer, boxes) {
4048
4055
  const releases = nodes.filter((n) => n.kind === "Helm::Release");
4049
4056
  if (releases.length === 0) return;
4050
4057
  const child = (parent, c) => {
4051
4058
  const arr = byContainer[parent] ?? (byContainer[parent] = []);
4052
4059
  if (!arr.includes(c)) arr.push(c);
4053
4060
  };
4054
- const cluster = boundManagedCluster(ir.nodes, boundContext);
4055
- const clusterTitle = cluster ? cluster.id : env ? `cluster ${env}` : "cluster";
4056
- const clusterBoxExists = byContainer[clusterTitle] !== void 0;
4061
+ const clusterBox = boxes.cluster !== void 0 && byContainer[boxes.cluster] !== void 0 ? boxes.cluster : void 0;
4057
4062
  for (const r of releases) {
4058
4063
  const ns = r.attrs?.namespace;
4059
4064
  if (typeof ns !== "string" || ns.length === 0) continue;
4060
- const nsBox = `namespace ${ns}`;
4061
- if (byContainer[nsBox] === void 0 && !clusterBoxExists) continue;
4062
- if (byContainer[nsBox] === void 0) child(clusterTitle, nsBox);
4063
- child(nsBox, r.id);
4065
+ const nsBox = boxes.namespaces[ns];
4066
+ if (nsBox !== void 0 && byContainer[nsBox] !== void 0) {
4067
+ child(nsBox, r.id);
4068
+ continue;
4069
+ }
4070
+ if (clusterBox === void 0) continue;
4071
+ const observed = nsBox ?? namespaceBoxKey(ns);
4072
+ child(clusterBox, observed);
4073
+ child(observed, r.id);
4064
4074
  }
4065
4075
  }
4066
4076
  function nestReleaseBoxes(byContainer) {
@@ -5360,6 +5370,12 @@ function renderArchitecture(ir, byContainer, opts = {}) {
5360
5370
  nodesep: Math.round(48 + spread * 48),
5361
5371
  ranksep: Math.round(60 + spread * 56)
5362
5372
  });
5373
+ if (opts.groupMarks) {
5374
+ for (const box of layout.groups ?? []) {
5375
+ const mark = box.id !== void 0 ? opts.groupMarks[box.id] : void 0;
5376
+ if (mark !== void 0) box.mark = mark;
5377
+ }
5378
+ }
5363
5379
  const svg = renderSvg(ir, layout, {
5364
5380
  fit: true,
5365
5381
  hideTitle: true,
@@ -6882,6 +6898,17 @@ function markOperatorHome(ir) {
6882
6898
  })
6883
6899
  };
6884
6900
  }
6901
+ var OPERATOR_HOME_GLYPH = {
6902
+ body: '<path d="M16.5 6.63A7 7 0 1 1 6.64 7.5"/><path d="M16.5 6.63 14.3 5.9M16.5 6.63 15.9 8.1"/>'
6903
+ };
6904
+ function operatorHomeBoxMarks(ir, namespaceBoxes) {
6905
+ const out = {};
6906
+ for (const home of operatorHomes(ir)) {
6907
+ const box = namespaceBoxes[home.namespace];
6908
+ if (box) out[box] = OPERATOR_HOME_GLYPH;
6909
+ }
6910
+ return out;
6911
+ }
6885
6912
  function operatorStrip(rows, now = Date.now()) {
6886
6913
  const out = rows.map((r) => {
6887
6914
  const expiresAt = r.lease ? Date.parse(r.lease.expiresAt) : NaN;
@@ -9208,8 +9235,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9208
9235
  ir = addClusterAnchorEdges(ir, estateContext);
9209
9236
  if (logical) {
9210
9237
  const logicalBefore = ir.nodes.length;
9211
- const { ir: projected, byContainer } = projectTopology(ir, metaEnv ?? void 0, estateContext, await estateSourceRoots(opts));
9212
- const { svg: svg2 } = renderArchitecture(projected, byContainer);
9238
+ const { ir: projected, byContainer, namespaceBoxes } = projectTopology(ir, metaEnv ?? void 0, estateContext, await estateSourceRoots(opts));
9239
+ const { svg: svg2 } = renderArchitecture(projected, byContainer, { groupMarks: operatorHomeBoxMarks(ir, namespaceBoxes ?? {}) });
9213
9240
  const logicalNote = notesFor("logical", projected, void 0, logicalBefore);
9214
9241
  return c.json({
9215
9242
  ir: projected,
@@ -9238,9 +9265,10 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9238
9265
  ir = joinCiProgress(ir, runner.applyProgress);
9239
9266
  } else if (logical) {
9240
9267
  const raw = mergeClusterRoot(await graphIr(cfg.projectDir, { ...opts, detail: 3 }), await clusterRootGraphIr(cfg.projectDir, opts));
9241
- const base = addValueMatchEdges(addK8sDeclaredEdges(raw));
9242
- const { ir: projected, byContainer } = projectTopology(base, metaEnv ?? void 0, await boundK8sContext(metaEnv ?? void 0), [await graphPath(cfg.projectDir, opts), cfg.projectDir]);
9243
- const { svg: svg2 } = renderArchitecture(projected, byContainer);
9268
+ const logicalContext = await boundK8sContext(metaEnv ?? void 0);
9269
+ const base = addClusterAnchorEdges(addValueMatchEdges(addK8sDeclaredEdges(raw)), logicalContext);
9270
+ const { ir: projected, byContainer, namespaceBoxes } = projectTopology(base, metaEnv ?? void 0, logicalContext, [await graphPath(cfg.projectDir, opts), cfg.projectDir]);
9271
+ const { svg: svg2 } = renderArchitecture(projected, byContainer, { groupMarks: operatorHomeBoxMarks(base, namespaceBoxes ?? {}) });
9244
9272
  const logicalNote = notesFor("logical", projected, void 0, base.nodes.length);
9245
9273
  return c.json({ ir: projected, svg: svg2, byContainer, meta: { projectDir: cfg.projectDir, env: metaEnv, tier: opts.tier ?? null, target: opts.target ?? null, mode: "logical", ...logicalNote ? { note: logicalNote } : {} } });
9246
9274
  } else {
@@ -9390,8 +9418,8 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9390
9418
  ].join("; ") : void 0;
9391
9419
  if (logical) {
9392
9420
  const logicalBefore = ir2.nodes.length;
9393
- const { ir: projected, byContainer } = projectTopology(ir2, env, boundContext2, await estateSourceRoots(query));
9394
- const { svg: svg3 } = renderArchitecture(projected, byContainer);
9421
+ const { ir: projected, byContainer, namespaceBoxes } = projectTopology(ir2, env, boundContext2, await estateSourceRoots(query));
9422
+ const { svg: svg3 } = renderArchitecture(projected, byContainer, { groupMarks: operatorHomeBoxMarks(ir2, namespaceBoxes ?? {}) });
9395
9423
  const note3 = [
9396
9424
  notesFor("logical", projected, void 0, logicalBefore),
9397
9425
  coverNote,
@@ -9433,8 +9461,9 @@ function createApp(cfg, broadcaster = new Broadcaster(), frames = new FrameBuffe
9433
9461
  ir = runtime ? attachRuntimeContainment(ir) : pruneRuntimeChildren(ir);
9434
9462
  if (logical) {
9435
9463
  const logicalBefore = ir.nodes.length;
9436
- const { ir: projected, byContainer } = projectTopology(addValueMatchEdges(addK8sDeclaredEdges(ir)), env, boundContext, [await graphPath(cfg.projectDir, opts), cfg.projectDir]);
9437
- const { svg: svg2 } = renderArchitecture(projected, byContainer);
9464
+ const projectionInput = addClusterAnchorEdges(addValueMatchEdges(addK8sDeclaredEdges(ir)), boundContext);
9465
+ const { ir: projected, byContainer, namespaceBoxes } = projectTopology(projectionInput, env, boundContext, [await graphPath(cfg.projectDir, opts), cfg.projectDir]);
9466
+ const { svg: svg2 } = renderArchitecture(projected, byContainer, { groupMarks: operatorHomeBoxMarks(projectionInput, namespaceBoxes ?? {}) });
9438
9467
  const logicalTierNote = tierMismatchNote(projected, beholdConfig.tiers, query.tier);
9439
9468
  const logicalNote = [logicalTierNote, notesFor("logical", projected, void 0, logicalBefore)].filter(Boolean).join(" \xB7 ");
9440
9469
  return c.json({ ir: projected, svg: svg2, byContainer, meta: { projectDir: cfg.projectDir, env, mode: "logical", ...logicalNote ? { note: logicalNote } : {} } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/behold",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "type": "module",
5
5
  "description": "behold — a live control plane on chant. See your whole estate (every substrate in one graph), coloured by drift; act through delegated, gated Ops.",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  "@hono/node-server": "^2.0.5",
38
38
  "@intentius/chant": "^0.52.1",
39
39
  "@intentius/chant-k8s-client": "^0.52.1",
40
- "@intentius/pinhole": "^0.3.5",
40
+ "@intentius/pinhole": "^0.3.7",
41
41
  "hono": "^4.12.34"
42
42
  },
43
43
  "devDependencies": {