import { readFile } from "node:fs/promises"; import { parseDocument } from "yaml"; export type CatalogIndex = { description: string; metadata: T; }; export function parseCatalogIndex(source: string, indexPath: string): CatalogIndex { const lines = source.split(/\r?\n/); if (lines[0] !== "---") { throw new Error(`${indexPath}: YAML Front Matter must start with ---`); } const closingDelimiter = lines.indexOf("---", 1); if (closingDelimiter === -1) { throw new Error(`${indexPath}: YAML Front Matter is missing its closing ---`); } const document = parseDocument(lines.slice(1, closingDelimiter).join("\n"), { prettyErrors: true, }); if (document.errors.length > 0) { throw new Error( `${indexPath}: ${document.errors.map((error) => error.message).join("; ")}`, ); } const metadata = document.toJS() as unknown; if (typeof metadata !== "object" || metadata === null || Array.isArray(metadata)) { throw new Error(`${indexPath}: YAML Front Matter must contain an object`); } if (Object.hasOwn(metadata, "description")) { throw new Error(`${indexPath}: description must be the Markdown body`); } const description = lines.slice(closingDelimiter + 1).join("\n").trim(); if (description.length === 0) { throw new Error(`${indexPath}: Markdown description is missing`); } return { metadata: metadata as T, description }; } export async function readCatalogIndex(indexPath: string): Promise> { return parseCatalogIndex(await readFile(indexPath, "utf8"), indexPath); }