/** * Docker lexicon generation pipeline. * * Orchestrates both Compose and Dockerfile type generation, * then writes merged artifacts to src/generated/. */ import { writeFileSync, mkdirSync } from "fs"; import { join } from "path"; import { generateComposePipeline } from "./generate-compose"; import { generateDockerfilePipeline } from "./generate-dockerfile"; import { entityTypeToTsName } from "./naming"; import type { ComposeParseResult } from "../spec/parse-compose"; import type { DockerfileParseResult } from "../spec/parse-engine"; export interface DockerGenerateResult { resources: number; properties: number; enums: number; warnings: Array<{ file: string; error: string }>; lexiconJSON: string; typesDTS: string; indexTS: string; } /** * Run the full Docker generation pipeline. */ export async function generate(opts: { verbose?: boolean; force?: boolean } = {}): Promise { const [composeResult, dockerfileResult] = await Promise.all([ generateComposePipeline(opts), generateDockerfilePipeline(opts), ]); const allCompose = composeResult.results; const dockerfile = dockerfileResult.result; const lexiconJSON = generateLexiconJSON(allCompose, dockerfile); const typesDTS = generateTypesDTS(allCompose, dockerfile); const indexTS = generateIndexTS(allCompose, dockerfile); const propertyCount = allCompose.reduce((n, c) => n + c.properties.length, 0) + dockerfile.instructions.length; return { resources: allCompose.length + 1, // +1 for Dockerfile properties: propertyCount, enums: 0, warnings: [], lexiconJSON, typesDTS, indexTS, }; } function generateLexiconJSON(compose: ComposeParseResult[], dockerfile: DockerfileParseResult): string { const entries: Record = {}; for (const c of compose) { const tsName = entityTypeToTsName(c.typeName); entries[tsName] = { resourceType: c.typeName, kind: "resource", description: c.description ?? "", properties: Object.fromEntries( c.properties.map((p) => [p.name, { type: p.type, description: p.description ?? "" }]), ), }; } const dfName = entityTypeToTsName(dockerfile.typeName); entries[dfName] = { resourceType: dockerfile.typeName, kind: "resource", description: dockerfile.description ?? "", properties: Object.fromEntries( dockerfile.instructions.map((i) => [i.name, { description: i.description ?? "" }]), ), }; return JSON.stringify(entries, null, 2); } function generateTypesDTS(compose: ComposeParseResult[], dockerfile: DockerfileParseResult): string { const lines: string[] = [ "// Code generated by chant generate. DO NOT EDIT.", 'import type { Declarable } from "@intentius/chant/declarable";', "", ]; for (const c of compose) { const tsName = entityTypeToTsName(c.typeName); lines.push(`export interface ${tsName}Props {`); for (const p of c.properties) { const opt = p.required ? "" : "?"; lines.push(` ${p.name}${opt}: ${p.type};`); } lines.push(`}`); lines.push(`export declare const ${tsName}: new (props?: ${tsName}Props) => Declarable;`); lines.push(""); } const dfName = entityTypeToTsName(dockerfile.typeName); lines.push(`export interface DockerfileStage {`); lines.push(` from: string;`); lines.push(` as?: string;`); for (const i of dockerfile.instructions.filter((i) => i.name !== "from")) { lines.push(` ${i.name}?: ${i.multi ? "string[]" : "string"};`); } lines.push(`}`); lines.push(`export interface DockerfileProps {`); lines.push(` name?: string;`); lines.push(` from?: string;`); lines.push(` stages?: DockerfileStage[];`); for (const i of dockerfile.instructions.filter((i) => i.name !== "from")) { lines.push(` ${i.name}?: ${i.multi ? "string[]" : "string"};`); } lines.push(`}`); lines.push(`export declare const ${dfName}: new (props?: DockerfileProps) => Declarable;`); lines.push(""); return lines.join("\n"); } function generateIndexTS(compose: ComposeParseResult[], dockerfile: DockerfileParseResult): string { const lines: string[] = [ "// Code generated by chant generate. DO NOT EDIT.", 'import { createResource } from "./runtime";', "", ]; for (const c of compose) { const tsName = entityTypeToTsName(c.typeName); lines.push(`export const ${tsName} = createResource("${c.typeName}", "docker", {});`); } lines.push(""); const dfName = entityTypeToTsName(dockerfile.typeName); lines.push(`export const ${dfName} = createResource("${dockerfile.typeName}", "docker", {});`); lines.push(""); return lines.join("\n"); } /** * Write generated artifacts to disk under baseDir/src/generated/. */ export function writeGeneratedFiles(result: DockerGenerateResult, baseDir: string): void { const generatedDir = join(baseDir, "src", "generated"); mkdirSync(generatedDir, { recursive: true }); writeFileSync(join(generatedDir, "lexicon-docker.json"), result.lexiconJSON); writeFileSync(join(generatedDir, "index.d.ts"), result.typesDTS); writeFileSync(join(generatedDir, "index.ts"), result.indexTS); writeFileSync( join(generatedDir, "runtime.ts"), `/**\n * Runtime factory constructors — re-exported from core.\n */\nexport { createResource, createProperty } from "@intentius/chant/runtime";\n`, ); }