@xplane/utils 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,85 @@
1
+ import { d as XrSnapshot, n as XrWatcher, o as KubernetesEvent, u as XrRef } from "../xr-watcher-EiWHVp3o.mjs";
2
+
3
+ //#region src/render/ci.d.ts
4
+ interface CiRendererOptions {
5
+ ref: XrRef;
6
+ /** Destination stream. Defaults to `process.stdout`. */
7
+ out?: NodeJS.WritableStream;
8
+ /** Heartbeat interval (ms). When no changes are observed within this window, emit a "no change" line so CI can see the job is alive. Defaults to 30000. Set to 0 to disable. */
9
+ heartbeatMs?: number;
10
+ /** When true, K8s Events are echoed inline. Defaults to false in CI (too noisy). */
11
+ showEvents?: boolean;
12
+ /** Every N consecutive idle heartbeats, expand the heartbeat line into a snapshot of unready + blocked resources. Defaults to 10. Set to 0 to disable. */
13
+ snapshotEveryHeartbeats?: number;
14
+ }
15
+ /**
16
+ * Append-only CI renderer.
17
+ *
18
+ * 1. Print a one-line "watching …" header on startup.
19
+ * 2. On the first snapshot, print a full snapshot block.
20
+ * 3. On every subsequent snapshot, print only the delta.
21
+ * 4. If nothing is printed within `heartbeatMs`, emit a single liveness line.
22
+ */
23
+ declare function renderCI(watcher: XrWatcher, opts: CiRendererOptions): Promise<void>;
24
+ //#endregion
25
+ //#region src/render/format.d.ts
26
+ /** Single-character glyph for the status of a tree node. */
27
+ declare function statusGlyph(state: 'ready' | 'pending' | 'blocked'): string;
28
+ /** Compact header line summarising the XR. */
29
+ declare function formatHeader(snapshot: XrSnapshot, ref: {
30
+ kind: string;
31
+ name: string;
32
+ namespace?: string;
33
+ }): string;
34
+ /** Inline progress representation, e.g. `5/12 ready · 3 blocked`. */
35
+ declare function formatProgress(ready: number, total: number, blocked: number): string;
36
+ /** Format a Kubernetes Event as a single line. */
37
+ declare function formatEvent(ev: KubernetesEvent): string;
38
+ //#endregion
39
+ //#region src/render/tty.d.ts
40
+ interface TtyRendererOptions {
41
+ ref: XrRef;
42
+ /** Destination stream. Defaults to `process.stdout`. */
43
+ out?: NodeJS.WriteStream;
44
+ /** Maximum number of recent Kubernetes events shown in the tail. Defaults to 5. */
45
+ eventTailSize?: number;
46
+ /** Allows callers (and tests) to provide a custom `log-update` instance. */
47
+ logger?: {
48
+ (frame: string): void;
49
+ clear: () => void;
50
+ done: () => void;
51
+ };
52
+ }
53
+ /**
54
+ * Live full-screen renderer using `log-update` to repaint a single frame.
55
+ * Resolves when the watcher ends.
56
+ */
57
+ declare function renderTTY(watcher: XrWatcher, opts: TtyRendererOptions): Promise<void>;
58
+ //#endregion
59
+ //#region src/render/index.d.ts
60
+ type RendererMode = 'tty' | 'ci';
61
+ /** Auto-select a renderer based on whether the destination is an interactive TTY. */
62
+ declare function selectRenderer(stream: NodeJS.WriteStream | NodeJS.WritableStream): RendererMode;
63
+ interface RunRendererOptions {
64
+ ref: XrRef;
65
+ /** Forces a renderer instead of auto-detecting. */
66
+ mode?: RendererMode;
67
+ /** Destination stream. Defaults to `process.stdout`. */
68
+ out?: NodeJS.WriteStream;
69
+ /** Max number of recent events kept in the TTY tail. */
70
+ eventTailSize?: number;
71
+ /** CI: heartbeat interval (ms) for liveness lines. 0 disables. */
72
+ heartbeatMs?: number;
73
+ /** CI: include K8s Events inline. Off by default. */
74
+ showEvents?: boolean;
75
+ /** CI: every N idle heartbeats, expand into a snapshot of unready + blocked resources. 0 disables. */
76
+ snapshotEveryHeartbeats?: number;
77
+ }
78
+ /**
79
+ * Drive either renderer against the supplied watcher. Resolves when the
80
+ * watcher's event stream ends.
81
+ */
82
+ declare function runRenderer(watcher: XrWatcher, opts: RunRendererOptions): Promise<void>;
83
+ //#endregion
84
+ export { type CiRendererOptions, RendererMode, RunRendererOptions, type TtyRendererOptions, formatEvent, formatHeader, formatProgress, renderCI, renderTTY, runRenderer, selectRenderer, statusGlyph };
85
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/render/ci.ts","../../src/render/format.ts","../../src/render/tty.ts","../../src/render/index.ts"],"mappings":";;;UAWiB,iBAAA;EACf,GAAA,EAAK,KAAA;EAD2B;EAGhC,GAAA,GAAM,MAAA,CAAO,cAAc;EAAA;EAE3B,WAAA;EAJK;EAML,UAAA;EAJM;EAMN,uBAAA;AAAA;;;;AAAuB;AAWzB;;;;iBAAsB,QAAA,CAAS,OAAA,EAAS,SAAA,EAAW,IAAA,EAAM,iBAAA,GAAoB,OAAA;;;;iBC3B7D,WAAA,CAAY,KAAsC;ADOlE;AAAA,iBCKgB,YAAA,CACd,QAAA,EAAU,UAAU,EACpB,GAAA;EAAO,IAAA;EAAc,IAAA;EAAc,SAAA;AAAA;;iBAgBrB,cAAA,CAAe,KAAA,UAAe,KAAA,UAAe,OAAA;;iBAO7C,WAAA,CAAY,EAAmB,EAAf,eAAe;;;UClC9B,kBAAA;EACf,GAAA,EAAK,KAAA;EFG2B;EEDhC,GAAA,GAAM,MAAA,CAAO,WAAW;EFIG;EEF3B,aAAA;EFAK;EEEL,MAAA;IAAA,CAAY,KAAA;IAAsB,KAAA;IAAmB,IAAA;EAAA;AAAA;;AFM9B;AAWzB;;iBEVsB,SAAA,CAAU,OAAA,EAAS,SAAA,EAAW,IAAA,EAAM,kBAAA,GAAqB,OAAA;;;KCZnE,YAAA;;iBAGI,cAAA,CAAe,MAAA,EAAQ,MAAA,CAAO,WAAA,GAAc,MAAA,CAAO,cAAA,GAAiB,YAAA;AAAA,UAKnE,kBAAA;EACf,GAAA,EAAK,KAAA;EHJC;EGMN,IAAA,GAAO,YAAA;EHJP;EGMA,GAAA,GAAM,MAAA,CAAO,WAAA;EHFb;EGIA,aAAA;EHJuB;EGMvB,WAAA;EHK4B;EGH5B,UAAA;EHGsC;EGDtC,uBAAA;AAAA;;;;;iBAOoB,WAAA,CAAY,OAAA,EAAS,SAAA,EAAW,IAAA,EAAM,kBAAA,GAAqB,OAAA"}
@@ -0,0 +1,2 @@
1
+ import { a as formatEvent, c as statusGlyph, i as renderCI, n as selectRenderer, o as formatHeader, r as renderTTY, s as formatProgress, t as runRenderer } from "../render-Cnhpyf1X.mjs";
2
+ export { formatEvent, formatHeader, formatProgress, renderCI, renderTTY, runRenderer, selectRenderer, statusGlyph };
@@ -0,0 +1,364 @@
1
+ import { t as buildTree } from "./tree-DaHkojq8.mjs";
2
+ import chalk from "chalk";
3
+ import { createLogUpdate } from "log-update";
4
+ //#region src/render/format.ts
5
+ /** Single-character glyph for the status of a tree node. */
6
+ function statusGlyph(state) {
7
+ switch (state) {
8
+ case "ready": return chalk.green("✔");
9
+ case "blocked": return chalk.red("✖");
10
+ case "pending": return chalk.yellow("⏳");
11
+ }
12
+ }
13
+ /** Compact header line summarising the XR. */
14
+ function formatHeader(snapshot, ref) {
15
+ const obj = snapshot.object;
16
+ const fqdn = ref.namespace ? `${ref.kind}/${ref.name} (${ref.namespace})` : `${ref.kind}/${ref.name}`;
17
+ const readyState = snapshot.ready ? chalk.green("Ready") : chalk.yellow(snapshot.readyReason ?? "NotReady");
18
+ const ts = obj.metadata?.creationTimestamp;
19
+ const age = ts ? formatAge(new Date(ts)) : "?";
20
+ const throttled = snapshot.updatesThrottled ? ` ${chalk.yellow("⚠ Updates Throttled")}` : "";
21
+ return `${chalk.bold(fqdn)} age=${age} ${readyState}${throttled}`;
22
+ }
23
+ /** Inline progress representation, e.g. `5/12 ready · 3 blocked`. */
24
+ function formatProgress(ready, total, blocked) {
25
+ const parts = [`${ready}/${total} ready`];
26
+ if (blocked > 0) parts.push(chalk.red(`${blocked} blocked`));
27
+ return parts.join(chalk.dim(" · "));
28
+ }
29
+ /** Format a Kubernetes Event as a single line. */
30
+ function formatEvent(ev) {
31
+ const color = ev.type === "Warning" ? chalk.yellow : chalk.dim;
32
+ const target = ev.involvedKind && ev.involvedName ? `${ev.involvedKind}/${ev.involvedName} ` : "";
33
+ return color(`${ev.lastTimestamp ?? ""} ${target}${ev.reason}: ${ev.message}`.trim());
34
+ }
35
+ function formatAge(from) {
36
+ const ms = Date.now() - from.getTime();
37
+ if (ms < 6e4) return `${Math.floor(ms / 1e3)}s`;
38
+ if (ms < 36e5) return `${Math.floor(ms / 6e4)}m`;
39
+ if (ms < 864e5) return `${Math.floor(ms / 36e5)}h`;
40
+ return `${Math.floor(ms / 864e5)}d`;
41
+ }
42
+ //#endregion
43
+ //#region src/render/ci.ts
44
+ /**
45
+ * Append-only CI renderer.
46
+ *
47
+ * 1. Print a one-line "watching …" header on startup.
48
+ * 2. On the first snapshot, print a full snapshot block.
49
+ * 3. On every subsequent snapshot, print only the delta.
50
+ * 4. If nothing is printed within `heartbeatMs`, emit a single liveness line.
51
+ */
52
+ async function renderCI(watcher, opts) {
53
+ const out = opts.out ?? process.stdout;
54
+ const heartbeatMs = opts.heartbeatMs ?? 3e4;
55
+ const showEvents = opts.showEvents ?? false;
56
+ const snapshotEvery = opts.snapshotEveryHeartbeats ?? 10;
57
+ const write = (line) => out.write(`${line}\n`);
58
+ const stamp = () => (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
59
+ let lastWriteAt = Date.now();
60
+ /** Warning (and worse) K8s events buffered since the last flush — always surfaced even when `showEvents` is off. */
61
+ const pendingWarnings = [];
62
+ const flushPendingWarnings = () => {
63
+ if (pendingWarnings.length === 0) return;
64
+ for (const e of pendingWarnings) write(formatEvent(e));
65
+ pendingWarnings.length = 0;
66
+ lastWriteAt = Date.now();
67
+ };
68
+ const emit = (line) => {
69
+ flushPendingWarnings();
70
+ write(line);
71
+ lastWriteAt = Date.now();
72
+ };
73
+ let prev;
74
+ let idleHeartbeats = 0;
75
+ let heartbeat;
76
+ if (heartbeatMs > 0) {
77
+ heartbeat = setInterval(() => {
78
+ const hadWarnings = pendingWarnings.length > 0;
79
+ flushPendingWarnings();
80
+ if (!prev) return;
81
+ if (!hadWarnings && Date.now() - lastWriteAt < heartbeatMs) return;
82
+ idleHeartbeats += 1;
83
+ const expand = snapshotEvery > 0 && idleHeartbeats % snapshotEvery === 0;
84
+ const ts = stamp();
85
+ const counts = formatCounts(prev.readyCount, prev.total, prev.blockedCount);
86
+ const headline = `${chalk.dim(ts)} ${chalk.dim("→")} ${counts} ${chalk.dim("(no change)")}`;
87
+ if (!expand) {
88
+ emit(headline);
89
+ return;
90
+ }
91
+ emit([headline, ...renderPendingBlock(prev)].join("\n"));
92
+ }, heartbeatMs);
93
+ heartbeat.unref?.();
94
+ }
95
+ emit(`${chalk.dim(stamp())} ${chalk.bold(`watching ${opts.ref.kind}/${opts.ref.name}${opts.ref.namespace ? ` -n ${opts.ref.namespace}` : ""}`)}`);
96
+ try {
97
+ for await (const ev of watcher) if (ev.type === "snapshot") {
98
+ const next = summarise(ev.snapshot);
99
+ if (!prev) {
100
+ emit(renderFullSnapshot(opts.ref, ev.snapshot, next, stamp()));
101
+ idleHeartbeats = 0;
102
+ } else {
103
+ const delta = renderDelta(prev, next, stamp());
104
+ if (delta) {
105
+ emit(delta);
106
+ idleHeartbeats = 0;
107
+ }
108
+ }
109
+ prev = next;
110
+ } else if (ev.type === "k8s-event") {
111
+ if (showEvents) emit(formatEvent(ev.event));
112
+ else if (ev.event.type !== "Normal") pendingWarnings.push(ev.event);
113
+ } else if (ev.type === "ready") emit(`${chalk.dim(stamp())} ${chalk.green("✔")} ${opts.ref.kind}/${opts.ref.name} ${chalk.green("is Ready")}`);
114
+ else if (ev.type === "error") emit(`${chalk.dim(stamp())} ${chalk.red("error:")} ${ev.error.message}`);
115
+ else if (ev.type === "end") {
116
+ flushPendingWarnings();
117
+ return;
118
+ }
119
+ } finally {
120
+ if (heartbeat) clearInterval(heartbeat);
121
+ }
122
+ }
123
+ function summarise(snapshot) {
124
+ const x = snapshot.xplane;
125
+ const ready = /* @__PURE__ */ new Set();
126
+ const unready = [];
127
+ const blocked = /* @__PURE__ */ new Map();
128
+ if (x) {
129
+ for (const r of x.emittedResources) if (r.ready) ready.add(r.nodePath);
130
+ else unready.push(r.nodePath);
131
+ for (const b of x.blockedResources) blocked.set(b.nodePath, b.waitingFor ?? []);
132
+ }
133
+ return {
134
+ reason: snapshot.readyReason ?? (snapshot.ready ? "Available" : void 0),
135
+ readyCount: ready.size || (snapshot.ready ? 1 : 0),
136
+ blockedCount: blocked.size,
137
+ total: x?.emittedResources.length ?? snapshot.resourceRefs.length,
138
+ ready,
139
+ unready,
140
+ blocked
141
+ };
142
+ }
143
+ function renderPendingBlock(s) {
144
+ const lines = [];
145
+ if (s.unready.length > 0) {
146
+ lines.push(chalk.dim(" unready:"));
147
+ for (const path of s.unready) lines.push(` ${chalk.dim("⏳")} ${path}`);
148
+ }
149
+ if (s.blocked.size > 0) {
150
+ lines.push(chalk.dim(" blocked:"));
151
+ for (const [path, waiting] of s.blocked) lines.push(` ${chalk.yellow("-")} ${path}${formatWaiting(waiting)}`);
152
+ }
153
+ return lines;
154
+ }
155
+ function renderFullSnapshot(ref, snapshot, s, ts) {
156
+ const lines = [];
157
+ const reason = s.reason ?? "Pending";
158
+ const target = `${ref.kind}/${ref.name}${ref.namespace ? ` (${ref.namespace})` : ""}`;
159
+ const counts = formatCounts(s.readyCount, s.total, s.blockedCount);
160
+ lines.push(`${chalk.dim(ts)} ${chalk.bold(target)} ${chalk.dim("·")} ${reason} ${chalk.dim("·")} ${counts}`);
161
+ if (snapshot.updatesThrottled) lines.push(` ${chalk.yellow("⚠ Updates Throttled")}`);
162
+ if (s.blocked.size > 0) {
163
+ lines.push(chalk.dim(" blocked:"));
164
+ for (const [path, waiting] of s.blocked) lines.push(` ${chalk.yellow("-")} ${path}${formatWaiting(waiting)}`);
165
+ }
166
+ return lines.join("\n");
167
+ }
168
+ function renderDelta(prev, next, ts) {
169
+ const becameReady = [];
170
+ const becameBlocked = [];
171
+ const waitingChanged = [];
172
+ for (const path of next.ready) if (!prev.ready.has(path)) becameReady.push(path);
173
+ for (const [path, waiting] of next.blocked) if (!prev.blocked.has(path)) becameBlocked.push(path);
174
+ else if ((prev.blocked.get(path) ?? []).join("|") !== waiting.join("|")) waitingChanged.push(path);
175
+ const reasonChanged = next.reason !== prev.reason;
176
+ const totalsChanged = next.readyCount !== prev.readyCount || next.blockedCount !== prev.blockedCount || next.total !== prev.total;
177
+ if (becameReady.length === 0 && becameBlocked.length === 0 && waitingChanged.length === 0 && !reasonChanged && !totalsChanged) return;
178
+ const lines = [];
179
+ const tsd = chalk.dim(ts);
180
+ for (const path of becameReady) lines.push(`${tsd} ${chalk.green("+")} ${path}`);
181
+ for (const path of becameBlocked) {
182
+ const waiting = next.blocked.get(path) ?? [];
183
+ lines.push(`${tsd} ${chalk.yellow("-")} ${path}${formatWaiting(waiting)}`);
184
+ }
185
+ for (const path of waitingChanged) {
186
+ const waiting = next.blocked.get(path) ?? [];
187
+ lines.push(`${tsd} ${chalk.yellow("~")} ${path}${formatWaiting(waiting)}`);
188
+ }
189
+ if (reasonChanged && next.reason) lines.push(`${tsd} ${chalk.yellow("~")} reason: ${chalk.dim(prev.reason ?? "?")} → ${next.reason}`);
190
+ lines.push(`${tsd} ${chalk.dim("→")} ${formatCounts(next.readyCount, next.total, next.blockedCount)}`);
191
+ return lines.join("\n");
192
+ }
193
+ function formatCounts(ready, total, blocked) {
194
+ const unready = Math.max(0, total - ready);
195
+ const grand = total + blocked;
196
+ const pct = grand > 0 ? Math.round(ready / grand * 100) : 0;
197
+ const readyText = `✅ ${ready} ready`;
198
+ const readyPart = total > 0 && ready === total ? chalk.green(readyText) : readyText;
199
+ const unreadyPart = `⏳ ${unready} unready`;
200
+ const blockedPart = `🚧 ${blocked} blocked`;
201
+ const pctPart = `📈 ${pct}%`;
202
+ const dot = "·";
203
+ return `${readyPart} ${dot} ${unreadyPart} ${dot} ${blockedPart} ${dot} ${pctPart}`;
204
+ }
205
+ function formatWaiting(waiting) {
206
+ if (waiting.length === 0) return "";
207
+ return chalk.dim(` waiting ${waiting.join(", ")}`);
208
+ }
209
+ //#endregion
210
+ //#region src/render/tty.ts
211
+ /**
212
+ * Live full-screen renderer using `log-update` to repaint a single frame.
213
+ * Resolves when the watcher ends.
214
+ */
215
+ async function renderTTY(watcher, opts) {
216
+ const out = opts.out ?? process.stdout;
217
+ const logger = opts.logger ?? createLogUpdate(out);
218
+ const tailSize = opts.eventTailSize ?? 5;
219
+ const events = [];
220
+ let snapshot;
221
+ let lastError;
222
+ const repaint = () => {
223
+ logger(renderFrame(opts.ref, snapshot, events, lastError, out.rows, out.columns));
224
+ };
225
+ repaint();
226
+ for await (const ev of watcher) if (ev.type === "snapshot") {
227
+ snapshot = ev.snapshot;
228
+ repaint();
229
+ } else if (ev.type === "k8s-event") {
230
+ events.push(ev.event);
231
+ if (events.length > tailSize) events.splice(0, events.length - tailSize);
232
+ repaint();
233
+ } else if (ev.type === "error") {
234
+ lastError = ev.error;
235
+ repaint();
236
+ } else if (ev.type === "ready") {
237
+ snapshot = ev.snapshot;
238
+ repaint();
239
+ } else if (ev.type === "end") {
240
+ logger.done();
241
+ return;
242
+ }
243
+ logger.done();
244
+ }
245
+ function renderFrame(ref, snapshot, events, err, rows, columns) {
246
+ const lines = [];
247
+ if (snapshot) {
248
+ lines.push(formatHeader(snapshot, ref));
249
+ const tree = buildTree(snapshot);
250
+ if (tree.stats.total > 0) lines.push(` ${formatProgress(tree.stats.ready, tree.stats.total, tree.stats.blocked)} ${chalk.dim(`(source: ${tree.source})`)}`);
251
+ if (snapshot.readyMessage) lines.push(chalk.dim(` ${snapshot.readyMessage}`));
252
+ if (tree.roots.length > 0) {
253
+ lines.push("");
254
+ lines.push(chalk.bold("Resources"));
255
+ renderTreeLines(tree, lines);
256
+ }
257
+ } else lines.push(chalk.dim(`waiting for first observation of ${ref.kind}/${ref.name}…`));
258
+ const errLines = [];
259
+ if (err) {
260
+ errLines.push("");
261
+ errLines.push(chalk.red(`error: ${err.message}`));
262
+ }
263
+ const eventLines = [];
264
+ if (events.length > 0) {
265
+ eventLines.push("");
266
+ eventLines.push(chalk.bold("Recent events"));
267
+ for (const e of events) eventLines.push(` ${formatEventLine(e)}`);
268
+ }
269
+ if (rows && rows > 0 && eventLines.length > 0) {
270
+ const available = rows - (lines.length + errLines.length);
271
+ if (available <= 2) eventLines.length = 0;
272
+ else if (eventLines.length > available) {
273
+ const keep = available - 2;
274
+ const kept = eventLines.slice(eventLines.length - keep);
275
+ eventLines.length = 0;
276
+ eventLines.push("", chalk.bold("Recent events"), ...kept);
277
+ }
278
+ }
279
+ const all = [
280
+ ...lines,
281
+ ...eventLines,
282
+ ...errLines
283
+ ];
284
+ if (columns && columns > 0) for (let i = 0; i < all.length; i++) {
285
+ const line = all[i];
286
+ if (visibleWidth(line) > columns) all[i] = truncateAnsi(line, columns);
287
+ }
288
+ return all.join("\n");
289
+ }
290
+ const ANSI_RE = /\u001b\[[0-9;]*m/g;
291
+ function visibleWidth(s) {
292
+ return s.replace(ANSI_RE, "").length;
293
+ }
294
+ /** Truncate a string with embedded ANSI escapes to `maxVisible` visible chars,
295
+ * appending an ellipsis and a reset code. */
296
+ function truncateAnsi(s, maxVisible) {
297
+ if (maxVisible <= 1) return "…";
298
+ let visible = 0;
299
+ let out = "";
300
+ const limit = maxVisible - 1;
301
+ let i = 0;
302
+ while (i < s.length && visible < limit) {
303
+ ANSI_RE.lastIndex = i;
304
+ const m = ANSI_RE.exec(s);
305
+ if (m && m.index === i) {
306
+ out += m[0];
307
+ i = m.index + m[0].length;
308
+ continue;
309
+ }
310
+ out += s[i];
311
+ visible++;
312
+ i++;
313
+ }
314
+ return `${out}\u2026\u001b[0m`;
315
+ }
316
+ function renderTreeLines(tree, out) {
317
+ for (const root of tree.roots) renderNode(root, "", true, out);
318
+ }
319
+ function renderNode(node, prefix, isLast, out) {
320
+ const branch = prefix === "" ? "" : isLast ? "└─ " : "├─ ";
321
+ const state = node.blocked ? "blocked" : node.ready ? "ready" : "pending";
322
+ const meta = node.kind ? chalk.dim(` (${node.kind})`) : "";
323
+ const nameDetail = node.name ? chalk.dim(node.namespace ? ` ${node.namespace}/${node.name}` : ` ${node.name}`) : "";
324
+ const waiting = node.waitingFor && node.waitingFor.length > 0 ? chalk.dim(` waiting for ${node.waitingFor.join(", ")}`) : "";
325
+ out.push(`${prefix}${branch}${statusGlyph(state)} ${node.label}${meta}${nameDetail}${waiting}`);
326
+ const childPrefix = prefix + (prefix === "" ? " " : isLast ? " " : "│ ");
327
+ for (let i = 0; i < node.children.length; i++) renderNode(node.children[i], childPrefix, i === node.children.length - 1, out);
328
+ }
329
+ function formatEventLine(ev) {
330
+ return (ev.type === "Warning" ? chalk.yellow : chalk.dim)(`${ev.lastTimestamp ? new Date(ev.lastTimestamp).toISOString().slice(11, 19) : ""} ${ev.involvedKind ? `${ev.involvedKind}/${ev.involvedName ?? "?"} ` : ""}${ev.reason}: ${ev.message}`).trim();
331
+ }
332
+ //#endregion
333
+ //#region src/render/index.ts
334
+ /** Auto-select a renderer based on whether the destination is an interactive TTY. */
335
+ function selectRenderer(stream) {
336
+ return stream.isTTY ? "tty" : "ci";
337
+ }
338
+ /**
339
+ * Drive either renderer against the supplied watcher. Resolves when the
340
+ * watcher's event stream ends.
341
+ */
342
+ async function runRenderer(watcher, opts) {
343
+ const out = opts.out ?? process.stdout;
344
+ if ((opts.mode ?? selectRenderer(out)) === "tty") {
345
+ const ttyOpts = {
346
+ ref: opts.ref,
347
+ out
348
+ };
349
+ if (opts.eventTailSize !== void 0) ttyOpts.eventTailSize = opts.eventTailSize;
350
+ return renderTTY(watcher, ttyOpts);
351
+ }
352
+ const ciOpts = {
353
+ ref: opts.ref,
354
+ out
355
+ };
356
+ if (opts.heartbeatMs !== void 0) ciOpts.heartbeatMs = opts.heartbeatMs;
357
+ if (opts.showEvents !== void 0) ciOpts.showEvents = opts.showEvents;
358
+ if (opts.snapshotEveryHeartbeats !== void 0) ciOpts.snapshotEveryHeartbeats = opts.snapshotEveryHeartbeats;
359
+ return renderCI(watcher, ciOpts);
360
+ }
361
+ //#endregion
362
+ export { formatEvent as a, statusGlyph as c, renderCI as i, selectRenderer as n, formatHeader as o, renderTTY as r, formatProgress as s, runRenderer as t };
363
+
364
+ //# sourceMappingURL=render-Cnhpyf1X.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render-Cnhpyf1X.mjs","names":["tty"],"sources":["../src/render/format.ts","../src/render/ci.ts","../src/render/tty.ts","../src/render/index.ts"],"sourcesContent":["import chalk from 'chalk';\nimport type { KubernetesEvent, XrSnapshot } from '../watcher/types.js';\n\n/** Single-character glyph for the status of a tree node. */\nexport function statusGlyph(state: 'ready' | 'pending' | 'blocked'): string {\n switch (state) {\n case 'ready':\n return chalk.green('✔');\n case 'blocked':\n return chalk.red('✖');\n case 'pending':\n return chalk.yellow('⏳');\n }\n}\n\n/** Compact header line summarising the XR. */\nexport function formatHeader(\n snapshot: XrSnapshot,\n ref: { kind: string; name: string; namespace?: string },\n): string {\n const obj = snapshot.object;\n const fqdn = ref.namespace\n ? `${ref.kind}/${ref.name} (${ref.namespace})`\n : `${ref.kind}/${ref.name}`;\n const readyState = snapshot.ready\n ? chalk.green('Ready')\n : chalk.yellow(snapshot.readyReason ?? 'NotReady');\n const ts = obj.metadata?.creationTimestamp as unknown as string | Date | undefined;\n const age = ts ? formatAge(new Date(ts)) : '?';\n const throttled = snapshot.updatesThrottled ? ` ${chalk.yellow('⚠ Updates Throttled')}` : '';\n return `${chalk.bold(fqdn)} age=${age} ${readyState}${throttled}`;\n}\n\n/** Inline progress representation, e.g. `5/12 ready · 3 blocked`. */\nexport function formatProgress(ready: number, total: number, blocked: number): string {\n const parts: string[] = [`${ready}/${total} ready`];\n if (blocked > 0) parts.push(chalk.red(`${blocked} blocked`));\n return parts.join(chalk.dim(' · '));\n}\n\n/** Format a Kubernetes Event as a single line. */\nexport function formatEvent(ev: KubernetesEvent): string {\n const color = ev.type === 'Warning' ? chalk.yellow : chalk.dim;\n const target = ev.involvedKind && ev.involvedName ? `${ev.involvedKind}/${ev.involvedName} ` : '';\n return color(`${ev.lastTimestamp ?? ''} ${target}${ev.reason}: ${ev.message}`.trim());\n}\n\nfunction formatAge(from: Date): string {\n const ms = Date.now() - from.getTime();\n if (ms < 60_000) return `${Math.floor(ms / 1000)}s`;\n if (ms < 3_600_000) return `${Math.floor(ms / 60_000)}m`;\n if (ms < 86_400_000) return `${Math.floor(ms / 3_600_000)}h`;\n return `${Math.floor(ms / 86_400_000)}d`;\n}\n","import chalk from 'chalk';\nimport type {\n KubernetesEvent,\n XplaneStatus,\n XrEvent,\n XrRef,\n XrSnapshot,\n} from '../watcher/types.js';\nimport type { XrWatcher } from '../watcher/xr-watcher.js';\nimport { formatEvent } from './format.js';\n\nexport interface CiRendererOptions {\n ref: XrRef;\n /** Destination stream. Defaults to `process.stdout`. */\n out?: NodeJS.WritableStream;\n /** Heartbeat interval (ms). When no changes are observed within this window, emit a \"no change\" line so CI can see the job is alive. Defaults to 30000. Set to 0 to disable. */\n heartbeatMs?: number;\n /** When true, K8s Events are echoed inline. Defaults to false in CI (too noisy). */\n showEvents?: boolean;\n /** Every N consecutive idle heartbeats, expand the heartbeat line into a snapshot of unready + blocked resources. Defaults to 10. Set to 0 to disable. */\n snapshotEveryHeartbeats?: number;\n}\n\n/**\n * Append-only CI renderer.\n *\n * 1. Print a one-line \"watching …\" header on startup.\n * 2. On the first snapshot, print a full snapshot block.\n * 3. On every subsequent snapshot, print only the delta.\n * 4. If nothing is printed within `heartbeatMs`, emit a single liveness line.\n */\nexport async function renderCI(watcher: XrWatcher, opts: CiRendererOptions): Promise<void> {\n const out = opts.out ?? process.stdout;\n const heartbeatMs = opts.heartbeatMs ?? 30_000;\n const showEvents = opts.showEvents ?? false;\n const snapshotEvery = opts.snapshotEveryHeartbeats ?? 10;\n const write = (line: string) => out.write(`${line}\\n`);\n const stamp = () => new Date().toISOString().slice(11, 19);\n let lastWriteAt = Date.now();\n /** Warning (and worse) K8s events buffered since the last flush — always surfaced even when `showEvents` is off. */\n const pendingWarnings: KubernetesEvent[] = [];\n const flushPendingWarnings = () => {\n if (pendingWarnings.length === 0) return;\n for (const e of pendingWarnings) write(formatEvent(e));\n pendingWarnings.length = 0;\n lastWriteAt = Date.now();\n };\n const emit = (line: string) => {\n flushPendingWarnings();\n write(line);\n lastWriteAt = Date.now();\n };\n\n let prev: SnapshotSummary | undefined;\n let idleHeartbeats = 0;\n let heartbeat: NodeJS.Timeout | undefined;\n if (heartbeatMs > 0) {\n heartbeat = setInterval(() => {\n // Surface any buffered warnings first, even if the snapshot itself hasn't changed.\n const hadWarnings = pendingWarnings.length > 0;\n flushPendingWarnings();\n if (!prev) return;\n if (!hadWarnings && Date.now() - lastWriteAt < heartbeatMs) return;\n idleHeartbeats += 1;\n const expand = snapshotEvery > 0 && idleHeartbeats % snapshotEvery === 0;\n const ts = stamp();\n const counts = formatCounts(prev.readyCount, prev.total, prev.blockedCount);\n const headline = `${chalk.dim(ts)} ${chalk.dim('→')} ${counts} ${chalk.dim('(no change)')}`;\n if (!expand) {\n emit(headline);\n return;\n }\n emit([headline, ...renderPendingBlock(prev)].join('\\n'));\n }, heartbeatMs);\n heartbeat.unref?.();\n }\n\n emit(\n `${chalk.dim(stamp())} ${chalk.bold(`watching ${opts.ref.kind}/${opts.ref.name}${opts.ref.namespace ? ` -n ${opts.ref.namespace}` : ''}`)}`,\n );\n\n try {\n for await (const ev of watcher as AsyncIterable<XrEvent>) {\n if (ev.type === 'snapshot') {\n const next = summarise(ev.snapshot);\n if (!prev) {\n emit(renderFullSnapshot(opts.ref, ev.snapshot, next, stamp()));\n idleHeartbeats = 0;\n } else {\n const delta = renderDelta(prev, next, stamp());\n if (delta) {\n emit(delta);\n idleHeartbeats = 0;\n }\n }\n prev = next;\n } else if (ev.type === 'k8s-event') {\n if (showEvents) {\n emit(formatEvent(ev.event));\n } else if (ev.event.type !== 'Normal') {\n // Warnings (and any future non-Normal severities) are always surfaced —\n // buffered here and flushed on the next emit / heartbeat.\n pendingWarnings.push(ev.event);\n }\n } else if (ev.type === 'ready') {\n emit(\n `${chalk.dim(stamp())} ${chalk.green('✔')} ${opts.ref.kind}/${opts.ref.name} ${chalk.green('is Ready')}`,\n );\n } else if (ev.type === 'error') {\n emit(`${chalk.dim(stamp())} ${chalk.red('error:')} ${ev.error.message}`);\n } else if (ev.type === 'end') {\n flushPendingWarnings();\n return;\n }\n }\n } finally {\n if (heartbeat) clearInterval(heartbeat);\n }\n}\n\ninterface SnapshotSummary {\n reason: string | undefined;\n readyCount: number;\n blockedCount: number;\n total: number;\n ready: Set<string>;\n unready: string[];\n blocked: Map<string, string[]>;\n}\n\nfunction summarise(snapshot: XrSnapshot): SnapshotSummary {\n const x: XplaneStatus | undefined = snapshot.xplane;\n const ready = new Set<string>();\n const unready: string[] = [];\n const blocked = new Map<string, string[]>();\n if (x) {\n for (const r of x.emittedResources) {\n if (r.ready) ready.add(r.nodePath);\n else unready.push(r.nodePath);\n }\n for (const b of x.blockedResources) blocked.set(b.nodePath, b.waitingFor ?? []);\n }\n return {\n reason: snapshot.readyReason ?? (snapshot.ready ? 'Available' : undefined),\n readyCount: ready.size || (snapshot.ready ? 1 : 0),\n blockedCount: blocked.size,\n total: x?.emittedResources.length ?? snapshot.resourceRefs.length,\n ready,\n unready,\n blocked,\n };\n}\n\nfunction renderPendingBlock(s: SnapshotSummary): string[] {\n const lines: string[] = [];\n if (s.unready.length > 0) {\n lines.push(chalk.dim(' unready:'));\n for (const path of s.unready) lines.push(` ${chalk.dim('⏳')} ${path}`);\n }\n if (s.blocked.size > 0) {\n lines.push(chalk.dim(' blocked:'));\n for (const [path, waiting] of s.blocked) {\n lines.push(` ${chalk.yellow('-')} ${path}${formatWaiting(waiting)}`);\n }\n }\n return lines;\n}\n\nfunction renderFullSnapshot(\n ref: XrRef,\n snapshot: XrSnapshot,\n s: SnapshotSummary,\n ts: string,\n): string {\n const lines: string[] = [];\n const reason = s.reason ?? 'Pending';\n const target = `${ref.kind}/${ref.name}${ref.namespace ? ` (${ref.namespace})` : ''}`;\n const counts = formatCounts(s.readyCount, s.total, s.blockedCount);\n lines.push(\n `${chalk.dim(ts)} ${chalk.bold(target)} ${chalk.dim('·')} ${reason} ${chalk.dim('·')} ${counts}`,\n );\n if (snapshot.updatesThrottled) {\n lines.push(` ${chalk.yellow('⚠ Updates Throttled')}`);\n }\n if (s.blocked.size > 0) {\n lines.push(chalk.dim(' blocked:'));\n for (const [path, waiting] of s.blocked) {\n lines.push(` ${chalk.yellow('-')} ${path}${formatWaiting(waiting)}`);\n }\n }\n return lines.join('\\n');\n}\n\nfunction renderDelta(prev: SnapshotSummary, next: SnapshotSummary, ts: string): string | undefined {\n const becameReady: string[] = [];\n const becameBlocked: string[] = [];\n const waitingChanged: string[] = [];\n\n for (const path of next.ready) if (!prev.ready.has(path)) becameReady.push(path);\n for (const [path, waiting] of next.blocked) {\n if (!prev.blocked.has(path)) {\n becameBlocked.push(path);\n } else {\n const a = (prev.blocked.get(path) ?? []).join('|');\n const b = waiting.join('|');\n if (a !== b) waitingChanged.push(path);\n }\n }\n const reasonChanged = next.reason !== prev.reason;\n const totalsChanged =\n next.readyCount !== prev.readyCount ||\n next.blockedCount !== prev.blockedCount ||\n next.total !== prev.total;\n\n if (\n becameReady.length === 0 &&\n becameBlocked.length === 0 &&\n waitingChanged.length === 0 &&\n !reasonChanged &&\n !totalsChanged\n ) {\n return undefined;\n }\n\n const lines: string[] = [];\n const tsd = chalk.dim(ts);\n for (const path of becameReady) lines.push(`${tsd} ${chalk.green('+')} ${path}`);\n for (const path of becameBlocked) {\n const waiting = next.blocked.get(path) ?? [];\n lines.push(`${tsd} ${chalk.yellow('-')} ${path}${formatWaiting(waiting)}`);\n }\n for (const path of waitingChanged) {\n const waiting = next.blocked.get(path) ?? [];\n lines.push(`${tsd} ${chalk.yellow('~')} ${path}${formatWaiting(waiting)}`);\n }\n if (reasonChanged && next.reason) {\n lines.push(\n `${tsd} ${chalk.yellow('~')} reason: ${chalk.dim(prev.reason ?? '?')} → ${next.reason}`,\n );\n }\n lines.push(\n `${tsd} ${chalk.dim('→')} ${formatCounts(next.readyCount, next.total, next.blockedCount)}`,\n );\n return lines.join('\\n');\n}\n\nfunction formatCounts(ready: number, total: number, blocked: number): string {\n const unready = Math.max(0, total - ready);\n const grand = total + blocked;\n const pct = grand > 0 ? Math.round((ready / grand) * 100) : 0;\n const readyText = `✅ ${ready} ready`;\n const readyPart = total > 0 && ready === total ? chalk.green(readyText) : readyText;\n const unreadyPart = `⏳ ${unready} unready`;\n const blockedPart = `🚧 ${blocked} blocked`;\n const pctPart = `📈 ${pct}%`;\n const dot = '·';\n return `${readyPart} ${dot} ${unreadyPart} ${dot} ${blockedPart} ${dot} ${pctPart}`;\n}\n\nfunction formatWaiting(waiting: string[]): string {\n if (waiting.length === 0) return '';\n return chalk.dim(` waiting ${waiting.join(', ')}`);\n}\n","import chalk from 'chalk';\nimport { createLogUpdate } from 'log-update';\nimport { buildTree, type ResourceTree, type TreeNode } from '../watcher/tree.js';\nimport type { KubernetesEvent, XrEvent, XrRef, XrSnapshot } from '../watcher/types.js';\nimport type { XrWatcher } from '../watcher/xr-watcher.js';\nimport { formatHeader, formatProgress, statusGlyph } from './format.js';\n\nexport interface TtyRendererOptions {\n ref: XrRef;\n /** Destination stream. Defaults to `process.stdout`. */\n out?: NodeJS.WriteStream;\n /** Maximum number of recent Kubernetes events shown in the tail. Defaults to 5. */\n eventTailSize?: number;\n /** Allows callers (and tests) to provide a custom `log-update` instance. */\n logger?: { (frame: string): void; clear: () => void; done: () => void };\n}\n\n/**\n * Live full-screen renderer using `log-update` to repaint a single frame.\n * Resolves when the watcher ends.\n */\nexport async function renderTTY(watcher: XrWatcher, opts: TtyRendererOptions): Promise<void> {\n const out = opts.out ?? process.stdout;\n const logger = opts.logger ?? createLogUpdate(out);\n const tailSize = opts.eventTailSize ?? 5;\n const events: KubernetesEvent[] = [];\n let snapshot: XrSnapshot | undefined;\n let lastError: Error | undefined;\n\n const repaint = () => {\n logger(renderFrame(opts.ref, snapshot, events, lastError, out.rows, out.columns));\n };\n repaint();\n\n for await (const ev of watcher as AsyncIterable<XrEvent>) {\n if (ev.type === 'snapshot') {\n snapshot = ev.snapshot;\n repaint();\n } else if (ev.type === 'k8s-event') {\n events.push(ev.event);\n if (events.length > tailSize) events.splice(0, events.length - tailSize);\n repaint();\n } else if (ev.type === 'error') {\n lastError = ev.error;\n repaint();\n } else if (ev.type === 'ready') {\n snapshot = ev.snapshot;\n repaint();\n } else if (ev.type === 'end') {\n logger.done();\n return;\n }\n }\n logger.done();\n}\n\nfunction renderFrame(\n ref: XrRef,\n snapshot: XrSnapshot | undefined,\n events: KubernetesEvent[],\n err: Error | undefined,\n rows: number | undefined,\n columns: number | undefined,\n): string {\n const lines: string[] = [];\n if (snapshot) {\n lines.push(formatHeader(snapshot, ref));\n const tree = buildTree(snapshot);\n if (tree.stats.total > 0) {\n lines.push(\n ` ${formatProgress(tree.stats.ready, tree.stats.total, tree.stats.blocked)} ${chalk.dim(`(source: ${tree.source})`)}`,\n );\n }\n if (snapshot.readyMessage) {\n lines.push(chalk.dim(` ${snapshot.readyMessage}`));\n }\n if (tree.roots.length > 0) {\n lines.push('');\n lines.push(chalk.bold('Resources'));\n renderTreeLines(tree, lines);\n }\n } else {\n lines.push(chalk.dim(`waiting for first observation of ${ref.kind}/${ref.name}…`));\n }\n\n const errLines: string[] = [];\n if (err) {\n errLines.push('');\n errLines.push(chalk.red(`error: ${err.message}`));\n }\n\n const eventLines: string[] = [];\n if (events.length > 0) {\n eventLines.push('');\n eventLines.push(chalk.bold('Recent events'));\n for (const e of events) eventLines.push(` ${formatEventLine(e)}`);\n }\n\n // When the terminal height is known, ensure the snapshot header + tree are\n // always visible by trimming the event tail (oldest first) to whatever rows\n // remain after reserving space for the snapshot block and error footer.\n if (rows && rows > 0 && eventLines.length > 0) {\n const reserved = lines.length + errLines.length;\n const available = rows - reserved;\n if (available <= 2) {\n // No room for events at all — drop them entirely.\n eventLines.length = 0;\n } else if (eventLines.length > available) {\n // Keep the heading + blank line, drop the oldest events to fit.\n const keep = available - 2;\n const kept = eventLines.slice(eventLines.length - keep);\n eventLines.length = 0;\n eventLines.push('', chalk.bold('Recent events'), ...kept);\n }\n }\n\n const all = [...lines, ...eventLines, ...errLines];\n if (columns && columns > 0) {\n for (let i = 0; i < all.length; i++) {\n const line = all[i] as string;\n if (visibleWidth(line) > columns) all[i] = truncateAnsi(line, columns);\n }\n }\n return all.join('\\n');\n}\n\n// biome-ignore lint/suspicious/noControlCharactersInRegex: ANSI escape sequences require ESC (0x1B).\nconst ANSI_RE = /\\u001b\\[[0-9;]*m/g;\n\nfunction visibleWidth(s: string): number {\n return s.replace(ANSI_RE, '').length;\n}\n\n/** Truncate a string with embedded ANSI escapes to `maxVisible` visible chars,\n * appending an ellipsis and a reset code. */\nfunction truncateAnsi(s: string, maxVisible: number): string {\n if (maxVisible <= 1) return '\\u2026';\n let visible = 0;\n let out = '';\n const limit = maxVisible - 1;\n let i = 0;\n while (i < s.length && visible < limit) {\n ANSI_RE.lastIndex = i;\n const m = ANSI_RE.exec(s);\n if (m && m.index === i) {\n out += m[0];\n i = m.index + m[0].length;\n continue;\n }\n out += s[i];\n visible++;\n i++;\n }\n return `${out}\\u2026\\u001b[0m`;\n}\n\nfunction renderTreeLines(tree: ResourceTree, out: string[]): void {\n for (const root of tree.roots) renderNode(root, '', true, out);\n}\n\nfunction renderNode(node: TreeNode, prefix: string, isLast: boolean, out: string[]): void {\n const branch = prefix === '' ? '' : isLast ? '└─ ' : '├─ ';\n const state = node.blocked ? 'blocked' : node.ready ? 'ready' : 'pending';\n const meta = node.kind ? chalk.dim(` (${node.kind})`) : '';\n const nameDetail = node.name\n ? chalk.dim(node.namespace ? ` ${node.namespace}/${node.name}` : ` ${node.name}`)\n : '';\n const waiting =\n node.waitingFor && node.waitingFor.length > 0\n ? chalk.dim(` waiting for ${node.waitingFor.join(', ')}`)\n : '';\n out.push(`${prefix}${branch}${statusGlyph(state)} ${node.label}${meta}${nameDetail}${waiting}`);\n const childPrefix = prefix + (prefix === '' ? ' ' : isLast ? ' ' : '│ ');\n for (let i = 0; i < node.children.length; i++) {\n renderNode(node.children[i] as TreeNode, childPrefix, i === node.children.length - 1, out);\n }\n}\n\nfunction formatEventLine(ev: KubernetesEvent): string {\n const color = ev.type === 'Warning' ? chalk.yellow : chalk.dim;\n const t = ev.lastTimestamp ? new Date(ev.lastTimestamp).toISOString().slice(11, 19) : '';\n const target = ev.involvedKind ? `${ev.involvedKind}/${ev.involvedName ?? '?'} ` : '';\n return color(`${t} ${target}${ev.reason}: ${ev.message}`).trim();\n}\n","import type { XrRef } from '../watcher/types.js';\nimport type { XrWatcher } from '../watcher/xr-watcher.js';\nimport { renderCI } from './ci.js';\nimport { renderTTY } from './tty.js';\n\nexport { type CiRendererOptions, renderCI } from './ci.js';\nexport { formatEvent, formatHeader, formatProgress, statusGlyph } from './format.js';\nexport { renderTTY, type TtyRendererOptions } from './tty.js';\n\nexport type RendererMode = 'tty' | 'ci';\n\n/** Auto-select a renderer based on whether the destination is an interactive TTY. */\nexport function selectRenderer(stream: NodeJS.WriteStream | NodeJS.WritableStream): RendererMode {\n const tty = stream as Partial<NodeJS.WriteStream>;\n return tty.isTTY ? 'tty' : 'ci';\n}\n\nexport interface RunRendererOptions {\n ref: XrRef;\n /** Forces a renderer instead of auto-detecting. */\n mode?: RendererMode;\n /** Destination stream. Defaults to `process.stdout`. */\n out?: NodeJS.WriteStream;\n /** Max number of recent events kept in the TTY tail. */\n eventTailSize?: number;\n /** CI: heartbeat interval (ms) for liveness lines. 0 disables. */\n heartbeatMs?: number;\n /** CI: include K8s Events inline. Off by default. */\n showEvents?: boolean;\n /** CI: every N idle heartbeats, expand into a snapshot of unready + blocked resources. 0 disables. */\n snapshotEveryHeartbeats?: number;\n}\n\n/**\n * Drive either renderer against the supplied watcher. Resolves when the\n * watcher's event stream ends.\n */\nexport async function runRenderer(watcher: XrWatcher, opts: RunRendererOptions): Promise<void> {\n const out = opts.out ?? process.stdout;\n const mode = opts.mode ?? selectRenderer(out);\n if (mode === 'tty') {\n const ttyOpts: Parameters<typeof renderTTY>[1] = { ref: opts.ref, out };\n if (opts.eventTailSize !== undefined) ttyOpts.eventTailSize = opts.eventTailSize;\n return renderTTY(watcher, ttyOpts);\n }\n const ciOpts: Parameters<typeof renderCI>[1] = { ref: opts.ref, out };\n if (opts.heartbeatMs !== undefined) ciOpts.heartbeatMs = opts.heartbeatMs;\n if (opts.showEvents !== undefined) ciOpts.showEvents = opts.showEvents;\n if (opts.snapshotEveryHeartbeats !== undefined)\n ciOpts.snapshotEveryHeartbeats = opts.snapshotEveryHeartbeats;\n return renderCI(watcher, ciOpts);\n}\n"],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ //#region src/watcher/tree.ts
2
+ /** Build a `ResourceTree` from a snapshot, preferring `status.xplane` when present. */
3
+ function buildTree(snapshot) {
4
+ if (snapshot.xplane) return fromXplane(snapshot.xplane.emittedResources, snapshot.xplane.blockedResources);
5
+ if (snapshot.resourceRefs.length > 0) return fromResourceRefs(snapshot.resourceRefs);
6
+ return {
7
+ roots: [],
8
+ stats: {
9
+ total: 0,
10
+ ready: 0,
11
+ blocked: 0
12
+ },
13
+ source: "empty"
14
+ };
15
+ }
16
+ function fromXplane(emitted, blocked) {
17
+ const byPath = /* @__PURE__ */ new Map();
18
+ const ownEntry = /* @__PURE__ */ new Set();
19
+ const roots = [];
20
+ const ensureNode = (path) => {
21
+ const existing = byPath.get(path);
22
+ if (existing) return existing;
23
+ const segments = path.split("/");
24
+ const node = {
25
+ label: segments[segments.length - 1] ?? path,
26
+ path,
27
+ ready: false,
28
+ blocked: false,
29
+ children: []
30
+ };
31
+ byPath.set(path, node);
32
+ if (segments.length === 1) roots.push(node);
33
+ else ensureNode(segments.slice(0, -1).join("/")).children.push(node);
34
+ return node;
35
+ };
36
+ for (const r of emitted) {
37
+ const node = ensureNode(r.nodePath);
38
+ node.ready = r.ready;
39
+ node.apiVersion = r.apiVersion;
40
+ node.kind = r.kind;
41
+ if (r.name) node.name = r.name;
42
+ if (r.namespace) node.namespace = r.namespace;
43
+ ownEntry.add(r.nodePath);
44
+ }
45
+ for (const b of blocked) {
46
+ const node = ensureNode(b.nodePath);
47
+ node.blocked = true;
48
+ if (b.waitingFor) node.waitingFor = b.waitingFor;
49
+ if (!node.apiVersion) node.apiVersion = b.apiVersion;
50
+ if (!node.kind) node.kind = b.kind;
51
+ if (!node.name && b.name) node.name = b.name;
52
+ if (!node.namespace && b.namespace) node.namespace = b.namespace;
53
+ ownEntry.add(b.nodePath);
54
+ }
55
+ const aggregate = (node) => {
56
+ for (const child of node.children) aggregate(child);
57
+ if (!ownEntry.has(node.path) && node.children.length > 0) {
58
+ node.ready = node.children.every((c) => c.ready);
59
+ node.blocked = node.children.some((c) => c.blocked);
60
+ }
61
+ };
62
+ for (const root of roots) aggregate(root);
63
+ const readyCount = emitted.reduce((n, r) => n + (r.ready ? 1 : 0), 0);
64
+ return {
65
+ roots,
66
+ stats: {
67
+ total: emitted.length,
68
+ ready: readyCount,
69
+ blocked: blocked.length
70
+ },
71
+ source: "xplane"
72
+ };
73
+ }
74
+ function fromResourceRefs(refs) {
75
+ return {
76
+ roots: refs.map((r) => ({
77
+ label: r.name,
78
+ path: r.name,
79
+ ready: false,
80
+ blocked: false,
81
+ apiVersion: r.apiVersion,
82
+ kind: r.kind,
83
+ children: []
84
+ })),
85
+ stats: {
86
+ total: refs.length,
87
+ ready: 0,
88
+ blocked: 0
89
+ },
90
+ source: "resourceRefs"
91
+ };
92
+ }
93
+ //#endregion
94
+ export { buildTree as t };
95
+
96
+ //# sourceMappingURL=tree-DaHkojq8.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-DaHkojq8.mjs","names":[],"sources":["../src/watcher/tree.ts"],"sourcesContent":["import type { BlockedResource, EmittedResource, XrSnapshot } from './types.js';\n\n/**\n * A node in the construct-derived resource tree.\n *\n * Resource names emitted by compositions encode their construct path with `/` as\n * the separator (e.g. `\"CMS Database/Security Group\"`). The tree mirrors that\n * hierarchy so renderers can indent each branch.\n */\nexport interface TreeNode {\n /** Last segment of the construct path (display label). */\n label: string;\n /** Full construct path (joined with `/`). */\n path: string;\n /** Kubernetes `metadata.name` of the leaf resource, when known. */\n name?: string;\n /** Kubernetes `metadata.namespace` of the leaf resource, when present. */\n namespace?: string;\n /** True when an emitted resource at this exact path is ready. */\n ready: boolean;\n /** True when this exact path appears in `blockedResources`. */\n blocked: boolean;\n /** Unresolved dependencies copied from the blocked entry. */\n waitingFor?: string[];\n /** API version of the leaf resource (if any). */\n apiVersion?: string;\n /** Kind of the leaf resource (if any). */\n kind?: string;\n /** Child nodes keyed by path segment. */\n children: TreeNode[];\n}\n\n/** Aggregate counts derived from `status.xplane`. */\nexport interface TreeStats {\n /** Total emitted resources. */\n total: number;\n /** Emitted resources marked ready. */\n ready: number;\n /** Blocked resources. */\n blocked: number;\n}\n\nexport interface ResourceTree {\n /** Root-level nodes. */\n roots: TreeNode[];\n /** Aggregate counts. Zero values when no xplane status is available. */\n stats: TreeStats;\n /** Source used to build the tree. */\n source: 'xplane' | 'resourceRefs' | 'empty';\n}\n\n/** Build a `ResourceTree` from a snapshot, preferring `status.xplane` when present. */\nexport function buildTree(snapshot: XrSnapshot): ResourceTree {\n if (snapshot.xplane) {\n return fromXplane(snapshot.xplane.emittedResources, snapshot.xplane.blockedResources);\n }\n if (snapshot.resourceRefs.length > 0) {\n return fromResourceRefs(snapshot.resourceRefs);\n }\n return { roots: [], stats: { total: 0, ready: 0, blocked: 0 }, source: 'empty' };\n}\n\nfunction fromXplane(emitted: EmittedResource[], blocked: BlockedResource[]): ResourceTree {\n const byPath = new Map<string, TreeNode>();\n const ownEntry = new Set<string>();\n const roots: TreeNode[] = [];\n\n const ensureNode = (path: string): TreeNode => {\n const existing = byPath.get(path);\n if (existing) return existing;\n const segments = path.split('/');\n const label = segments[segments.length - 1] ?? path;\n const node: TreeNode = { label, path, ready: false, blocked: false, children: [] };\n byPath.set(path, node);\n if (segments.length === 1) {\n roots.push(node);\n } else {\n const parentPath = segments.slice(0, -1).join('/');\n ensureNode(parentPath).children.push(node);\n }\n return node;\n };\n\n for (const r of emitted) {\n const node = ensureNode(r.nodePath);\n node.ready = r.ready;\n node.apiVersion = r.apiVersion;\n node.kind = r.kind;\n if (r.name) node.name = r.name;\n if (r.namespace) node.namespace = r.namespace;\n ownEntry.add(r.nodePath);\n }\n for (const b of blocked) {\n const node = ensureNode(b.nodePath);\n node.blocked = true;\n if (b.waitingFor) node.waitingFor = b.waitingFor;\n if (!node.apiVersion) node.apiVersion = b.apiVersion;\n if (!node.kind) node.kind = b.kind;\n if (!node.name && b.name) node.name = b.name;\n if (!node.namespace && b.namespace) node.namespace = b.namespace;\n ownEntry.add(b.nodePath);\n }\n\n // Container nodes (synthesised parents with no resource of their own) aggregate\n // readiness from their descendants: ready iff every descendant is ready and\n // none are blocked.\n const aggregate = (node: TreeNode): void => {\n for (const child of node.children) aggregate(child);\n if (!ownEntry.has(node.path) && node.children.length > 0) {\n node.ready = node.children.every((c) => c.ready);\n node.blocked = node.children.some((c) => c.blocked);\n }\n };\n for (const root of roots) aggregate(root);\n\n const readyCount = emitted.reduce((n, r) => n + (r.ready ? 1 : 0), 0);\n return {\n roots,\n stats: { total: emitted.length, ready: readyCount, blocked: blocked.length },\n source: 'xplane',\n };\n}\n\nfunction fromResourceRefs(refs: XrSnapshot['resourceRefs']): ResourceTree {\n const roots = refs.map<TreeNode>((r) => ({\n label: r.name,\n path: r.name,\n ready: false,\n blocked: false,\n apiVersion: r.apiVersion,\n kind: r.kind,\n children: [],\n }));\n return {\n roots,\n stats: { total: refs.length, ready: 0, blocked: 0 },\n source: 'resourceRefs',\n };\n}\n"],"mappings":";;AAoDA,SAAgB,UAAU,UAAoC;CAC5D,IAAI,SAAS,QACX,OAAO,WAAW,SAAS,OAAO,kBAAkB,SAAS,OAAO,gBAAgB;CAEtF,IAAI,SAAS,aAAa,SAAS,GACjC,OAAO,iBAAiB,SAAS,YAAY;CAE/C,OAAO;EAAE,OAAO,CAAC;EAAG,OAAO;GAAE,OAAO;GAAG,OAAO;GAAG,SAAS;EAAE;EAAG,QAAQ;CAAQ;AACjF;AAEA,SAAS,WAAW,SAA4B,SAA0C;CACxF,MAAM,yBAAS,IAAI,IAAsB;CACzC,MAAM,2BAAW,IAAI,IAAY;CACjC,MAAM,QAAoB,CAAC;CAE3B,MAAM,cAAc,SAA2B;EAC7C,MAAM,WAAW,OAAO,IAAI,IAAI;EAChC,IAAI,UAAU,OAAO;EACrB,MAAM,WAAW,KAAK,MAAM,GAAG;EAE/B,MAAM,OAAiB;GAAE,OADX,SAAS,SAAS,SAAS,MAAM;GACf;GAAM,OAAO;GAAO,SAAS;GAAO,UAAU,CAAC;EAAE;EACjF,OAAO,IAAI,MAAM,IAAI;EACrB,IAAI,SAAS,WAAW,GACtB,MAAM,KAAK,IAAI;OAGf,WADmB,SAAS,MAAM,GAAG,EAAE,EAAE,KAAK,GAC1B,CAAC,EAAE,SAAS,KAAK,IAAI;EAE3C,OAAO;CACT;CAEA,KAAK,MAAM,KAAK,SAAS;EACvB,MAAM,OAAO,WAAW,EAAE,QAAQ;EAClC,KAAK,QAAQ,EAAE;EACf,KAAK,aAAa,EAAE;EACpB,KAAK,OAAO,EAAE;EACd,IAAI,EAAE,MAAM,KAAK,OAAO,EAAE;EAC1B,IAAI,EAAE,WAAW,KAAK,YAAY,EAAE;EACpC,SAAS,IAAI,EAAE,QAAQ;CACzB;CACA,KAAK,MAAM,KAAK,SAAS;EACvB,MAAM,OAAO,WAAW,EAAE,QAAQ;EAClC,KAAK,UAAU;EACf,IAAI,EAAE,YAAY,KAAK,aAAa,EAAE;EACtC,IAAI,CAAC,KAAK,YAAY,KAAK,aAAa,EAAE;EAC1C,IAAI,CAAC,KAAK,MAAM,KAAK,OAAO,EAAE;EAC9B,IAAI,CAAC,KAAK,QAAQ,EAAE,MAAM,KAAK,OAAO,EAAE;EACxC,IAAI,CAAC,KAAK,aAAa,EAAE,WAAW,KAAK,YAAY,EAAE;EACvD,SAAS,IAAI,EAAE,QAAQ;CACzB;CAKA,MAAM,aAAa,SAAyB;EAC1C,KAAK,MAAM,SAAS,KAAK,UAAU,UAAU,KAAK;EAClD,IAAI,CAAC,SAAS,IAAI,KAAK,IAAI,KAAK,KAAK,SAAS,SAAS,GAAG;GACxD,KAAK,QAAQ,KAAK,SAAS,OAAO,MAAM,EAAE,KAAK;GAC/C,KAAK,UAAU,KAAK,SAAS,MAAM,MAAM,EAAE,OAAO;EACpD;CACF;CACA,KAAK,MAAM,QAAQ,OAAO,UAAU,IAAI;CAExC,MAAM,aAAa,QAAQ,QAAQ,GAAG,MAAM,KAAK,EAAE,QAAQ,IAAI,IAAI,CAAC;CACpE,OAAO;EACL;EACA,OAAO;GAAE,OAAO,QAAQ;GAAQ,OAAO;GAAY,SAAS,QAAQ;EAAO;EAC3E,QAAQ;CACV;AACF;AAEA,SAAS,iBAAiB,MAAgD;CAUxE,OAAO;EACL,OAVY,KAAK,KAAe,OAAO;GACvC,OAAO,EAAE;GACT,MAAM,EAAE;GACR,OAAO;GACP,SAAS;GACT,YAAY,EAAE;GACd,MAAM,EAAE;GACR,UAAU,CAAC;EACb,EAEM;EACJ,OAAO;GAAE,OAAO,KAAK;GAAQ,OAAO;GAAG,SAAS;EAAE;EAClD,QAAQ;CACV;AACF"}