Use dedicated libs to parse TOML, YAML 742319d9
Fixes #15, parsing YAML and TOML using dedicated libraries that support multiline strings correctly.

Also adds tests that were run before and after changes. Multiline string tests expectedly failed before.
Heath Stewart · 2026-02-21 12:48 4 file(s) · +402 −82
bun.lock +11 −0
34 34
        "@clack/prompts": "^1.0.0",
35 35
        "cmd-ts": "^0.14.3",
36 36
        "glob": "^13.0.0",
37 +
        "js-yaml": "^4.1.1",
37 38
        "mime-types": "^2.1.35",
38 39
        "minimatch": "^10.1.1",
39 40
        "open": "^11.0.0",
41 +
        "smol-toml": "^1.6.0",
40 42
      },
41 43
      "devDependencies": {
42 44
        "@biomejs/biome": "^2.3.13",
45 +
        "@types/js-yaml": "^4.0.9",
43 46
        "@types/mime-types": "^3.0.1",
44 47
        "@types/node": "^20",
45 48
      },
581 584
582 585
    "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
583 586
587 +
    "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
588 +
584 589
    "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
585 590
586 591
    "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
623 628
624 629
    "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
625 630
631 +
    "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
632 +
626 633
    "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
627 634
628 635
    "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
1003 1010
1004 1011
    "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
1005 1012
1013 +
    "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
1014 +
1006 1015
    "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
1007 1016
1008 1017
    "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
1374 1383
    "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
1375 1384
1376 1385
    "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
1386 +
1387 +
    "smol-toml": ["smol-toml@1.6.0", "", {}, "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw=="],
1377 1388
1378 1389
    "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
1379 1390
packages/cli/package.json +5 −1
17 17
		"lint": "biome lint --write",
18 18
		"format": "biome format --write",
19 19
		"build": "bun build src/index.ts --target node --outdir dist && mkdir -p dist/components && cp src/components/*.js dist/components/",
20 +
		"test": "bun test",
20 21
		"dev": "bun run build && bun link",
21 22
		"deploy": "bun run build && bun publish"
22 23
	},
23 24
	"devDependencies": {
24 25
		"@biomejs/biome": "^2.3.13",
26 +
		"@types/js-yaml": "^4.0.9",
25 27
		"@types/mime-types": "^3.0.1",
26 28
		"@types/node": "^20"
27 29
	},
34 36
		"@clack/prompts": "^1.0.0",
35 37
		"cmd-ts": "^0.14.3",
36 38
		"glob": "^13.0.0",
39 +
		"js-yaml": "^4.1.1",
37 40
		"mime-types": "^2.1.35",
38 41
		"minimatch": "^10.1.1",
39 -
		"open": "^11.0.0"
42 +
		"open": "^11.0.0",
43 +
		"smol-toml": "^1.6.0"
40 44
	}
41 45
}
packages/cli/src/lib/markdown.ts +9 −81
1 1
import * as fs from "node:fs/promises";
2 2
import * as path from "node:path";
3 3
import { glob } from "glob";
4 +
import yaml from "js-yaml";
4 5
import { minimatch } from "minimatch";
6 +
import * as toml from "smol-toml";
5 7
import type { BlogPost, FrontmatterMapping, PostFrontmatter } from "./types";
6 8
7 9
export function parseFrontmatter(
31 33
	// +++ uses TOML (key = value)
32 34
	// --- and *** use YAML (key: value)
33 35
	const isToml = delimiter === "+++";
34 -
	const separator = isToml ? "=" : ":";
35 -
36 -
	// Parse frontmatter manually
37 -
	const raw: Record<string, unknown> = {};
38 -
	const lines = frontmatterStr.split("\n");
39 -
40 -
	let i = 0;
41 -
	while (i < lines.length) {
42 -
		const line = lines[i];
43 -
		if (line === undefined) {
44 -
			i++;
45 -
			continue;
46 -
		}
47 -
		const sepIndex = line.indexOf(separator);
48 -
		if (sepIndex === -1) {
49 -
			i++;
50 -
			continue;
51 -
		}
52 -
53 -
		const key = line.slice(0, sepIndex).trim();
54 -
		let value = line.slice(sepIndex + 1).trim();
55 36
56 -
		// Handle quoted strings
57 -
		if (
58 -
			(value.startsWith('"') && value.endsWith('"')) ||
59 -
			(value.startsWith("'") && value.endsWith("'"))
60 -
		) {
61 -
			value = value.slice(1, -1);
62 -
		}
63 -
64 -
		// Handle inline arrays (simple case for tags)
65 -
		if (value.startsWith("[") && value.endsWith("]")) {
66 -
			const arrayContent = value.slice(1, -1);
67 -
			raw[key] = arrayContent
68 -
				.split(",")
69 -
				.map((item) => item.trim().replace(/^["']|["']$/g, ""));
70 -
		} else if (value === "" && !isToml) {
71 -
			// Check for YAML-style multiline array (key with no value followed by - items)
72 -
			const arrayItems: string[] = [];
73 -
			let j = i + 1;
74 -
			while (j < lines.length) {
75 -
				const nextLine = lines[j];
76 -
				if (nextLine === undefined) {
77 -
					j++;
78 -
					continue;
79 -
				}
80 -
				// Check if line is a list item (starts with whitespace and -)
81 -
				const listMatch = nextLine.match(/^\s+-\s*(.*)$/);
82 -
				if (listMatch && listMatch[1] !== undefined) {
83 -
					let itemValue = listMatch[1].trim();
84 -
					// Remove quotes if present
85 -
					if (
86 -
						(itemValue.startsWith('"') && itemValue.endsWith('"')) ||
87 -
						(itemValue.startsWith("'") && itemValue.endsWith("'"))
88 -
					) {
89 -
						itemValue = itemValue.slice(1, -1);
90 -
					}
91 -
					arrayItems.push(itemValue);
92 -
					j++;
93 -
				} else if (nextLine.trim() === "") {
94 -
					// Skip empty lines within the array
95 -
					j++;
96 -
				} else {
97 -
					// Hit a new key or non-list content
98 -
					break;
99 -
				}
100 -
			}
101 -
			if (arrayItems.length > 0) {
102 -
				raw[key] = arrayItems;
103 -
				i = j;
104 -
				continue;
105 -
			} else {
106 -
				raw[key] = value;
107 -
			}
108 -
		} else if (value === "true") {
109 -
			raw[key] = true;
110 -
		} else if (value === "false") {
111 -
			raw[key] = false;
112 -
		} else {
113 -
			raw[key] = value;
114 -
		}
115 -
		i++;
37 +
	// Parse frontmatter using the appropriate library
38 +
	let raw: Record<string, unknown>;
39 +
	if (isToml) {
40 +
		raw = toml.parse(frontmatterStr) as Record<string, unknown>;
41 +
	} else {
42 +
		// Use CORE_SCHEMA to keep dates as strings rather than Date objects
43 +
		raw = (yaml.load(frontmatterStr, { schema: yaml.CORE_SCHEMA }) as Record<string, unknown>) ?? {};
116 44
	}
117 45
118 46
	// Apply field mappings to normalize to standard PostFrontmatter fields
packages/cli/test/markdown.test.ts (added) +377 −0
1 +
import { describe, expect, it } from "bun:test";
2 +
import { parseFrontmatter } from "../src/lib/markdown";
3 +
4 +
describe("parseFrontmatter", () => {
5 +
	describe("delimiters", () => {
6 +
		it("parses YAML frontmatter (--- delimiter)", () => {
7 +
			const content = `---
8 +
title: Hello World
9 +
---
10 +
Body content here.`;
11 +
			const { frontmatter, body } = parseFrontmatter(content);
12 +
			expect(frontmatter.title).toBe("Hello World");
13 +
			expect(body).toBe("Body content here.");
14 +
		});
15 +
16 +
		it("parses TOML frontmatter (+++ delimiter)", () => {
17 +
			const content = `+++
18 +
title = "Hugo Post"
19 +
+++
20 +
Body content here.`;
21 +
			const { frontmatter, body } = parseFrontmatter(content);
22 +
			expect(frontmatter.title).toBe("Hugo Post");
23 +
			expect(body).toBe("Body content here.");
24 +
		});
25 +
26 +
		it("parses alternative frontmatter (*** delimiter)", () => {
27 +
			const content = `***
28 +
title: Alt Post
29 +
***
30 +
Body content here.`;
31 +
			const { frontmatter, body } = parseFrontmatter(content);
32 +
			expect(frontmatter.title).toBe("Alt Post");
33 +
			expect(body).toBe("Body content here.");
34 +
		});
35 +
36 +
		it("throws when no frontmatter is present", () => {
37 +
			const content = "Just plain content with no frontmatter.";
38 +
			expect(() => parseFrontmatter(content)).toThrow(
39 +
				"Could not parse frontmatter",
40 +
			);
41 +
		});
42 +
	});
43 +
44 +
	describe("scalar values", () => {
45 +
		it("parses a string value", () => {
46 +
			const content = `---
47 +
title: My Post
48 +
description: A short description
49 +
---
50 +
`;
51 +
			const { frontmatter } = parseFrontmatter(content);
52 +
			expect(frontmatter.title).toBe("My Post");
53 +
			expect(frontmatter.description).toBe("A short description");
54 +
		});
55 +
56 +
		it("strips double quotes from values", () => {
57 +
			const content = `---
58 +
title: "Quoted Title"
59 +
---
60 +
`;
61 +
			const { frontmatter } = parseFrontmatter(content);
62 +
			expect(frontmatter.title).toBe("Quoted Title");
63 +
		});
64 +
65 +
		it("strips single quotes from values", () => {
66 +
			const content = `---
67 +
title: 'Single Quoted'
68 +
---
69 +
`;
70 +
			const { frontmatter } = parseFrontmatter(content);
71 +
			expect(frontmatter.title).toBe("Single Quoted");
72 +
		});
73 +
74 +
		it("parses YAML folded multiline string", () => {
75 +
			const content = `---
76 +
excerpt: >
77 +
  This is a folded
78 +
  multiline string
79 +
---
80 +
`;
81 +
			const { rawFrontmatter } = parseFrontmatter(content);
82 +
			expect(rawFrontmatter.excerpt).toBe("This is a folded multiline string\n");
83 +
		});
84 +
85 +
		it("parses YAML stripped folded multiline string", () => {
86 +
			const content = `---
87 +
excerpt: >-
88 +
  This is a stripped folded
89 +
  multiline string
90 +
---
91 +
`;
92 +
			const { rawFrontmatter } = parseFrontmatter(content);
93 +
			expect(rawFrontmatter.excerpt).toBe("This is a stripped folded multiline string");
94 +
		});
95 +
96 +
		it("parses YAML literal multiline string", () => {
97 +
			const content = `---
98 +
excerpt: |
99 +
  This is a literal
100 +
  multiline string
101 +
---
102 +
`;
103 +
			const { rawFrontmatter } = parseFrontmatter(content);
104 +
			expect(rawFrontmatter.excerpt).toBe("This is a literal\nmultiline string\n");
105 +
		});
106 +
107 +
		it("parses YAML kept literal multiline string", () => {
108 +
			const content = `---
109 +
excerpt: |+
110 +
  This is a kept literal
111 +
  multiline string
112 +
113 +
end: true
114 +
---
115 +
`;
116 +
			const { rawFrontmatter } = parseFrontmatter(content);
117 +
			expect(rawFrontmatter.excerpt).toBe("This is a kept literal\nmultiline string\n\n");
118 +
		});
119 +
120 +
		it("parses boolean true", () => {
121 +
			const content = `---
122 +
draft: true
123 +
---
124 +
`;
125 +
			const { frontmatter } = parseFrontmatter(content);
126 +
			expect(frontmatter.draft).toBe(true);
127 +
		});
128 +
129 +
		it("parses boolean false", () => {
130 +
			const content = `---
131 +
draft: false
132 +
---
133 +
`;
134 +
			const { frontmatter } = parseFrontmatter(content);
135 +
			expect(frontmatter.draft).toBe(false);
136 +
		});
137 +
138 +
		it('parses string "true" in draft field as boolean true', () => {
139 +
			const content = `---
140 +
draft: true
141 +
---
142 +
`;
143 +
			const { rawFrontmatter } = parseFrontmatter(content);
144 +
			expect(rawFrontmatter.draft).toBe(true);
145 +
		});
146 +
	});
147 +
148 +
	describe("arrays", () => {
149 +
		it("parses inline YAML arrays", () => {
150 +
			const content = `---
151 +
tags: [typescript, bun, testing]
152 +
---
153 +
`;
154 +
			const { frontmatter } = parseFrontmatter(content);
155 +
			expect(frontmatter.tags).toEqual(["typescript", "bun", "testing"]);
156 +
		});
157 +
158 +
		it("parses inline YAML arrays with quoted items", () => {
159 +
			const content = `---
160 +
tags: ["typescript", "bun", "testing"]
161 +
---
162 +
`;
163 +
			const { frontmatter } = parseFrontmatter(content);
164 +
			expect(frontmatter.tags).toEqual(["typescript", "bun", "testing"]);
165 +
		});
166 +
167 +
		it("parses YAML block arrays", () => {
168 +
			const content = `---
169 +
tags:
170 +
  - typescript
171 +
  - bun
172 +
  - testing
173 +
---
174 +
`;
175 +
			const { frontmatter } = parseFrontmatter(content);
176 +
			expect(frontmatter.tags).toEqual(["typescript", "bun", "testing"]);
177 +
		});
178 +
179 +
		it("parses YAML block arrays with quoted items", () => {
180 +
			const content = `---
181 +
tags:
182 +
  - "typescript"
183 +
  - 'bun'
184 +
---
185 +
`;
186 +
			const { frontmatter } = parseFrontmatter(content);
187 +
			expect(frontmatter.tags).toEqual(["typescript", "bun"]);
188 +
		});
189 +
190 +
		it("parses inline TOML arrays", () => {
191 +
			const content = `+++
192 +
tags = ["typescript", "bun"]
193 +
+++
194 +
`;
195 +
			const { frontmatter } = parseFrontmatter(content);
196 +
			expect(frontmatter.tags).toEqual(["typescript", "bun"]);
197 +
		});
198 +
	});
199 +
200 +
	describe("publish date fallbacks", () => {
201 +
		it("uses publishDate field directly", () => {
202 +
			const content = `---
203 +
publishDate: 2024-01-15
204 +
---
205 +
`;
206 +
			const { frontmatter } = parseFrontmatter(content);
207 +
			expect(frontmatter.publishDate).toBe("2024-01-15");
208 +
		});
209 +
210 +
		it("falls back to pubDate", () => {
211 +
			const content = `---
212 +
pubDate: 2024-02-01
213 +
---
214 +
`;
215 +
			const { frontmatter } = parseFrontmatter(content);
216 +
			expect(frontmatter.publishDate).toBe("2024-02-01");
217 +
		});
218 +
219 +
		it("falls back to date", () => {
220 +
			const content = `---
221 +
date: 2024-03-10
222 +
---
223 +
`;
224 +
			const { frontmatter } = parseFrontmatter(content);
225 +
			expect(frontmatter.publishDate).toBe("2024-03-10");
226 +
		});
227 +
228 +
		it("falls back to createdAt", () => {
229 +
			const content = `---
230 +
createdAt: 2024-04-20
231 +
---
232 +
`;
233 +
			const { frontmatter } = parseFrontmatter(content);
234 +
			expect(frontmatter.publishDate).toBe("2024-04-20");
235 +
		});
236 +
237 +
		it("falls back to created_at", () => {
238 +
			const content = `---
239 +
created_at: 2024-05-30
240 +
---
241 +
`;
242 +
			const { frontmatter } = parseFrontmatter(content);
243 +
			expect(frontmatter.publishDate).toBe("2024-05-30");
244 +
		});
245 +
246 +
		it("prefers publishDate over other fallbacks", () => {
247 +
			const content = `---
248 +
publishDate: 2024-01-01
249 +
date: 2023-01-01
250 +
---
251 +
`;
252 +
			const { frontmatter } = parseFrontmatter(content);
253 +
			expect(frontmatter.publishDate).toBe("2024-01-01");
254 +
		});
255 +
	});
256 +
257 +
	describe("rawFrontmatter", () => {
258 +
		it("returns all raw fields", () => {
259 +
			const content = `---
260 +
title: Raw Test
261 +
custom: value
262 +
---
263 +
`;
264 +
			const { rawFrontmatter } = parseFrontmatter(content);
265 +
			expect(rawFrontmatter.title).toBe("Raw Test");
266 +
			expect(rawFrontmatter.custom).toBe("value");
267 +
		});
268 +
269 +
		it("preserves atUri in both frontmatter and rawFrontmatter", () => {
270 +
			const content = `---
271 +
title: Post
272 +
atUri: at://did:plc:abc123/app.bsky.feed.post/xyz
273 +
---
274 +
`;
275 +
			const { frontmatter, rawFrontmatter } = parseFrontmatter(content);
276 +
			expect(frontmatter.atUri).toBe(
277 +
				"at://did:plc:abc123/app.bsky.feed.post/xyz",
278 +
			);
279 +
			expect(rawFrontmatter.atUri).toBe(
280 +
				"at://did:plc:abc123/app.bsky.feed.post/xyz",
281 +
			);
282 +
		});
283 +
	});
284 +
285 +
	describe("FrontmatterMapping", () => {
286 +
		it("maps a custom title field", () => {
287 +
			const content = `---
288 +
name: My Mapped Title
289 +
---
290 +
`;
291 +
			const { frontmatter } = parseFrontmatter(content, { title: "name" });
292 +
			expect(frontmatter.title).toBe("My Mapped Title");
293 +
		});
294 +
295 +
		it("maps a custom description field", () => {
296 +
			const content = `---
297 +
summary: Custom description
298 +
---
299 +
`;
300 +
			const { frontmatter } = parseFrontmatter(content, {
301 +
				description: "summary",
302 +
			});
303 +
			expect(frontmatter.description).toBe("Custom description");
304 +
		});
305 +
306 +
		it("maps a custom publishDate field", () => {
307 +
			const content = `---
308 +
publishedOn: 2024-06-15
309 +
---
310 +
`;
311 +
			const { frontmatter } = parseFrontmatter(content, {
312 +
				publishDate: "publishedOn",
313 +
			});
314 +
			expect(frontmatter.publishDate).toBe("2024-06-15");
315 +
		});
316 +
317 +
		it("maps a custom coverImage field", () => {
318 +
			const content = `---
319 +
heroImage: /images/cover.jpg
320 +
---
321 +
`;
322 +
			const { frontmatter } = parseFrontmatter(content, {
323 +
				coverImage: "heroImage",
324 +
			});
325 +
			expect(frontmatter.ogImage).toBe("/images/cover.jpg");
326 +
		});
327 +
328 +
		it("maps a custom tags field", () => {
329 +
			const content = `---
330 +
categories: [news, updates]
331 +
---
332 +
`;
333 +
			const { frontmatter } = parseFrontmatter(content, { tags: "categories" });
334 +
			expect(frontmatter.tags).toEqual(["news", "updates"]);
335 +
		});
336 +
337 +
		it("maps a custom draft field", () => {
338 +
			const content = `---
339 +
unpublished: true
340 +
---
341 +
`;
342 +
			const { frontmatter } = parseFrontmatter(content, { draft: "unpublished" });
343 +
			expect(frontmatter.draft).toBe(true);
344 +
		});
345 +
346 +
		it("falls back to standard field name when mapped field is absent", () => {
347 +
			const content = `---
348 +
title: Standard Title
349 +
---
350 +
`;
351 +
			const { frontmatter } = parseFrontmatter(content, { title: "heading" });
352 +
			expect(frontmatter.title).toBe("Standard Title");
353 +
		});
354 +
	});
355 +
356 +
	describe("body", () => {
357 +
		it("returns the body content after the closing delimiter", () => {
358 +
			const content = `---
359 +
title: Post
360 +
---
361 +
# Heading
362 +
363 +
Some paragraph text.`;
364 +
			const { body } = parseFrontmatter(content);
365 +
			expect(body).toBe("# Heading\n\nSome paragraph text.");
366 +
		});
367 +
368 +
		it("returns an empty body when there is no content after frontmatter", () => {
369 +
			const content = `---
370 +
title: Post
371 +
---
372 +
`;
373 +
			const { body } = parseFrontmatter(content);
374 +
			expect(body).toBe("");
375 +
		});
376 +
	});
377 +
});