@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.
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,394 @@
1
+ #!/usr/bin/env node
2
+ import { a as awaitReady, n as listXrCollection, o as loadKubeConfig, r as parseTarget, t as createXrWatcher } from "./xr-watcher-1etyvp-6.mjs";
3
+ import { t as runRenderer } from "./render-Cnhpyf1X.mjs";
4
+ import { createRequire } from "node:module";
5
+ import { ApiextensionsV1Api } from "@kubernetes/client-node";
6
+ import { defineCommand, runMain } from "citty";
7
+ //#region src/cli/discovery.ts
8
+ let crdCache;
9
+ /**
10
+ * Fetch the cluster's CRDs and project them into a flat list of resolved
11
+ * resources (one entry per served version). Cached per `KubeConfig` instance.
12
+ */
13
+ async function listCrdResources(kc) {
14
+ crdCache ??= /* @__PURE__ */ new WeakMap();
15
+ const cached = crdCache.get(kc);
16
+ if (cached) return cached;
17
+ const promise = fetchCrdResources(kc).catch((err) => {
18
+ crdCache?.delete(kc);
19
+ throw err;
20
+ });
21
+ crdCache.set(kc, promise);
22
+ return promise;
23
+ }
24
+ async function fetchCrdResources(kc) {
25
+ const list = await kc.makeApiClient(ApiextensionsV1Api).listCustomResourceDefinition();
26
+ const out = [];
27
+ for (const item of list.items ?? []) {
28
+ const spec = item.spec;
29
+ if (!spec?.group || !spec.names?.kind || !spec.names.plural) continue;
30
+ const namespaced = spec.scope === "Namespaced";
31
+ for (const v of spec.versions ?? []) {
32
+ if (!v.name || v.served === false) continue;
33
+ out.push({
34
+ group: spec.group,
35
+ version: v.name,
36
+ kind: spec.names.kind,
37
+ plural: spec.names.plural,
38
+ namespaced
39
+ });
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+ /**
45
+ * Resolve a `<resource>[.group][.version]` user hint to a single CRD-backed
46
+ * resource. Throws when nothing matches or when the hint is ambiguous.
47
+ */
48
+ async function resolveResource(kc, hint) {
49
+ const all = await listCrdResources(kc);
50
+ const r = hint.resource.toLowerCase();
51
+ const matches = all.filter((c) => {
52
+ const kindLc = c.kind.toLowerCase();
53
+ const pluralLc = c.plural.toLowerCase();
54
+ if (kindLc !== r && pluralLc !== r) return false;
55
+ if (hint.group !== void 0 && c.group !== hint.group) return false;
56
+ if (hint.version !== void 0 && c.version !== hint.version) return false;
57
+ return true;
58
+ });
59
+ if (matches.length === 0) {
60
+ const detail = [
61
+ hint.resource,
62
+ hint.group,
63
+ hint.version
64
+ ].filter(Boolean).join(".");
65
+ throw new Error(`No CRD found matching "${detail}". Available kinds: ${summarise(all)}.`);
66
+ }
67
+ if (matches.length > 1) {
68
+ if (hint.version === void 0) {
69
+ const grouped = /* @__PURE__ */ new Map();
70
+ for (const m of matches) grouped.set(`${m.group}/${m.kind}`, [...grouped.get(`${m.group}/${m.kind}`) ?? [], m]);
71
+ if (grouped.size === 1) return [...matches].sort((a, b) => b.version.localeCompare(a.version))[0];
72
+ }
73
+ const candidates = matches.map((m) => `${m.plural}.${m.version}.${m.group}`).join(", ");
74
+ throw new Error(`Ambiguous resource hint "${hint.resource}". Candidates: ${candidates}. Disambiguate with .group or .version.group.`);
75
+ }
76
+ return matches[0];
77
+ }
78
+ function summarise(all) {
79
+ const seen = /* @__PURE__ */ new Set();
80
+ const out = [];
81
+ for (const r of all) {
82
+ const k = `${r.plural}.${r.group}`;
83
+ if (seen.has(k)) continue;
84
+ seen.add(k);
85
+ out.push(k);
86
+ if (out.length >= 20) {
87
+ out.push("…");
88
+ break;
89
+ }
90
+ }
91
+ return out.join(", ");
92
+ }
93
+ //#endregion
94
+ //#region src/cli/get-status.ts
95
+ /**
96
+ * Headless implementation of the `xplane-utils get-status` subcommand.
97
+ * Fetches a single XR and prints its `.status` to `out` in the requested format.
98
+ */
99
+ async function runGetStatusCommand(args, deps = {}) {
100
+ const load = deps.loadKubeConfig ?? loadKubeConfig;
101
+ const resolve = deps.resolveResource ?? resolveResource;
102
+ const list = deps.listXrCollection ?? listXrCollection;
103
+ const out = deps.out ?? process.stdout;
104
+ const kcOpts = {};
105
+ if (args.kubeconfig !== void 0) kcOpts.kubeconfig = args.kubeconfig;
106
+ if (args.context !== void 0) kcOpts.context = args.context;
107
+ const kc = load(kcOpts);
108
+ const parsed = parseTarget(args.target);
109
+ const resolved = await resolve(kc, {
110
+ resource: parsed.resource,
111
+ ...parsed.group !== void 0 ? { group: parsed.group } : {},
112
+ ...parsed.version !== void 0 ? { version: parsed.version } : {}
113
+ });
114
+ const ref = {
115
+ group: resolved.group,
116
+ version: resolved.version,
117
+ plural: resolved.plural,
118
+ kind: resolved.kind,
119
+ namespaced: resolved.namespaced,
120
+ name: parsed.name,
121
+ ...args.namespace !== void 0 ? { namespace: args.namespace } : {}
122
+ };
123
+ if (ref.namespaced && !ref.namespace) return {
124
+ code: 1,
125
+ error: `${ref.kind} is namespaced — pass --namespace/-n`
126
+ };
127
+ const item = (await list(kc, ref)).items[0];
128
+ if (!item) return {
129
+ code: 1,
130
+ error: `${`${ref.kind}/${ref.name}${ref.namespace ? ` -n ${ref.namespace}` : ""}`} not found`
131
+ };
132
+ const status = item.status ?? {};
133
+ const excluded = /* @__PURE__ */ new Set();
134
+ if (!args.includeXplane) excluded.add("xplane");
135
+ if (!args.includeConditions) excluded.add("conditions");
136
+ const filtered = excluded.size === 0 ? status : Object.fromEntries(Object.entries(status).filter(([k]) => !excluded.has(k)));
137
+ const text = (args.format ?? "dot") === "json" ? JSON.stringify(filtered, null, args.pretty === false ? 0 : 2) : toDotLines(filtered).join("\n");
138
+ out.write(text.length > 0 ? `${text}\n` : "");
139
+ return { code: 0 };
140
+ }
141
+ /**
142
+ * Flatten an arbitrary JSON value into `path=value` lines. Object keys are
143
+ * joined with `.`; array indices use `[n]`. Leaf values are JSON-encoded so
144
+ * strings retain their quoting and special characters are preserved.
145
+ */
146
+ function toDotLines(value, prefix = "") {
147
+ if (value === void 0) return [];
148
+ if (value === null) return [`${prefix || "."}=null`];
149
+ if (Array.isArray(value)) {
150
+ if (value.length === 0) return [`${prefix || "."}=[]`];
151
+ return value.flatMap((v, i) => toDotLines(v, `${prefix}[${i}]`));
152
+ }
153
+ if (typeof value === "object") {
154
+ const entries = Object.entries(value);
155
+ if (entries.length === 0) return [`${prefix || "."}={}`];
156
+ return entries.flatMap(([k, v]) => toDotLines(v, prefix ? `${prefix}.${k}` : k));
157
+ }
158
+ return [`${prefix || "."}=${JSON.stringify(value)}`];
159
+ }
160
+ //#endregion
161
+ //#region src/cli/duration.ts
162
+ /** Parse a duration like `30s`, `5m`, `2h`, `100ms`, or a bare integer (milliseconds). */
163
+ function parseDuration(input) {
164
+ if (input === void 0 || input === "") return void 0;
165
+ const m = /^(\d+)(ms|s|m|h)?$/.exec(input);
166
+ if (!m) throw new Error(`Invalid duration "${input}". Use e.g. "30s", "5m", "1h", or a number of ms.`);
167
+ const n = Number(m[1]);
168
+ switch (m[2]) {
169
+ case void 0:
170
+ case "ms": return n;
171
+ case "s": return n * 1e3;
172
+ case "m": return n * 6e4;
173
+ case "h": return n * 36e5;
174
+ }
175
+ }
176
+ //#endregion
177
+ //#region src/cli/watch.ts
178
+ /**
179
+ * Headless implementation of the `xplane-utils watch` subcommand.
180
+ * Returns the exit code instead of calling `process.exit` so it is unit-testable.
181
+ */
182
+ async function runWatchCommand(args, deps = {}) {
183
+ const load = deps.loadKubeConfig ?? loadKubeConfig;
184
+ const resolve = deps.resolveResource ?? resolveResource;
185
+ const factory = deps.createXrWatcher ?? createXrWatcher;
186
+ const render = deps.runRenderer ?? runRenderer;
187
+ const wait = deps.awaitReady ?? awaitReady;
188
+ const kcOpts = {};
189
+ if (args.kubeconfig !== void 0) kcOpts.kubeconfig = args.kubeconfig;
190
+ if (args.context !== void 0) kcOpts.context = args.context;
191
+ const kc = load(kcOpts);
192
+ const parsed = parseTarget(args.target);
193
+ const resolved = await resolve(kc, {
194
+ resource: parsed.resource,
195
+ ...parsed.group !== void 0 ? { group: parsed.group } : {},
196
+ ...parsed.version !== void 0 ? { version: parsed.version } : {}
197
+ });
198
+ const ref = {
199
+ group: resolved.group,
200
+ version: resolved.version,
201
+ plural: resolved.plural,
202
+ kind: resolved.kind,
203
+ namespaced: resolved.namespaced,
204
+ name: parsed.name,
205
+ ...args.namespace !== void 0 ? { namespace: args.namespace } : {}
206
+ };
207
+ if (ref.namespaced && !ref.namespace) return {
208
+ code: 1,
209
+ error: `${ref.kind} is namespaced — pass --namespace/-n`
210
+ };
211
+ const watcher = factory({
212
+ kubeConfig: kc,
213
+ ref,
214
+ ...deps.signal !== void 0 ? { signal: deps.signal } : {},
215
+ ...args.disableEvents ? { disableEvents: true } : {}
216
+ });
217
+ const mode = args.mode === "tty" || args.mode === "ci" ? args.mode : void 0;
218
+ const renderOpts = { ref };
219
+ if (mode) renderOpts.mode = mode;
220
+ if (deps.out) renderOpts.out = deps.out;
221
+ const heartbeatMs = parseDuration(args.heartbeat);
222
+ if (heartbeatMs !== void 0) renderOpts.heartbeatMs = heartbeatMs;
223
+ if (args.showEvents !== void 0) renderOpts.showEvents = args.showEvents;
224
+ if (args.snapshotEveryHeartbeats !== void 0) renderOpts.snapshotEveryHeartbeats = args.snapshotEveryHeartbeats;
225
+ const renderPromise = render(watcher, renderOpts);
226
+ const timeoutMs = parseDuration(args.timeout);
227
+ try {
228
+ await wait(watcher, timeoutMs !== void 0 ? { timeoutMs } : {});
229
+ watcher.stop();
230
+ await renderPromise;
231
+ return { code: 0 };
232
+ } catch (_err) {
233
+ watcher.stop();
234
+ await renderPromise.catch(() => void 0);
235
+ return { code: 1 };
236
+ }
237
+ }
238
+ //#endregion
239
+ //#region src/cli.ts
240
+ const { version } = createRequire(import.meta.url)("../package.json");
241
+ const watch = defineCommand({
242
+ meta: {
243
+ name: "watch",
244
+ description: "Subscribe to a Crossplane XR and block until it becomes Ready"
245
+ },
246
+ args: {
247
+ target: {
248
+ type: "positional",
249
+ required: true,
250
+ description: "kubectl-style target: <resource[.group][.version]>/<name>"
251
+ },
252
+ namespace: {
253
+ type: "string",
254
+ alias: "n",
255
+ description: "Namespace (required for namespaced XRs)"
256
+ },
257
+ kubeconfig: {
258
+ type: "string",
259
+ description: "Path to kubeconfig"
260
+ },
261
+ context: {
262
+ type: "string",
263
+ description: "Override the active kubeconfig context"
264
+ },
265
+ timeout: {
266
+ type: "string",
267
+ description: "Maximum time to wait for Ready, e.g. \"30s\", \"5m\", \"1h\""
268
+ },
269
+ mode: {
270
+ type: "string",
271
+ description: "Force renderer mode: \"tty\" or \"ci\" (default: auto)"
272
+ },
273
+ "no-events": {
274
+ type: "boolean",
275
+ description: "Disable subscribing to Kubernetes Events for the XR"
276
+ },
277
+ heartbeat: {
278
+ type: "string",
279
+ description: "CI mode: heartbeat interval for liveness lines (default \"30s\", \"0\" to disable)"
280
+ },
281
+ "show-events": {
282
+ type: "boolean",
283
+ description: "CI mode: include Kubernetes Events inline (off by default)"
284
+ },
285
+ "snapshot-every": {
286
+ type: "string",
287
+ description: "CI mode: every N idle heartbeats, expand the liveness line into a snapshot of unready + blocked resources (default 10, 0 to disable)"
288
+ }
289
+ },
290
+ async run({ args }) {
291
+ const controller = new AbortController();
292
+ process.on("SIGINT", () => controller.abort());
293
+ process.on("SIGTERM", () => controller.abort());
294
+ const cmdArgs = { target: String(args.target) };
295
+ if (typeof args.namespace === "string") cmdArgs.namespace = args.namespace;
296
+ if (typeof args.kubeconfig === "string") cmdArgs.kubeconfig = args.kubeconfig;
297
+ if (typeof args.context === "string") cmdArgs.context = args.context;
298
+ if (typeof args.timeout === "string") cmdArgs.timeout = args.timeout;
299
+ if (typeof args.mode === "string") cmdArgs.mode = args.mode;
300
+ if (args["no-events"]) cmdArgs.disableEvents = true;
301
+ if (typeof args.heartbeat === "string") cmdArgs.heartbeat = args.heartbeat;
302
+ if (args["show-events"]) cmdArgs.showEvents = true;
303
+ if (typeof args["snapshot-every"] === "string") {
304
+ const n = Number.parseInt(args["snapshot-every"], 10);
305
+ if (Number.isFinite(n) && n >= 0) cmdArgs.snapshotEveryHeartbeats = n;
306
+ }
307
+ const result = await runWatchCommand(cmdArgs, { signal: controller.signal });
308
+ if (result.code !== 0) {
309
+ if (result.error) process.stderr.write(`${result.error}\n`);
310
+ process.exit(result.code);
311
+ }
312
+ process.exit(0);
313
+ }
314
+ });
315
+ const getStatus = defineCommand({
316
+ meta: {
317
+ name: "get-status",
318
+ description: "Fetch a Crossplane XR and print its .status"
319
+ },
320
+ args: {
321
+ target: {
322
+ type: "positional",
323
+ required: true,
324
+ description: "kubectl-style target: <resource[.group][.version]>/<name>"
325
+ },
326
+ namespace: {
327
+ type: "string",
328
+ alias: "n",
329
+ description: "Namespace (required for namespaced XRs)"
330
+ },
331
+ kubeconfig: {
332
+ type: "string",
333
+ description: "Path to kubeconfig"
334
+ },
335
+ context: {
336
+ type: "string",
337
+ description: "Override the active kubeconfig context"
338
+ },
339
+ format: {
340
+ type: "string",
341
+ alias: "o",
342
+ description: "Output format: \"dot\" (default) or \"json\""
343
+ },
344
+ compact: {
345
+ type: "boolean",
346
+ description: "JSON mode: emit compact (single-line) JSON instead of pretty-printed"
347
+ },
348
+ "include-xplane": {
349
+ type: "boolean",
350
+ description: "Include the framework-managed status.xplane subtree (excluded by default)"
351
+ },
352
+ "include-conditions": {
353
+ type: "boolean",
354
+ description: "Include status.conditions (excluded by default)"
355
+ }
356
+ },
357
+ async run({ args }) {
358
+ const cmdArgs = { target: String(args.target) };
359
+ if (typeof args.namespace === "string") cmdArgs.namespace = args.namespace;
360
+ if (typeof args.kubeconfig === "string") cmdArgs.kubeconfig = args.kubeconfig;
361
+ if (typeof args.context === "string") cmdArgs.context = args.context;
362
+ if (typeof args.format === "string") {
363
+ if (args.format !== "dot" && args.format !== "json") {
364
+ process.stderr.write(`Invalid --format "${args.format}" (expected "dot" or "json")\n`);
365
+ process.exit(2);
366
+ }
367
+ cmdArgs.format = args.format;
368
+ }
369
+ if (args.compact) cmdArgs.pretty = false;
370
+ if (args["include-xplane"]) cmdArgs.includeXplane = true;
371
+ if (args["include-conditions"]) cmdArgs.includeConditions = true;
372
+ const result = await runGetStatusCommand(cmdArgs);
373
+ if (result.code !== 0) {
374
+ process.stderr.write(`${result.error}\n`);
375
+ process.exit(result.code);
376
+ }
377
+ process.exit(0);
378
+ }
379
+ });
380
+ runMain(defineCommand({
381
+ meta: {
382
+ name: "xplane-utils",
383
+ version,
384
+ description: "Utilities for Crossplane compositions built with xplane"
385
+ },
386
+ subCommands: {
387
+ watch,
388
+ "get-status": getStatus
389
+ }
390
+ }));
391
+ //#endregion
392
+ export {};
393
+
394
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli/discovery.ts","../src/cli/get-status.ts","../src/cli/duration.ts","../src/cli/watch.ts","../src/cli.ts"],"sourcesContent":["import { ApiextensionsV1Api, type KubeConfig } from '@kubernetes/client-node';\n\n/** Resolved API resource metadata. */\nexport interface ResolvedResource {\n group: string;\n version: string;\n kind: string;\n plural: string;\n namespaced: boolean;\n}\n\nlet crdCache: WeakMap<KubeConfig, Promise<ResolvedResource[]>> | undefined;\n\n/**\n * Fetch the cluster's CRDs and project them into a flat list of resolved\n * resources (one entry per served version). Cached per `KubeConfig` instance.\n */\nexport async function listCrdResources(kc: KubeConfig): Promise<ResolvedResource[]> {\n crdCache ??= new WeakMap();\n const cached = crdCache.get(kc);\n if (cached) return cached;\n const promise = fetchCrdResources(kc).catch((err) => {\n crdCache?.delete(kc);\n throw err;\n });\n crdCache.set(kc, promise);\n return promise;\n}\n\nasync function fetchCrdResources(kc: KubeConfig): Promise<ResolvedResource[]> {\n const api = kc.makeApiClient(ApiextensionsV1Api);\n const list = await api.listCustomResourceDefinition();\n const out: ResolvedResource[] = [];\n for (const item of list.items ?? []) {\n const spec = item.spec;\n if (!spec?.group || !spec.names?.kind || !spec.names.plural) continue;\n const namespaced = spec.scope === 'Namespaced';\n for (const v of spec.versions ?? []) {\n if (!v.name || v.served === false) continue;\n out.push({\n group: spec.group,\n version: v.name,\n kind: spec.names.kind,\n plural: spec.names.plural,\n namespaced,\n });\n }\n }\n return out;\n}\n\n/**\n * Resolve a `<resource>[.group][.version]` user hint to a single CRD-backed\n * resource. Throws when nothing matches or when the hint is ambiguous.\n */\nexport async function resolveResource(\n kc: KubeConfig,\n hint: { resource: string; group?: string; version?: string },\n): Promise<ResolvedResource> {\n const all = await listCrdResources(kc);\n const r = hint.resource.toLowerCase();\n const matches = all.filter((c) => {\n const kindLc = c.kind.toLowerCase();\n const pluralLc = c.plural.toLowerCase();\n if (kindLc !== r && pluralLc !== r) return false;\n if (hint.group !== undefined && c.group !== hint.group) return false;\n if (hint.version !== undefined && c.version !== hint.version) return false;\n return true;\n });\n if (matches.length === 0) {\n const detail = [hint.resource, hint.group, hint.version].filter(Boolean).join('.');\n throw new Error(`No CRD found matching \"${detail}\". Available kinds: ${summarise(all)}.`);\n }\n if (matches.length > 1) {\n if (hint.version === undefined) {\n const grouped = new Map<string, ResolvedResource[]>();\n for (const m of matches)\n grouped.set(`${m.group}/${m.kind}`, [...(grouped.get(`${m.group}/${m.kind}`) ?? []), m]);\n if (grouped.size === 1) {\n // Multiple served versions of the same kind/group — pick the lexicographically highest.\n const sorted = [...matches].sort((a, b) => b.version.localeCompare(a.version));\n return sorted[0] as ResolvedResource;\n }\n }\n const candidates = matches.map((m) => `${m.plural}.${m.version}.${m.group}`).join(', ');\n throw new Error(\n `Ambiguous resource hint \"${hint.resource}\". Candidates: ${candidates}. Disambiguate with .group or .version.group.`,\n );\n }\n return matches[0] as ResolvedResource;\n}\n\nfunction summarise(all: ResolvedResource[]): string {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const r of all) {\n const k = `${r.plural}.${r.group}`;\n if (seen.has(k)) continue;\n seen.add(k);\n out.push(k);\n if (out.length >= 20) {\n out.push('…');\n break;\n }\n }\n return out.join(', ');\n}\n","import type { KubeConfig } from '@kubernetes/client-node';\nimport { type LoadKubeConfigOptions, loadKubeConfig } from '../client/kubeconfig.js';\nimport { listXrCollection } from '../client/lists.js';\nimport { parseTarget } from '../watcher/target.js';\nimport type { XrRef } from '../watcher/types.js';\nimport { resolveResource } from './discovery.js';\n\nexport type GetStatusFormat = 'dot' | 'json';\n\nexport interface GetStatusCommandArgs {\n target: string;\n namespace?: string;\n kubeconfig?: string;\n context?: string;\n /** Output format. Defaults to \"dot\". */\n format?: GetStatusFormat;\n /** Pretty-print JSON output. Ignored for `dot`. Defaults to true. */\n pretty?: boolean;\n /** Include the framework-managed `status.xplane` subtree. Defaults to false. */\n includeXplane?: boolean;\n /** Include `status.conditions`. Defaults to false. */\n includeConditions?: boolean;\n}\n\nexport interface GetStatusCommandDeps {\n loadKubeConfig?: (opts: LoadKubeConfigOptions) => KubeConfig;\n resolveResource?: typeof resolveResource;\n listXrCollection?: typeof listXrCollection;\n /** Destination stream for the rendered output. */\n out?: NodeJS.WritableStream;\n}\n\nexport type GetStatusResult = { code: 0 } | { code: 1; error: string };\n\n/**\n * Headless implementation of the `xplane-utils get-status` subcommand.\n * Fetches a single XR and prints its `.status` to `out` in the requested format.\n */\nexport async function runGetStatusCommand(\n args: GetStatusCommandArgs,\n deps: GetStatusCommandDeps = {},\n): Promise<GetStatusResult> {\n const load = deps.loadKubeConfig ?? loadKubeConfig;\n const resolve = deps.resolveResource ?? resolveResource;\n const list = deps.listXrCollection ?? listXrCollection;\n const out = deps.out ?? process.stdout;\n\n const kcOpts: LoadKubeConfigOptions = {};\n if (args.kubeconfig !== undefined) kcOpts.kubeconfig = args.kubeconfig;\n if (args.context !== undefined) kcOpts.context = args.context;\n const kc = load(kcOpts);\n\n const parsed = parseTarget(args.target);\n const resolved = await resolve(kc, {\n resource: parsed.resource,\n ...(parsed.group !== undefined ? { group: parsed.group } : {}),\n ...(parsed.version !== undefined ? { version: parsed.version } : {}),\n });\n const ref: XrRef = {\n group: resolved.group,\n version: resolved.version,\n plural: resolved.plural,\n kind: resolved.kind,\n namespaced: resolved.namespaced,\n name: parsed.name,\n ...(args.namespace !== undefined ? { namespace: args.namespace } : {}),\n };\n if (ref.namespaced && !ref.namespace) {\n return { code: 1, error: `${ref.kind} is namespaced — pass --namespace/-n` };\n }\n\n const result = await list(kc, ref);\n const item = result.items[0] as Record<string, unknown> | undefined;\n if (!item) {\n const target = `${ref.kind}/${ref.name}${ref.namespace ? ` -n ${ref.namespace}` : ''}`;\n return { code: 1, error: `${target} not found` };\n }\n const status = (item.status ?? {}) as Record<string, unknown>;\n const excluded = new Set<string>();\n if (!args.includeXplane) excluded.add('xplane');\n if (!args.includeConditions) excluded.add('conditions');\n const filtered =\n excluded.size === 0\n ? status\n : Object.fromEntries(Object.entries(status).filter(([k]) => !excluded.has(k)));\n\n const format: GetStatusFormat = args.format ?? 'dot';\n const text =\n format === 'json'\n ? JSON.stringify(filtered, null, args.pretty === false ? 0 : 2)\n : toDotLines(filtered).join('\\n');\n out.write(text.length > 0 ? `${text}\\n` : '');\n return { code: 0 };\n}\n\n/**\n * Flatten an arbitrary JSON value into `path=value` lines. Object keys are\n * joined with `.`; array indices use `[n]`. Leaf values are JSON-encoded so\n * strings retain their quoting and special characters are preserved.\n */\nexport function toDotLines(value: unknown, prefix = ''): string[] {\n if (value === undefined) return [];\n if (value === null) return [`${prefix || '.'}=null`];\n if (Array.isArray(value)) {\n if (value.length === 0) return [`${prefix || '.'}=[]`];\n return value.flatMap((v, i) => toDotLines(v, `${prefix}[${i}]`));\n }\n if (typeof value === 'object') {\n const entries = Object.entries(value as Record<string, unknown>);\n if (entries.length === 0) return [`${prefix || '.'}={}`];\n return entries.flatMap(([k, v]) => toDotLines(v, prefix ? `${prefix}.${k}` : k));\n }\n return [`${prefix || '.'}=${JSON.stringify(value)}`];\n}\n","/** Parse a duration like `30s`, `5m`, `2h`, `100ms`, or a bare integer (milliseconds). */\nexport function parseDuration(input: string | undefined): number | undefined {\n if (input === undefined || input === '') return undefined;\n const m = /^(\\d+)(ms|s|m|h)?$/.exec(input);\n if (!m)\n throw new Error(`Invalid duration \"${input}\". Use e.g. \"30s\", \"5m\", \"1h\", or a number of ms.`);\n const n = Number(m[1]);\n switch (m[2]) {\n case undefined:\n case 'ms':\n return n;\n case 's':\n return n * 1000;\n case 'm':\n return n * 60_000;\n case 'h':\n return n * 3_600_000;\n }\n // Unreachable — regex guarantees one of the cases above matches.\n /* c8 ignore next */\n return undefined;\n}\n","import type { KubeConfig } from '@kubernetes/client-node';\nimport { type LoadKubeConfigOptions, loadKubeConfig } from '../client/kubeconfig.js';\nimport { type RendererMode, runRenderer } from '../render/index.js';\nimport { awaitReady } from '../watcher/await-ready.js';\nimport { parseTarget } from '../watcher/target.js';\nimport type { XrRef } from '../watcher/types.js';\nimport { createXrWatcher, type XrWatcher } from '../watcher/xr-watcher.js';\nimport { resolveResource } from './discovery.js';\nimport { parseDuration } from './duration.js';\n\nexport interface WatchCommandArgs {\n target: string;\n namespace?: string;\n kubeconfig?: string;\n context?: string;\n timeout?: string;\n mode?: string;\n disableEvents?: boolean;\n /** CI: heartbeat interval (e.g. \"30s\"). 0 disables. */\n heartbeat?: string;\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\nexport interface WatchCommandDeps {\n /** Override KubeConfig loader (for tests). */\n loadKubeConfig?: (opts: LoadKubeConfigOptions) => KubeConfig;\n /** Override discovery (for tests). */\n resolveResource?: typeof resolveResource;\n /** Override watcher factory (for tests). */\n createXrWatcher?: typeof createXrWatcher;\n /** Override renderer (for tests). */\n runRenderer?: typeof runRenderer;\n /** Override awaitReady (for tests). */\n awaitReady?: typeof awaitReady;\n /** Destination stream for the renderer. */\n out?: NodeJS.WriteStream;\n /** Signal that aborts both the watcher and renderer. */\n signal?: AbortSignal;\n}\n\nexport type WatchResult = { code: 0 } | { code: 1; error?: string };\n\n/**\n * Headless implementation of the `xplane-utils watch` subcommand.\n * Returns the exit code instead of calling `process.exit` so it is unit-testable.\n */\nexport async function runWatchCommand(\n args: WatchCommandArgs,\n deps: WatchCommandDeps = {},\n): Promise<WatchResult> {\n const load = deps.loadKubeConfig ?? loadKubeConfig;\n const resolve = deps.resolveResource ?? resolveResource;\n const factory = deps.createXrWatcher ?? createXrWatcher;\n const render = deps.runRenderer ?? runRenderer;\n const wait = deps.awaitReady ?? awaitReady;\n\n const kcOpts: LoadKubeConfigOptions = {};\n if (args.kubeconfig !== undefined) kcOpts.kubeconfig = args.kubeconfig;\n if (args.context !== undefined) kcOpts.context = args.context;\n const kc = load(kcOpts);\n\n const parsed = parseTarget(args.target);\n const resolved = await resolve(kc, {\n resource: parsed.resource,\n ...(parsed.group !== undefined ? { group: parsed.group } : {}),\n ...(parsed.version !== undefined ? { version: parsed.version } : {}),\n });\n const ref: XrRef = {\n group: resolved.group,\n version: resolved.version,\n plural: resolved.plural,\n kind: resolved.kind,\n namespaced: resolved.namespaced,\n name: parsed.name,\n ...(args.namespace !== undefined ? { namespace: args.namespace } : {}),\n };\n if (ref.namespaced && !ref.namespace) {\n return { code: 1, error: `${ref.kind} is namespaced — pass --namespace/-n` };\n }\n\n const watcher: XrWatcher = factory({\n kubeConfig: kc,\n ref,\n ...(deps.signal !== undefined ? { signal: deps.signal } : {}),\n ...(args.disableEvents ? { disableEvents: true } : {}),\n });\n\n const mode: RendererMode | undefined =\n args.mode === 'tty' || args.mode === 'ci' ? args.mode : undefined;\n const renderOpts: Parameters<typeof runRenderer>[1] = { ref };\n if (mode) renderOpts.mode = mode;\n if (deps.out) renderOpts.out = deps.out;\n const heartbeatMs = parseDuration(args.heartbeat);\n if (heartbeatMs !== undefined) renderOpts.heartbeatMs = heartbeatMs;\n if (args.showEvents !== undefined) renderOpts.showEvents = args.showEvents;\n if (args.snapshotEveryHeartbeats !== undefined)\n renderOpts.snapshotEveryHeartbeats = args.snapshotEveryHeartbeats;\n const renderPromise = render(watcher, renderOpts);\n\n const timeoutMs = parseDuration(args.timeout);\n try {\n await wait(watcher, timeoutMs !== undefined ? { timeoutMs } : {});\n watcher.stop();\n await renderPromise;\n return { code: 0 };\n } catch (_err) {\n watcher.stop();\n await renderPromise.catch(() => undefined);\n // The renderer already surfaced the error inline — omit it here to avoid duplication.\n return { code: 1 };\n }\n}\n","#!/usr/bin/env node\nimport { createRequire } from 'node:module';\nimport { defineCommand, runMain } from 'citty';\nimport {\n type GetStatusCommandArgs,\n type GetStatusFormat,\n runGetStatusCommand,\n} from './cli/get-status.js';\nimport { runWatchCommand, type WatchCommandArgs } from './cli/watch.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json') as { version: string };\n\nconst watch = defineCommand({\n meta: {\n name: 'watch',\n description: 'Subscribe to a Crossplane XR and block until it becomes Ready',\n },\n args: {\n target: {\n type: 'positional',\n required: true,\n description: 'kubectl-style target: <resource[.group][.version]>/<name>',\n },\n namespace: {\n type: 'string',\n alias: 'n',\n description: 'Namespace (required for namespaced XRs)',\n },\n kubeconfig: { type: 'string', description: 'Path to kubeconfig' },\n context: { type: 'string', description: 'Override the active kubeconfig context' },\n timeout: {\n type: 'string',\n description: 'Maximum time to wait for Ready, e.g. \"30s\", \"5m\", \"1h\"',\n },\n mode: { type: 'string', description: 'Force renderer mode: \"tty\" or \"ci\" (default: auto)' },\n 'no-events': {\n type: 'boolean',\n description: 'Disable subscribing to Kubernetes Events for the XR',\n },\n heartbeat: {\n type: 'string',\n description: 'CI mode: heartbeat interval for liveness lines (default \"30s\", \"0\" to disable)',\n },\n 'show-events': {\n type: 'boolean',\n description: 'CI mode: include Kubernetes Events inline (off by default)',\n },\n 'snapshot-every': {\n type: 'string',\n description:\n 'CI mode: every N idle heartbeats, expand the liveness line into a snapshot of unready + blocked resources (default 10, 0 to disable)',\n },\n },\n async run({ args }) {\n const controller = new AbortController();\n process.on('SIGINT', () => controller.abort());\n process.on('SIGTERM', () => controller.abort());\n\n const cmdArgs: WatchCommandArgs = { target: String(args.target) };\n if (typeof args.namespace === 'string') cmdArgs.namespace = args.namespace;\n if (typeof args.kubeconfig === 'string') cmdArgs.kubeconfig = args.kubeconfig;\n if (typeof args.context === 'string') cmdArgs.context = args.context;\n if (typeof args.timeout === 'string') cmdArgs.timeout = args.timeout;\n if (typeof args.mode === 'string') cmdArgs.mode = args.mode;\n if (args['no-events']) cmdArgs.disableEvents = true;\n if (typeof args.heartbeat === 'string') cmdArgs.heartbeat = args.heartbeat;\n if (args['show-events']) cmdArgs.showEvents = true;\n if (typeof args['snapshot-every'] === 'string') {\n const n = Number.parseInt(args['snapshot-every'], 10);\n if (Number.isFinite(n) && n >= 0) cmdArgs.snapshotEveryHeartbeats = n;\n }\n\n const result = await runWatchCommand(cmdArgs, { signal: controller.signal });\n if (result.code !== 0) {\n if (result.error) process.stderr.write(`${result.error}\\n`);\n process.exit(result.code);\n }\n process.exit(0);\n },\n});\n\nconst getStatus = defineCommand({\n meta: {\n name: 'get-status',\n description: 'Fetch a Crossplane XR and print its .status',\n },\n args: {\n target: {\n type: 'positional',\n required: true,\n description: 'kubectl-style target: <resource[.group][.version]>/<name>',\n },\n namespace: {\n type: 'string',\n alias: 'n',\n description: 'Namespace (required for namespaced XRs)',\n },\n kubeconfig: { type: 'string', description: 'Path to kubeconfig' },\n context: { type: 'string', description: 'Override the active kubeconfig context' },\n format: {\n type: 'string',\n alias: 'o',\n description: 'Output format: \"dot\" (default) or \"json\"',\n },\n compact: {\n type: 'boolean',\n description: 'JSON mode: emit compact (single-line) JSON instead of pretty-printed',\n },\n 'include-xplane': {\n type: 'boolean',\n description: 'Include the framework-managed status.xplane subtree (excluded by default)',\n },\n 'include-conditions': {\n type: 'boolean',\n description: 'Include status.conditions (excluded by default)',\n },\n },\n async run({ args }) {\n const cmdArgs: GetStatusCommandArgs = { target: String(args.target) };\n if (typeof args.namespace === 'string') cmdArgs.namespace = args.namespace;\n if (typeof args.kubeconfig === 'string') cmdArgs.kubeconfig = args.kubeconfig;\n if (typeof args.context === 'string') cmdArgs.context = args.context;\n if (typeof args.format === 'string') {\n if (args.format !== 'dot' && args.format !== 'json') {\n process.stderr.write(`Invalid --format \"${args.format}\" (expected \"dot\" or \"json\")\\n`);\n process.exit(2);\n }\n cmdArgs.format = args.format as GetStatusFormat;\n }\n if (args.compact) cmdArgs.pretty = false;\n if (args['include-xplane']) cmdArgs.includeXplane = true;\n if (args['include-conditions']) cmdArgs.includeConditions = true;\n\n const result = await runGetStatusCommand(cmdArgs);\n if (result.code !== 0) {\n process.stderr.write(`${result.error}\\n`);\n process.exit(result.code);\n }\n process.exit(0);\n },\n});\n\nconst main = defineCommand({\n meta: {\n name: 'xplane-utils',\n version,\n description: 'Utilities for Crossplane compositions built with xplane',\n },\n subCommands: { watch, 'get-status': getStatus },\n});\n\nvoid runMain(main);\n"],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import { a as EmittedResource, c as XplaneStatus, d as XrSnapshot, i as BlockedResource, l as XrEvent, n as XrWatcher, o as KubernetesEvent, r as createXrWatcher, s as ResourceRef, t as CreateXrWatcherOptions, u as XrRef } from "./xr-watcher-EiWHVp3o.mjs";
2
+ import { KubeConfig, KubernetesObject } from "@kubernetes/client-node";
3
+
4
+ //#region src/client/kubeconfig.d.ts
5
+ interface LoadKubeConfigOptions {
6
+ /** Explicit kubeconfig file path. Falls back to `KUBECONFIG` / default discovery. */
7
+ kubeconfig?: string;
8
+ /** Override the active context. */
9
+ context?: string;
10
+ }
11
+ /**
12
+ * Load a `KubeConfig` from disk using `client-node`'s default rules (with
13
+ * optional explicit overrides). The returned config has its current context
14
+ * applied.
15
+ */
16
+ declare function loadKubeConfig(opts?: LoadKubeConfigOptions): KubeConfig;
17
+ //#endregion
18
+ //#region src/watcher/await-ready.d.ts
19
+ interface AwaitReadyOptions {
20
+ /** Maximum time in ms to wait for `Ready=True` before rejecting. */
21
+ timeoutMs?: number;
22
+ }
23
+ /**
24
+ * Resolve when the watcher observes its first `Ready=True` snapshot. Rejects on
25
+ * the watcher's first error, if the stream ends without becoming ready, or on
26
+ * `timeoutMs`.
27
+ *
28
+ * Uses the watcher's dedicated `ready` promise — it does NOT consume queue
29
+ * events, so it composes safely with a concurrent renderer iterating the
30
+ * watcher.
31
+ *
32
+ * The watcher itself is not stopped on resolution — call `watcher.stop()` from
33
+ * a `.finally()` if you want the underlying connections torn down.
34
+ */
35
+ declare function awaitReady(watcher: XrWatcher, opts?: AwaitReadyOptions): Promise<XrSnapshot>;
36
+ //#endregion
37
+ //#region src/watcher/readiness.d.ts
38
+ /**
39
+ * Build an `XrSnapshot` from a raw Kubernetes object. Parses the `Ready` condition,
40
+ * the optional `status.xplane` payload and `status.resourceRefs`.
41
+ */
42
+ declare function buildSnapshot(object: KubernetesObject): XrSnapshot;
43
+ //#endregion
44
+ //#region src/watcher/target.d.ts
45
+ /**
46
+ * Parse a kubectl-style resource target of the form:
47
+ * <resource>/<name>
48
+ * where `<resource>` is one of:
49
+ * - `kind` (e.g. `xprojects`, `XProject`)
50
+ * - `kind.group` (e.g. `xprojects.platform.example.com`)
51
+ * - `kind.version.group` (e.g. `xprojects.v1alpha1.platform.example.com`)
52
+ *
53
+ * The `kind` token is returned as-is; resolving it to an API group/version/plural
54
+ * is the discovery layer's responsibility (see `client/discovery.ts`).
55
+ */
56
+ interface ParsedTarget {
57
+ /** Resource hint: kind, plural, or short name as typed by the user. */
58
+ resource: string;
59
+ /** Optional API group hint extracted from `kind.group` or `kind.version.group`. */
60
+ group?: string;
61
+ /** Optional API version hint extracted from `kind.version.group`. */
62
+ version?: string;
63
+ /** Object name. */
64
+ name: string;
65
+ }
66
+ declare function parseTarget(input: string): ParsedTarget;
67
+ //#endregion
68
+ //#region src/watcher/tree.d.ts
69
+ /**
70
+ * A node in the construct-derived resource tree.
71
+ *
72
+ * Resource names emitted by compositions encode their construct path with `/` as
73
+ * the separator (e.g. `"CMS Database/Security Group"`). The tree mirrors that
74
+ * hierarchy so renderers can indent each branch.
75
+ */
76
+ interface TreeNode {
77
+ /** Last segment of the construct path (display label). */
78
+ label: string;
79
+ /** Full construct path (joined with `/`). */
80
+ path: string;
81
+ /** Kubernetes `metadata.name` of the leaf resource, when known. */
82
+ name?: string;
83
+ /** Kubernetes `metadata.namespace` of the leaf resource, when present. */
84
+ namespace?: string;
85
+ /** True when an emitted resource at this exact path is ready. */
86
+ ready: boolean;
87
+ /** True when this exact path appears in `blockedResources`. */
88
+ blocked: boolean;
89
+ /** Unresolved dependencies copied from the blocked entry. */
90
+ waitingFor?: string[];
91
+ /** API version of the leaf resource (if any). */
92
+ apiVersion?: string;
93
+ /** Kind of the leaf resource (if any). */
94
+ kind?: string;
95
+ /** Child nodes keyed by path segment. */
96
+ children: TreeNode[];
97
+ }
98
+ /** Aggregate counts derived from `status.xplane`. */
99
+ interface TreeStats {
100
+ /** Total emitted resources. */
101
+ total: number;
102
+ /** Emitted resources marked ready. */
103
+ ready: number;
104
+ /** Blocked resources. */
105
+ blocked: number;
106
+ }
107
+ interface ResourceTree {
108
+ /** Root-level nodes. */
109
+ roots: TreeNode[];
110
+ /** Aggregate counts. Zero values when no xplane status is available. */
111
+ stats: TreeStats;
112
+ /** Source used to build the tree. */
113
+ source: 'xplane' | 'resourceRefs' | 'empty';
114
+ }
115
+ /** Build a `ResourceTree` from a snapshot, preferring `status.xplane` when present. */
116
+ declare function buildTree(snapshot: XrSnapshot): ResourceTree;
117
+ //#endregion
118
+ export { type AwaitReadyOptions, type BlockedResource, type CreateXrWatcherOptions, type EmittedResource, type KubernetesEvent, type LoadKubeConfigOptions, type ParsedTarget, type ResourceRef, type ResourceTree, type TreeNode, type TreeStats, type XplaneStatus, type XrEvent, type XrRef, type XrSnapshot, type XrWatcher, awaitReady, buildSnapshot, buildTree, createXrWatcher, loadKubeConfig, parseTarget };
119
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/client/kubeconfig.ts","../src/watcher/await-ready.ts","../src/watcher/readiness.ts","../src/watcher/target.ts","../src/watcher/tree.ts"],"mappings":";;;;UAEiB,qBAAA;;EAEf,UAAA;EAFe;EAIf,OAAO;AAAA;;AAAA;AAQT;;;iBAAgB,cAAA,CAAe,IAAA,GAAM,qBAAA,GAA6B,UAAU;;;UCX3D,iBAAA;;EAEf,SAAS;AAAA;;;ADCF;AAQT;;;;;;;;AAA4E;iBCMtD,UAAA,CACpB,OAAA,EAAS,SAAA,EACT,IAAA,GAAM,iBAAA,GACL,OAAA,CAAQ,UAAA;;;;;ADrBX;;iBEiBgB,aAAA,CAAc,MAAA,EAAQ,gBAAA,GAAmB,UAAU;;;;;;;AFjBnE;;;;AAIS;AAQT;;UGHiB,YAAA;EHG2D;EGD1E,QAAA;EHC6B;EGC7B,KAAA;EHD0E;EGG1E,OAAA;;EAEA,IAAA;AAAA;AAAA,iBAGc,WAAA,CAAY,KAAA,WAAgB,YAAY;;;;;;AHpBxD;;;;UIOiB,QAAA;EJKD;EIHd,KAAA;;EAEA,IAAA;EJCmC;EICnC,IAAA;EJDgE;EIGhE,SAAA;EJH0E;EIK1E,KAAA;;EAEA,OAAA;EHlBe;EGoBf,UAAA;;EAEA,UAAA;EHpBS;EGsBT,IAAA;EHP8B;EGS9B,QAAA,EAAU,QAAQ;AAAA;;UAIH,SAAA;EHVd;EGYD,KAAA;EHZQ;EGcR,KAAA;EHhBA;EGkBA,OAAA;AAAA;AAAA,UAGe,YAAA;EHnBN;EGqBT,KAAA,EAAO,QAAA;EHrBY;EGuBnB,KAAA,EAAO,SAAS;;EAEhB,MAAA;AAAA;;iBAIc,SAAA,CAAU,QAAA,EAAU,UAAA,GAAa,YAAY"}
package/dist/index.mjs ADDED
@@ -0,0 +1,3 @@
1
+ import { a as awaitReady, i as buildSnapshot, o as loadKubeConfig, r as parseTarget, t as createXrWatcher } from "./xr-watcher-1etyvp-6.mjs";
2
+ import { t as buildTree } from "./tree-DaHkojq8.mjs";
3
+ export { awaitReady, buildSnapshot, buildTree, createXrWatcher, loadKubeConfig, parseTarget };