@kimchi-dev/kimchictl 0.0.8 → 0.0.9
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/api/workspaces.d.ts +2 -0
- package/dist/api/workspaces.d.ts.map +1 -1
- package/dist/api/workspaces.js +1 -0
- package/dist/api/workspaces.js.map +1 -1
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +2 -0
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/templates.d.ts +15 -0
- package/dist/commands/templates.d.ts.map +1 -0
- package/dist/commands/templates.js +174 -0
- package/dist/commands/templates.js.map +1 -0
- package/dist/commands/workspace.d.ts.map +1 -1
- package/dist/commands/workspace.js +8 -174
- package/dist/commands/workspace.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/src/api/workspaces.ts +3 -0
- package/src/commands/registry.ts +2 -0
- package/src/commands/templates.test.ts +126 -0
- package/src/commands/templates.ts +204 -0
- package/src/commands/workspace.test.ts +4 -97
- package/src/commands/workspace.ts +8 -194
- package/src/version.ts +1 -1
|
@@ -2,13 +2,6 @@ import { readFileSync } from "node:fs"
|
|
|
2
2
|
import { resolve } from "node:path"
|
|
3
3
|
import { resolveWorkspace } from "../api/resolver.js"
|
|
4
4
|
import { formatAge, formatBytes, formatMillicores } from "../api/resources.js"
|
|
5
|
-
import {
|
|
6
|
-
AmbiguousTemplateError,
|
|
7
|
-
getWorkspaceTemplate,
|
|
8
|
-
listWorkspaceTemplates,
|
|
9
|
-
TemplateNotFoundError,
|
|
10
|
-
type WorkspaceTemplate,
|
|
11
|
-
} from "../api/templates.js"
|
|
12
5
|
import { waitForWorkspaceActive } from "../api/wait.js"
|
|
13
6
|
import {
|
|
14
7
|
type CreateWorkspaceSpec,
|
|
@@ -43,11 +36,10 @@ const GROUP_USAGE = `Usage:
|
|
|
43
36
|
kimchictl workspace <verb> [options]
|
|
44
37
|
|
|
45
38
|
Verbs:
|
|
46
|
-
create
|
|
47
|
-
list
|
|
48
|
-
get
|
|
49
|
-
delete
|
|
50
|
-
templates Browse workspace templates (list | get)
|
|
39
|
+
create Create a workspace (server generates the alias)
|
|
40
|
+
list List workspaces (alias: ls)
|
|
41
|
+
get Show workspace details
|
|
42
|
+
delete Delete a workspace (alias: rm)
|
|
51
43
|
|
|
52
44
|
Create options:
|
|
53
45
|
--desc <text> Description
|
|
@@ -81,9 +73,6 @@ export async function runWorkspace(args: string[], deps: WorkspaceDeps = {}): Pr
|
|
|
81
73
|
case "delete":
|
|
82
74
|
case "rm":
|
|
83
75
|
return runDelete(rest, deps)
|
|
84
|
-
case "templates":
|
|
85
|
-
case "template":
|
|
86
|
-
return runTemplates(rest, deps)
|
|
87
76
|
case "help":
|
|
88
77
|
case "--help":
|
|
89
78
|
case "-h":
|
|
@@ -274,13 +263,14 @@ async function runList(args: string[], deps: WorkspaceDeps): Promise<number> {
|
|
|
274
263
|
w.alias,
|
|
275
264
|
w.status,
|
|
276
265
|
w.cluster,
|
|
266
|
+
w.templateName || "-",
|
|
277
267
|
formatAge(w.createdAt, now),
|
|
278
268
|
formatMillicores(w.cpuMillicores),
|
|
279
269
|
formatBytes(w.ramBytes),
|
|
280
270
|
formatBytes(w.pvcSizeBytes),
|
|
281
271
|
w.uri ?? "",
|
|
282
272
|
])
|
|
283
|
-
console.log(formatTable(["NAME", "STATUS", "CLUSTER", "AGE", "CPU", "RAM", "PVC", "URI"], rows))
|
|
273
|
+
console.log(formatTable(["NAME", "STATUS", "CLUSTER", "TEMPLATE", "AGE", "CPU", "RAM", "PVC", "URI"], rows))
|
|
284
274
|
return 0
|
|
285
275
|
}
|
|
286
276
|
|
|
@@ -349,184 +339,6 @@ async function runDelete(args: string[], deps: WorkspaceDeps): Promise<number> {
|
|
|
349
339
|
return 0
|
|
350
340
|
}
|
|
351
341
|
|
|
352
|
-
// ---------------------------------------------------------------- templates
|
|
353
|
-
|
|
354
|
-
async function runTemplates(args: string[], deps: WorkspaceDeps): Promise<number> {
|
|
355
|
-
const [verb, ...rest] = args
|
|
356
|
-
switch (verb) {
|
|
357
|
-
case "list":
|
|
358
|
-
case "ls":
|
|
359
|
-
return runTemplatesList(rest, deps)
|
|
360
|
-
case "get":
|
|
361
|
-
return runTemplatesGet(rest, deps)
|
|
362
|
-
case "help":
|
|
363
|
-
case "--help":
|
|
364
|
-
case "-h":
|
|
365
|
-
console.log(TEMPLATES_USAGE)
|
|
366
|
-
return 0
|
|
367
|
-
case undefined:
|
|
368
|
-
console.error(TEMPLATES_USAGE)
|
|
369
|
-
return 2
|
|
370
|
-
default:
|
|
371
|
-
console.error(`kimchictl workspace templates: unknown verb "${verb}"`)
|
|
372
|
-
console.error(TEMPLATES_USAGE)
|
|
373
|
-
return 2
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const TEMPLATES_USAGE = `Usage:
|
|
378
|
-
kimchictl workspace templates <verb> [options]
|
|
379
|
-
|
|
380
|
-
Verbs:
|
|
381
|
-
list List workspace templates (alias: ls)
|
|
382
|
-
get Show template details
|
|
383
|
-
|
|
384
|
-
Options:
|
|
385
|
-
--help Show this help`
|
|
386
|
-
|
|
387
|
-
async function runTemplatesList(args: string[], deps: WorkspaceDeps): Promise<number> {
|
|
388
|
-
const { values, positionals } = parseFlags("workspace templates list", args, {
|
|
389
|
-
output: { type: "string", short: "o", default: "table" },
|
|
390
|
-
})
|
|
391
|
-
if (positionals.length > 0) {
|
|
392
|
-
throw new UsageError("kimchictl workspace templates list: unexpected positional argument")
|
|
393
|
-
}
|
|
394
|
-
if (values.output !== "json" && values.output !== "table") {
|
|
395
|
-
throw new UsageError(
|
|
396
|
-
`kimchictl workspace templates list: unsupported --output "${String(values.output)}" (expected table|json)`,
|
|
397
|
-
)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const { key } = requireApiKey(deps.env)
|
|
401
|
-
const templates = await listWorkspaceTemplates(key, { fetch: deps.fetch })
|
|
402
|
-
|
|
403
|
-
if (values.output === "json") {
|
|
404
|
-
console.log(JSON.stringify(templates.map(serializeTemplate), null, 2))
|
|
405
|
-
return 0
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const now = deps.now?.() ?? new Date()
|
|
409
|
-
const rows = templates.map((t) => [
|
|
410
|
-
t.name || t.id.slice(0, 12),
|
|
411
|
-
t.description.length > 40 ? `${t.description.slice(0, 37)}…` : t.description || "-",
|
|
412
|
-
formatMillicores(t.spec?.resources?.cpu ? parseQuantity(t.spec.resources.cpu) : undefined),
|
|
413
|
-
formatBytes(t.spec?.resources?.memory ? parseByteQuantity(t.spec.resources.memory) : undefined),
|
|
414
|
-
formatBytes(t.spec?.resources?.pvcSize ? parseByteQuantity(t.spec.resources.pvcSize) : undefined),
|
|
415
|
-
formatAge(t.createTime, now),
|
|
416
|
-
])
|
|
417
|
-
console.log(formatTable(["NAME", "DESCRIPTION", "CPU", "MEMORY", "STORAGE", "AGE"], rows))
|
|
418
|
-
return 0
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
async function runTemplatesGet(args: string[], deps: WorkspaceDeps): Promise<number> {
|
|
422
|
-
const { values, positionals } = parseFlags("workspace templates get", args, {
|
|
423
|
-
output: { type: "string", short: "o", default: "text" },
|
|
424
|
-
})
|
|
425
|
-
if (positionals.length !== 1) {
|
|
426
|
-
throw new UsageError("kimchictl workspace templates get: expected exactly one template name or ID")
|
|
427
|
-
}
|
|
428
|
-
if (values.output !== "json" && values.output !== "text") {
|
|
429
|
-
throw new UsageError(
|
|
430
|
-
`kimchictl workspace templates get: unsupported --output "${String(values.output)}" (expected text|json)`,
|
|
431
|
-
)
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const { key } = requireApiKey(deps.env)
|
|
435
|
-
const ref = positionals[0] ?? ""
|
|
436
|
-
|
|
437
|
-
// Templates are addressed by ID in the API; when the ref is a name,
|
|
438
|
-
// list-and-match (the API has no by-name lookup).
|
|
439
|
-
let template: WorkspaceTemplate | undefined
|
|
440
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(ref)) {
|
|
441
|
-
template = await getWorkspaceTemplate(key, ref, { fetch: deps.fetch })
|
|
442
|
-
} else {
|
|
443
|
-
const all = await listWorkspaceTemplates(key, { fetch: deps.fetch })
|
|
444
|
-
const matches = all.filter((t) => t.name === ref || t.name.startsWith(ref))
|
|
445
|
-
if (matches.length === 0) throw new TemplateNotFoundError(ref)
|
|
446
|
-
if (matches.length > 1)
|
|
447
|
-
throw new AmbiguousTemplateError(
|
|
448
|
-
ref,
|
|
449
|
-
matches.map((t) => t.name),
|
|
450
|
-
)
|
|
451
|
-
template = matches[0]
|
|
452
|
-
}
|
|
453
|
-
if (!template) throw new TemplateNotFoundError(ref)
|
|
454
|
-
|
|
455
|
-
if (values.output === "json") {
|
|
456
|
-
console.log(JSON.stringify(serializeTemplate(template), null, 2))
|
|
457
|
-
return 0
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
for (const line of renderTemplateCard(template, deps.color ?? false, deps.now?.() ?? new Date())) {
|
|
461
|
-
console.log(line)
|
|
462
|
-
}
|
|
463
|
-
return 0
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function renderTemplateCard(template: WorkspaceTemplate, color: boolean, now: Date): string[] {
|
|
467
|
-
const lines: string[] = [`${template.name || template.id.slice(0, 12)} ${dim("template", color)}`]
|
|
468
|
-
if (template.description) lines.push(dim(` ${template.description}`, color))
|
|
469
|
-
|
|
470
|
-
const parts: string[] = []
|
|
471
|
-
const res = template.spec?.resources
|
|
472
|
-
if (res) {
|
|
473
|
-
if (res.cpu) parts.push(formatMillicores(parseQuantity(res.cpu)))
|
|
474
|
-
if (res.memory) parts.push(formatBytes(parseByteQuantity(res.memory)))
|
|
475
|
-
}
|
|
476
|
-
if (parts.length > 0) {
|
|
477
|
-
lines.push(dim(` ${parts.join(" · ")}`, color))
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const deps = template.spec?.dependencies
|
|
481
|
-
if (deps && deps.length > 0) {
|
|
482
|
-
lines.push(` deps ${deps.join(", ")}`)
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const eg = template.spec?.egressPolicy
|
|
486
|
-
if (eg) {
|
|
487
|
-
const egParts: string[] = []
|
|
488
|
-
if (eg.denyByDefault === false) egParts.push("default-allow")
|
|
489
|
-
if (eg.allowed && eg.allowed.length > 0) egParts.push(`allow: ${eg.allowed.join(", ")}`)
|
|
490
|
-
if (eg.denied && eg.denied.length > 0) egParts.push(`deny: ${eg.denied.join(", ")}`)
|
|
491
|
-
if (egParts.length > 0) lines.push(dim(` egress ${egParts.join(" · ")}`, color))
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
lines.push(dim(` updated ${formatAge(template.updateTime, now)} ago`, color))
|
|
495
|
-
return lines
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function serializeTemplate(t: WorkspaceTemplate): Record<string, unknown> {
|
|
499
|
-
return {
|
|
500
|
-
id: t.id,
|
|
501
|
-
name: t.name,
|
|
502
|
-
...(t.description ? { description: t.description } : {}),
|
|
503
|
-
spec: t.spec,
|
|
504
|
-
...(t.createdBy ? { createdBy: t.createdBy } : {}),
|
|
505
|
-
...(t.updateTime.getTime() > 0 ? { updateTime: t.updateTime.toISOString() } : {}),
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/** Parse a k8s CPU quantity string ("500m", "2") to millicores. */
|
|
510
|
-
function parseQuantity(q: string): number | undefined {
|
|
511
|
-
const value = Number.parseFloat(q.replace(/m$/, ""))
|
|
512
|
-
if (Number.isNaN(value)) return undefined
|
|
513
|
-
return q.endsWith("m") ? value : value * 1000
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/** Parse a k8s memory/storage quantity string ("1Gi", "512Mi") to bytes. */
|
|
517
|
-
function parseByteQuantity(q: string): number | undefined {
|
|
518
|
-
const units: Record<string, number> = { Ki: 1024, Mi: 1024 ** 2, Gi: 1024 ** 3, Ti: 1024 ** 4 }
|
|
519
|
-
for (const [suffix, mult] of Object.entries(units)) {
|
|
520
|
-
if (q.endsWith(suffix)) {
|
|
521
|
-
const value = Number.parseFloat(q.slice(0, -suffix.length))
|
|
522
|
-
if (!Number.isNaN(value)) return value * mult
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
const value = Number.parseFloat(q)
|
|
526
|
-
if (!Number.isNaN(value)) return value
|
|
527
|
-
return undefined
|
|
528
|
-
}
|
|
529
|
-
|
|
530
342
|
// ---------------------------------------------------------------- shared
|
|
531
343
|
|
|
532
344
|
/** Tinted status dot: green=active, yellow=transitional/suspended, dim=terminated. */
|
|
@@ -560,6 +372,7 @@ function renderWorkspaceCard(
|
|
|
560
372
|
const meta = [
|
|
561
373
|
workspace.cluster ? `cluster ${workspace.cluster}` : "",
|
|
562
374
|
workspace.clientType,
|
|
375
|
+
workspace.templateName ? `from ${workspace.templateName}` : "",
|
|
563
376
|
`created ${formatAge(workspace.createdAt, now)} ago`,
|
|
564
377
|
].filter(Boolean)
|
|
565
378
|
if (meta.length > 0) lines.push(dim(` ${meta.join(" · ")}`, color))
|
|
@@ -593,6 +406,7 @@ function serializeWorkspace(w: Workspace): Record<string, unknown> {
|
|
|
593
406
|
status: w.status,
|
|
594
407
|
...(w.cluster ? { cluster: w.cluster } : {}),
|
|
595
408
|
...(w.clientType ? { clientType: w.clientType } : {}),
|
|
409
|
+
...(w.templateName ? { templateName: w.templateName } : {}),
|
|
596
410
|
...(w.uri ? { uri: w.uri } : {}),
|
|
597
411
|
createdAt: w.createdAt.getTime() > 0 ? w.createdAt.toISOString() : undefined,
|
|
598
412
|
resources: {
|
package/src/version.ts
CHANGED