import { z } from "zod" import { parse, type TemplateVariable } from "./parser" import { renderDir } from "./render" interface Stringable { toString: () => string } interface Issue { message: string, path: Stringable } export class SchemaMismatchError extends Error { constructor(issue: Issue) { super(`Shema doesnt match used template variables: ${issue.path}: ${issue.message}` ) } } function validateSchemaMatchesTemplates(userSchema: z.ZodType, variables: TemplateVariable[]) { if (userSchema._zod.def.type !== "object") { throw new SchemaMismatchError({ path: "", message: "Schema must be a z.object()" }) } // Collect all expected paths: leaf variables + intermediate segments must be objects const expected = new Map() for (const v of variables) { const segments = v.path.split(".") for (let i = 0; i < segments.length - 1; i++) { const intermediate = segments.slice(0, i + 1).join(".") if (!expected.has(intermediate)) { expected.set(intermediate, "object") } } expected.set(v.path, v.type) } for (const [path, expectedType] of expected) { const segments = path.split(".") let current = userSchema let currentPath = "" for (const seg of segments) { currentPath = currentPath ? `${currentPath}.${seg}` : seg if (current._zod.def.type !== "object") { throw new SchemaMismatchError({ path: currentPath, message: `expected z.object() but schema has z.${current._zod.def.type as string}()` }) } const shape = (current as z.ZodObject).shape if (!(seg in shape)) { throw new SchemaMismatchError({ path: currentPath, message: `missing in schema` }) } current = shape[seg] } const actual = current._zod.def.type as string if (actual !== expectedType) { throw new SchemaMismatchError({ path, message: `expected z.${expectedType}() but schema has z.${actual}()` }) } } } export const initRenderer = (dirPath: string) => { const variables = parse(dirPath) const createRenderer = (userSchema: S) => { validateSchemaMatchesTemplates(userSchema, variables) return (targetPath: string, context: z.infer) => { userSchema.parse(context) renderDir(dirPath, targetPath, context as Record) } } return createRenderer }