diff --git a/.changeset/flat-baboons-nail.md b/.changeset/flat-baboons-nail.md new file mode 100644 index 0000000000..128d4234ba --- /dev/null +++ b/.changeset/flat-baboons-nail.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Prevent frontmatter errors from crashing the dev server diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts index e695f9e602..ab0e254add 100644 --- a/packages/astro/src/content/types-generator.ts +++ b/packages/astro/src/content/types-generator.ts @@ -226,8 +226,17 @@ export async function createContentTypesGenerator({ events.push(event); debounceTimeout && clearTimeout(debounceTimeout); + const runEventsSafe = async () => { + try { + await runEvents(opts); + } catch { + // Prevent frontmatter errors from crashing the server. The errors + // are still reported on page reflects as desired. + // Errors still crash dev from *starting*. + } + }; debounceTimeout = setTimeout( - async () => runEvents(opts), + runEventsSafe, 50 /* debounce to batch chokidar events */ ); } diff --git a/packages/astro/test/units/content-collections/frontmatter.test.js b/packages/astro/test/units/content-collections/frontmatter.test.js new file mode 100644 index 0000000000..d4a9f1ecee --- /dev/null +++ b/packages/astro/test/units/content-collections/frontmatter.test.js @@ -0,0 +1,72 @@ +import { fileURLToPath } from 'node:url'; +import nodeFS from 'node:fs'; +import path from 'node:path'; +import slash from 'slash'; + +import { runInContainer } from '../../../dist/core/dev/index.js'; +import { attachContentServerListeners } from '../../../dist/content/index.js'; +import { createFs, triggerFSEvent } from '../test-utils.js'; + +const root = new URL('../../fixtures/alias/', import.meta.url); + +function getTypesDts() { + const typesdtsURL = new URL('../../../src/content/template/types.d.ts', import.meta.url); + const relpath = slash(path.relative(fileURLToPath(root), fileURLToPath(typesdtsURL))); + return { + [relpath]: nodeFS.readFileSync(typesdtsURL, 'utf-8') + }; +} + +describe('frontmatter', () => { + it('errors in content/ does not crash server', async () => { + const fs = createFs( + { + ...getTypesDts(), + '/src/content/posts/blog.md': ` + --- + title: One + --- + `, + '/src/content/config.ts': ` + import { defineCollection, z } from 'astro:content'; + + const posts = defineCollection({ + schema: z.string() + }); + + export const collections = { + posts + }; + `, + '/src/pages/index.astro': ` + --- + --- + + Test + +

Test

+ + + `, + }, + root + ); + + await runInContainer({ fs, root }, async (container) => { + await attachContentServerListeners(container); + + fs.writeFileFromRootSync( + '/src/content/posts/blog.md', + ` + --- + title: One + title: two + --- + ` + ); + triggerFSEvent(container, fs, '/src/content/posts/blog.md', 'change'); + await new Promise(resolve => setTimeout(resolve, 100)); + // Note, if we got here, it didn't crash + }); + }) +}); diff --git a/packages/astro/test/units/test-utils.js b/packages/astro/test/units/test-utils.js index f142fceaf6..68a86d4756 100644 --- a/packages/astro/test/units/test-utils.js +++ b/packages/astro/test/units/test-utils.js @@ -3,7 +3,7 @@ import { Volume } from 'memfs'; import httpMocks from 'node-mocks-http'; import realFS from 'node:fs'; import npath from 'path'; -import { fileURLToPath } from 'url'; +import { fileURLToPath, pathToFileURL } from 'url'; import { unixify } from './correct-path.js'; class VirtualVolume extends Volume { @@ -26,6 +26,10 @@ class VirtualVolume extends Volume { return npath.posix.join(this.#root, pth); } + readFile(p, ...args) { + return super.readFile(this.#forcePath(p), ...args); + } + existsSync(p) { return super.existsSync(this.#forcePath(p)); }