/* Generate JSON Schema artifacts under __generated__/ and write index.ts re-exports. */ import { mkdirSync, writeFileSync } from "node:fs"; import { dirname, join, relative } from "node:path"; import { createGenerator } from "ts-json-schema-generator"; import { cleanStaleGenerated } from "./cleanup.ts"; import { discoverSchemaRoots, type SchemaRoot } from "./discover-schema-roots.ts"; import { GENERATED_DIR, schemaExportName, schemaJsonBasename, schemaJsonImportVar } from "./names.ts"; export interface RunSchemagenOptions { /** Project root (default: `process.cwd()`). */ projectRoot?: string; /** Source tree directory relative to project root (default: `src`). Unused; kept for API compat. */ srcDir?: string; /** Path to tsconfig relative to project root (default: `tsconfig.json`). */ tsconfig?: string; } export interface RunSchemagenResult { schemas: number; } function resolveTsconfig(projectRoot: string, tsconfig: string): string { const path = join(projectRoot, tsconfig); try { return path; } catch { throw new Error(`tsconfig not found: ${path}`); } } function generatedDirForRoot(projectRoot: string, root: SchemaRoot): string { return join(dirname(join(projectRoot, root.path)), GENERATED_DIR); } function generateJson(projectRoot: string, tsconfigPath: string, root: SchemaRoot): Record { const typeFile = join(projectRoot, root.sourcePath); const generator = createGenerator({ path: typeFile, type: root.typeName, tsconfig: tsconfigPath, topRef: false, skipTypeCheck: false, jsDoc: "extended", additionalProperties: false, }); const schema = generator.createSchema(root.typeName) as Record; schema.additionalProperties = false; return schema; } function writeGeneratedIndex(generatedDir: string, roots: SchemaRoot[]): void { const sorted = [...roots].sort((left, right) => left.typeName.localeCompare(right.typeName)); const lines = ["// Auto-generated by argsbarg schemagen — do not edit by hand.", ""]; for (const root of sorted) { lines.push(`import ${schemaJsonImportVar(root.typeName)} from "./${schemaJsonBasename(root.typeName)}";`); } if (sorted.length > 0) { lines.push(""); } for (const root of sorted) { lines.push( `export const ${schemaExportName(root.typeName)} = ${schemaJsonImportVar(root.typeName)} as Record;`, ); lines.push(""); } writeFileSync(join(generatedDir, "index.ts"), `${lines.join("\n")}`); } /** Generate colocated `__generated__` JSON schemas and `index.ts` barrels. */ export function runSchemagen(options: RunSchemagenOptions = {}): RunSchemagenResult { const projectRoot = options.projectRoot ?? process.cwd(); const srcDir = options.srcDir ?? "src"; const tsconfigPath = resolveTsconfig(projectRoot, options.tsconfig ?? "tsconfig.json"); const roots = discoverSchemaRoots(projectRoot, srcDir); const byGeneratedDir = new Map(); for (const root of roots) { const schema = generateJson(projectRoot, tsconfigPath, root); const generatedDir = generatedDirForRoot(projectRoot, root); mkdirSync(generatedDir, { recursive: true }); const jsonPath = join(generatedDir, schemaJsonBasename(root.typeName)); writeFileSync(jsonPath, `${JSON.stringify(schema, null, 2)}\n`); console.log(`wrote ${relative(projectRoot, jsonPath)} (${root.typeName})`); const list = byGeneratedDir.get(generatedDir) ?? []; list.push(root); byGeneratedDir.set(generatedDir, list); } for (const [generatedDir, dirRoots] of byGeneratedDir) { writeGeneratedIndex(generatedDir, dirRoots); console.log(`wrote ${relative(projectRoot, join(generatedDir, "index.ts"))}`); } cleanStaleGenerated(projectRoot, srcDir, byGeneratedDir); return { schemas: roots.length }; }