Files
kuber/command/add.ts
T
2026-10-07 05:10:59 +00:00

111 lines
3.1 KiB
TypeScript

import { defineCommand } from "citty";
import { ctx } from "../lib/context";
import { addAppToCompose, type AppOptions } from "../lib/scaffold";
import { readCompose, resolveComposeFile } from "../lib/yaml";
import {
resolveScaffoldInput,
terminalPrompt,
type AppInput,
type ScaffoldPrompt,
} from "../lib/scaffold-prompts";
export { terminalPrompt, type AppInput, type ScaffoldPrompt };
export async function resolveAppInput(
root: string,
input: AppInput,
prompt: ScaffoldPrompt = terminalPrompt,
composeFile?: string,
): Promise<AppOptions> {
const file = composeFile ?? (await resolveComposeFile(root));
const existingServices = file
? Object.keys((await readCompose(file)).services ?? {})
: [];
return (await resolveScaffoldInput(root, input, prompt, { existingServices }))
.app;
}
export const appArgs = {
name: { type: "string" as const, description: "Service name" },
path: {
type: "string" as const,
description: "App directory relative to the project",
},
source: {
type: "string" as const,
description: "image, auto (Buildpacks), or template",
},
image: {
type: "string" as const,
description: "Image reference when source=image",
},
cnb: {
type: "string" as const,
description: "Optional CNB URI when source=auto",
},
template: {
type: "string" as const,
description: "Dockerfile template ID when source=template",
},
postgres: {
type: "string" as const,
description: "Postgres claim name or user/database",
},
s3: { type: "string" as const, description: "S3 claim name or key/bucket" },
cpu: { type: "string" as const, description: "CPU limit in cores" },
memory: { type: "string" as const, description: "Memory limit, e.g. 512m" },
replicas: { type: "string" as const, description: "Replica count" },
"non-interactive": {
type: "boolean" as const,
description: "Use defaults and require explicit choices when ambiguous",
},
};
export function appInputFromArgs(args: Record<string, unknown>): AppInput {
return {
...Object.fromEntries(
[
"name",
"path",
"source",
"image",
"template",
"cnb",
"postgres",
"s3",
"cpu",
"memory",
]
.filter((key) => args[key] !== undefined)
.map((key) => [key, String(args[key])]),
),
...(args.replicas !== undefined ? { replicas: Number(args.replicas) } : {}),
nonInteractive: Boolean(args["non-interactive"]),
};
}
export const add = defineCommand({
meta: { name: "add", description: "Add a resource to this project" },
subCommands: {
app: defineCommand({
meta: {
name: "app",
description: "Add a service without changing existing services",
},
args: appArgs,
async run({ args }) {
const { cwd, path } = ctx();
const file = await path();
const app = await resolveAppInput(
cwd,
appInputFromArgs(args),
terminalPrompt,
file,
);
const composePath = await addAppToCompose(cwd, app, file);
console.log(`Added ${app.name} to ${composePath}`);
},
}),
},
});