Repository navigation
fix(md): keep the dev server running on invalid frontmatter - #690
Open
morinokami wants to merge 1 commit into
Open
morinokami wants to merge 1 commit into
morinokami wants to merge 1 commit into
Conversation
A Markdown page with invalid frontmatter stopped `vp dev`. Saving one rejected the `@void/md` watcher listener, and the unhandled rejection exited the process. Starting with one failed the plugin's `buildStart` scan. A page removed during a scan, as when switching branches, stopped the server the same way. On the dev server, an invalid page now fails on its own. It answers 500 with the file name and `Invalid frontmatter`, and other pages keep working. The error is logged once per failure, and lists built from the pages keep the page's last good entry. Builds still fail, and now name the page. gray-matter stores a source in its cache before parsing it, so a second read of invalid frontmatter returned empty data. With the server still running, the page would render its fences as content instead of failing, so `parseFrontmatter` passes options, which skips the cache. The shared page scan also stopped startup. It read `layout:` with a regex, so `layout: [unclosed` named a layout called `[unclosed`, which then failed layout validation. It now uses gray-matter, the parser `@void/md` uses, so the two agree on what a page's frontmatter says. `void` already depends on `yaml`, but it only warns on an unknown tag (`layout: !missing value`) and accepts forms gray-matter rejects, such as `<<: value`. So `void` gains gray-matter as a dependency, imported only when an app has Markdown pages. Forms the regex misread now work, such as `layout: post # comment`. A test compares what the two packages read. The scan still parses only frontmatter whose first line is exactly `---`, which keeps it away from gray-matter's `eval` of `---js` blocks. A layout that parses but does not exist still stops startup, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8F8mc1JSJJ8bD9Fkqnoxm
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A Markdown page with invalid frontmatter stops
vp dev. For example, saving this page exits the process with code 1 and aYAMLException:The dev server could stop in four ways:
@void/mdwatcher listener rejected, and the unhandled rejection exited the process.buildStartscan threw.readFileSyncthrewENOENTin the same listener. Adding and removing many pages in a burst reproduces this, which approximates switching branches.layout:line. The shared page scan invoid/pagesreadlayout:with a regex, solayout: [unclosednamed a layout called[unclosed, and layout validation stopped startup. Error handling in@void/mdcannot catch this.Builds also failed on invalid frontmatter, as they should, but the error did not name the file.
Fix
On the dev server, an invalid page now fails on its own. It answers 500 with the file name and
Invalid frontmatter, and other pages keep working. Inpackages/md/src/plugin.ts:scanMdPageshandles errors per page. It logs the error once per failure and keeps the page's last good entry, so lists built from the pages don't change on every save. A page with no good entry yet is left out. A page removed since the glob is skipped without an error.void/pagesplugin does.compile()name the page. Vite's error metadata does not cross the worker boundary, so the file name has to be in the message.parseFrontmatterinpackages/md/src/metadata.tsis now the one place in@void/mdthat parses frontmatter. gray-matter stores a source in its cache before parsing it, so the next read of invalid frontmatter returned empty data with the fences left in the content. Once the server survived, the page answered 200 with its frontmatter rendered as text. Passing options skips the cache.scanPagesinpackages/void/src/pages/scan.tsnow readslayoutwith gray-matter, the parser@void/mduses, so the two agree on what a page's frontmatter says. If the frontmatter does not parse, the page has no layout, and@void/mdreports the error.This also fixes valid forms the regex misread.
layout: post # commentnamed a layout calledpost # comment, andlayout:followed bytitle: Hinamed a layout calledtitle: Hi.This is a bug fix with no new API, so the docs do not change.
New dependency
voidgainsgray-matterindependencies, with the same range as@void/md. Apps without Markdown pages install it but do not load it:scanPagesimports it when it reaches a.mdpage. Importingvoidtakes the same time as onmain(median 257ms, against 260ms onmain), and the parser's strings do not appear in the build output ofplayground/markdown-blog.voidalready depends onyaml, butyamland gray-matter disagreed on 44 of 167 frontmatter inputs:yamlonly warns on an unknown tag (layout: !missing value) and accepts forms gray-matter rejects (<<: value), so a page@void/mdrejects could still stop startup.yamlrejects forms gray-matter accepts, such as tab indentation, so a valid page would lose its layout.Not changed here
layout: nonexistentis valid frontmatter, so it still stops startup. Saved on a running server, it logs the error and the server keeps running.---.@void/mdalso reads---yaml,---and---js, so those pages get no layout from the scan. This keeps the scan away from gray-matter'sevalof---jsblocks.titleanddescriptionin the scan still use the regex, sotitle: Foo # commentreads asFoo # comment.<script>block.titlealone) is valid YAML, so it is not an error, and the frontmatter is a string.Tests
packages/md/test/unit/plugin-invalid-pages.test.ts(new): runs the plugin's hooks against a temporary app. It covers starting with an invalid page, saving one, fixing it, repeated logging, a page removed during a scan, a failed scan and builds. It also compares whatscanPagesandextractMetadataread on 15 frontmatter forms, so a future difference between the two fails a test.packages/md/test/integration/invalid-frontmatter.test.ts(new): runsvp devon a copy ofplayground/markdown-blog. Three invalid pages, two of them with an invalidlayout:line, are present at startup, and another page is saved invalid and then fixed.packages/md/test/unit/metadata.test.ts: every read of invalid frontmatter throws.packages/void/test/unit/pages-scan.test.ts: which forms name a layout, which name none, and which throw.Without this fix, 38 of these unit cases fail, and the integration test fails because the server stops during startup. I checked this by running the tests against the source on
main.Verification
vp test run: 772 files pass, 1 skipped (15,766 tests pass, 29 skipped).vp run check: 33/33 pass.scanPagesandextractMetadataon all 167 inputs:@void/mdrejects 64, and none of them stops the scan. It accepts 103, and both read the same layout.vp devon React (playground/named-layouts) and Vue (playground/markdown-blog):layout: !missing value,layout: [unclosedor four other invalid forms: the server starts, the page answers 500 and names the file, and other pages answer 200.layout: [unclosedon a running server: the server keeps running, the page answers 500, other pages answer 200, and fixing the page brings it back.vp devon React: a plain layout, tab indentation, a quoted value across lines, a merge key and a trailing comment all apply the layout.vp build: invalid frontmatter exits 1 and names the page on React and Vue. On Vue, this includes a page with a client script, and a build with valid pages exits 0.