48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
|
import { parseDocument } from "yaml";
|
|
|
|
export type CatalogIndex<T> = {
|
|
description: string;
|
|
metadata: T;
|
|
};
|
|
|
|
export function parseCatalogIndex<T>(source: string, indexPath: string): CatalogIndex<T> {
|
|
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<T>(indexPath: string): Promise<CatalogIndex<T>> {
|
|
return parseCatalogIndex<T>(await readFile(indexPath, "utf8"), indexPath);
|
|
}
|