Don't throw on empty or invalid frontmatter

Since js-yaml 5 an empty or comment-only block throws instead of returning
undefined, as does invalid YAML. Markdown renders the content from the
same parse, so one such block made the whole document fail to render.
This commit is contained in:
Rūdolfs Ošiņš
2026-10-01 18:20:24 +02:00
parent 5d8bc00f13
commit be0c2a424b
2 changed files with 56 additions and 1 deletions
+6 -1
View File
@@ -12,7 +12,12 @@ export function parseFrontmatter(input: string): ParsedFrontmatter {
if (!match) {
return { data: {}, content: input };
}
const parsed = load(match[1]);
let parsed: unknown;
try {
parsed = load(match[1]);
} catch {
parsed = undefined;
}
const data =
parsed && typeof parsed === "object" && !Array.isArray(parsed)
? (parsed as Record<string, unknown>)
+50
View File
@@ -0,0 +1,50 @@
import { expect, test } from "vitest";
import { parseFrontmatter } from "@app/lib/frontmatter";
test.each([
["LF", "---\ntitle: Hello\nn: 1\n---\n# Body\n"],
["CRLF", "---\r\ntitle: Hello\r\nn: 1\r\n---\r\n# Body\n"],
])("splits %s frontmatter from the content", (_, input) => {
expect(parseFrontmatter(input)).toEqual({
data: { title: "Hello", n: 1 },
content: "# Body\n",
});
});
test("accepts a closing fence at the end of the input", () => {
expect(parseFrontmatter("---\na: 1\n---")).toEqual({
data: { a: 1 },
content: "",
});
});
test("stops at the first closing fence", () => {
expect(parseFrontmatter("---\na: 1\n---\ntext\n---\nmore")).toEqual({
data: { a: 1 },
content: "text\n---\nmore",
});
});
test.each([
"# Title\n---\na: 1\n---\n",
" ---\na: 1\n---\n",
"---\na: 1\n",
"---\n---\n",
])("leaves %j without frontmatter as is", input => {
expect(parseFrontmatter(input)).toEqual({ data: {}, content: input });
});
test.each([
["a scalar", "just text"],
["a list", "- a\n- b"],
["null", "~"],
["nothing", ""],
["a comment", "# draft"],
["invalid YAML", "Note: see: below"],
])("drops a block holding %s but strips it", (_, yaml) => {
expect(parseFrontmatter(`---\n${yaml}\n---\nbody`)).toEqual({
data: {},
content: "body",
});
});