import { type AnyResource, AnyResourceSchema, ResourceMap, } from "@proscenium/playbill"; import YAML, { YAMLParseError } from "yaml"; import { ZodError } from "zod"; import { ResourceFileError } from "./errors"; import { extractFrontmatter, extractMarkdown } from "./markdown"; type FileFormat = "yaml" | "markdown"; function determineFileFormat(filePath: string): FileFormat { return filePath.split(".").pop() === "md" ? "markdown" : "yaml"; } function loadYamlResourceFile(filePath: string, text: string): AnyResource[] { const parsedDocs = YAML.parseAllDocuments(text); if (parsedDocs.some((doc) => doc.toJS() === null)) { throw new ResourceFileError("Encountered NULL resource", filePath); } const errors = parsedDocs.flatMap((doc) => doc.errors); if (errors.length > 0) { throw new ResourceFileError( "Error parsing YAML resource", filePath, errors.map((e) => e.message).join(", "), ); } const collection: AnyResource[] = []; for (const doc of parsedDocs) { const raw = doc.toJS(); const parsed = AnyResourceSchema.parse(raw); const type = parsed.$define; const schemaToUse = ResourceMap[type]; const validated = schemaToUse.parse(parsed); collection.push(validated); } return collection; } function loadMarkdownResourceFile( filePath: string, text: string, ): AnyResource[] { try { const frontmatter = extractFrontmatter(text); const markdown = extractMarkdown(text); const together = { ...frontmatter, description: markdown, }; const parsed = AnyResourceSchema.parse(together); return [parsed]; } catch (e) { if (e instanceof YAMLParseError) { throw new ResourceFileError( "Error parsing Markdown frontmatter", filePath, e.message, ); } if (e instanceof ZodError) { throw new ResourceFileError( "Error parsing resource file", filePath, e.message, ); } throw e; } } /** * Load and process all documents in a yaml file at the given path * @param filePath - The path to the yaml file * @returns An array of validated resources * * @throws ResourceFileError * @throws NullResourceError */ export async function loadResourceFile( filePath: string, ): Promise { const file = Bun.file(filePath); const text = await file.text(); const format = determineFileFormat(filePath); switch (format) { case "yaml": return loadYamlResourceFile(filePath, text); case "markdown": return loadMarkdownResourceFile(filePath, text); } }