Files
kuber/command/main.ts
T
2026-09-27 10:40:50 +00:00

102 lines
3.6 KiB
TypeScript

import { defineCommand } from "citty";
import { wrapCommandErrors } from "../lib/error";
function lazyCommand<T extends object>(
load: () => Promise<T>,
): () => Promise<T> {
let pending: Promise<T> | undefined;
return () =>
(pending ??= load()
.then((command) => {
// Keep the exported command objects pristine: tests and callers may
// import them directly while the CLI needs wrapped handlers.
const cliCommand = cloneCommand(command);
wrapCommandErrors(cliCommand);
return cliCommand;
})
.catch((error) => {
pending = undefined;
throw error;
}));
}
function cloneCommand<T extends object>(command: T): T {
const clone = { ...command } as T & { subCommands?: unknown };
const subCommands = clone.subCommands;
if (subCommands && typeof subCommands === "object") {
clone.subCommands = Object.fromEntries(
Object.entries(subCommands).map(([name, child]) => [
name,
child && typeof child === "object" ? cloneCommand(child) : child,
]),
);
}
return clone;
}
export const main = defineCommand({
meta: {
name: "kuber",
version: "2.5.1",
description: "Docker Compose -> K8s translation layer",
},
args: {
config: {
type: "string",
description: "Path to a kuber configuration file",
},
},
subCommands: {
audit: lazyCommand(() => import("./audit").then((m) => m.audit)),
ci: lazyCommand(() => import("./ci").then((m) => m.ci)),
db: lazyCommand(() => import("./db").then((m) => m.db)),
down: lazyCommand(() => import("./down").then((m) => m.down)),
export: lazyCommand(() => import("./export").then((m) => m.exportCommand)),
exec: lazyCommand(() => import("./exec").then((m) => m.exec)),
logs: lazyCommand(() => import("./logs").then((m) => m.logs)),
maintenance: lazyCommand(() =>
import("./maintenance").then((m) => m.maintenance),
),
login: lazyCommand(() => import("./auth").then((m) => m.login)),
logout: lazyCommand(() => import("./auth").then((m) => m.logout)),
operations: lazyCommand(() =>
import("./operations").then((m) => m.operations),
),
ps: lazyCommand(() => import("./ps").then((m) => m.ps)),
restart: lazyCommand(() => import("./restart").then((m) => m.restart)),
rollback: lazyCommand(() => import("./rollback").then((m) => m.rollback)),
s3: lazyCommand(() => import("./s3").then((m) => m.s3)),
start: lazyCommand(() => import("./start").then((m) => m.start)),
stop: lazyCommand(() => import("./stop").then((m) => m.stop)),
up: lazyCommand(() => import("./up").then((m) => m.up)),
users: lazyCommand(() => import("./users").then((m) => m.users)),
trust: lazyCommand(() => import("./trust").then((m) => m.trust)),
whoami: lazyCommand(() => import("./auth").then((m) => m.whoami)),
},
});
type Completion = Awaited<
ReturnType<typeof import("@bomb.sh/tab/citty")["default"]>
>;
let completion: Promise<Completion> | undefined;
export function initializeCompletion() {
completion ??= (async () => {
// tab inspects command objects directly, unlike citty's lazy resolver.
const commands = main.subCommands;
if (commands && typeof commands !== "function") {
main.subCommands = Object.fromEntries(
await Promise.all(
Object.entries(commands).map(async ([name, command]) => [
name,
typeof command === "function" ? await command() : await command,
]),
),
);
}
const { default: tab } = await import("@bomb.sh/tab/citty");
return tab(main);
})();
return completion;
}