From 9cd2105fc7233e5c5e9564889016b1b596fc1a13 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 15 Jan 2025 17:37:52 +0000 Subject: [PATCH] fix: defer validation of reference entries --- .changeset/heavy-fireants-melt.md | 5 ++ packages/astro/src/content/runtime.ts | 19 +---- packages/astro/test/content-layer.test.js | 18 +++++ .../content-layer/src/content.config.ts | 77 ++++++------------- .../fixtures/content-layer/src/data/cats.json | 51 ++++++++++++ 5 files changed, 100 insertions(+), 70 deletions(-) create mode 100644 .changeset/heavy-fireants-melt.md create mode 100644 packages/astro/test/fixtures/content-layer/src/data/cats.json diff --git a/.changeset/heavy-fireants-melt.md b/.changeset/heavy-fireants-melt.md new file mode 100644 index 000000000000..b8010626aab7 --- /dev/null +++ b/.changeset/heavy-fireants-melt.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a bug that caused references to be incorrectly reported as invalid diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts index aa6b63be3e83..019a4c8b12cf 100644 --- a/packages/astro/src/content/runtime.ts +++ b/packages/astro/src/content/runtime.ts @@ -642,7 +642,6 @@ export function createReference({ lookupMap }: { lookupMap: ContentLookupMap }) } const flattenedErrorPath = ctx.path.join('.'); - const collectionIsInStore = store.hasCollection(collection); if (typeof lookup === 'object') { // If these don't match then something is wrong with the reference @@ -659,22 +658,8 @@ export function createReference({ lookupMap }: { lookupMap: ContentLookupMap }) return lookup; } - if (collectionIsInStore) { - const entry = store.get(collection, lookup); - if (!entry) { - ctx.addIssue({ - code: ZodIssueCode.custom, - message: `**${flattenedErrorPath}**: Reference to ${collection} invalid. Entry ${lookup} does not exist.`, - }); - return; - } - return { id: lookup, collection }; - } - - // If the collection is not in the lookup map or store, it may be a content layer collection and the store may not yet be populated. - // If the store has 0 or 1 entries it probably means that the entries have not yet been loaded. - // The store may have a single entry even if the collections have not loaded, because the top-level metadata collection is generated early. - if (!lookupMap[collection] && store.collections().size <= 1) { + // If the collection is not in the lookup map it may be a content layer collection and the store may not yet be populated. + if (!lookupMap[collection]) { // For now, we can't validate this reference, so we'll optimistically convert it to a reference object which we'll validate // later in the pipeline when we do have access to the store. return { id: lookup, collection }; diff --git a/packages/astro/test/content-layer.test.js b/packages/astro/test/content-layer.test.js index 002c097fcdc3..8a15826620d9 100644 --- a/packages/astro/test/content-layer.test.js +++ b/packages/astro/test/content-layer.test.js @@ -313,6 +313,24 @@ describe('Content Layer', () => { assert.equal(newJson.increment.data.lastValue, 1); await fixture.resetAllFiles(); }); + + + it('can handle references being renamed after a build', async () => { + let newJson = devalue.parse(await fixture.readFile('/collections.json')); + assert.deepEqual(newJson.entryWithReference.data.cat, { collection: 'cats', id: 'tabby' }); + await fixture.editFile('src/data/cats.json', (prev) => { + return prev.replace('tabby', 'tabby-cat'); + }); + await fixture.editFile('src/content/space/columbia-copy.md', (prev) => { + return prev.replace('cat: tabby', 'cat: tabby-cat'); + }); + await fixture.build(); + newJson = devalue.parse(await fixture.readFile('/collections.json')); + assert.deepEqual(newJson.entryWithReference.data.cat, { collection: 'cats', id: 'tabby-cat' }); + await fixture.resetAllFiles(); + }); + + }); describe('Dev', () => { diff --git a/packages/astro/test/fixtures/content-layer/src/content.config.ts b/packages/astro/test/fixtures/content-layer/src/content.config.ts index 484d14d528cf..88ed0231ebc9 100644 --- a/packages/astro/test/fixtures/content-layer/src/content.config.ts +++ b/packages/astro/test/fixtures/content-layer/src/content.config.ts @@ -2,6 +2,7 @@ import { defineCollection, z, reference } from 'astro:content'; import { file, glob } from 'astro/loaders'; import { loader } from './loaders/post-loader.js'; import { parse as parseToml } from 'toml'; +import { readFile } from 'fs/promises'; const blog = defineCollection({ loader: loader({ url: 'https://jsonplaceholder.typicode.com/posts' }), @@ -71,43 +72,32 @@ const rodents = defineCollection({ nocturnal: z.boolean(), }), }); +// Absolute paths should also work +const absoluteRoot = new URL('content/space', import.meta.url); + +const spacecraft = defineCollection({ + loader: glob({ pattern: '*.md', base: absoluteRoot }), + schema: ({ image }) => + z.object({ + title: z.string(), + description: z.string(), + publishedDate: z.coerce.date(), + tags: z.array(z.string()), + heroImage: image().optional(), + cat: reference('cats').default('siamese'), + something: z + .string() + .optional() + .transform((str) => ({ type: 'test', content: str })), + }), +}); + const cats = defineCollection({ loader: async function () { - return [ - { - breed: 'Siamese', - id: 'siamese', - size: 'Medium', - origin: 'Thailand', - lifespan: '15 years', - temperament: ['Active', 'Affectionate', 'Social', 'Playful'], - }, - { - breed: 'Persian', - id: 'persian', - size: 'Medium', - origin: 'Iran', - lifespan: '15 years', - temperament: ['Calm', 'Affectionate', 'Social'], - }, - { - breed: 'Tabby', - id: 'tabby', - size: 'Medium', - origin: 'Egypt', - lifespan: '15 years', - temperament: ['Curious', 'Playful', 'Independent'], - }, - { - breed: 'Ragdoll', - id: 'ragdoll', - size: 'Medium', - origin: 'United States', - lifespan: '15 years', - temperament: ['Calm', 'Affectionate', 'Social'], - }, - ]; + const file = new URL('data/cats.json', import.meta.url); + const data = await readFile(file, 'utf-8'); + return JSON.parse(data); }, schema: z.object({ breed: z.string(), @@ -140,25 +130,6 @@ const birds = defineCollection({ }), }); -// Absolute paths should also work -const absoluteRoot = new URL('content/space', import.meta.url); - -const spacecraft = defineCollection({ - loader: glob({ pattern: '*.md', base: absoluteRoot }), - schema: ({ image }) => - z.object({ - title: z.string(), - description: z.string(), - publishedDate: z.coerce.date(), - tags: z.array(z.string()), - heroImage: image().optional(), - cat: reference('cats').default('siamese'), - something: z - .string() - .optional() - .transform((str) => ({ type: 'test', content: str })), - }), -}); const probes = defineCollection({ loader: glob({ pattern: ['*.md', '!voyager-*'], base: 'src/data/space-probes' }), diff --git a/packages/astro/test/fixtures/content-layer/src/data/cats.json b/packages/astro/test/fixtures/content-layer/src/data/cats.json new file mode 100644 index 000000000000..cf44e70b73b6 --- /dev/null +++ b/packages/astro/test/fixtures/content-layer/src/data/cats.json @@ -0,0 +1,51 @@ +[ + { + "breed": "Siamese", + "id": "siamese", + "size": "Medium", + "origin": "Thailand", + "lifespan": "15 years", + "temperament": [ + "Active", + "Affectionate", + "Social", + "Playful" + ] + }, + { + "breed": "Persian", + "id": "persian", + "size": "Medium", + "origin": "Iran", + "lifespan": "15 years", + "temperament": [ + "Calm", + "Affectionate", + "Social" + ] + }, + { + "breed": "Tabby", + "id": "tabby", + "size": "Medium", + "origin": "Egypt", + "lifespan": "15 years", + "temperament": [ + "Curious", + "Playful", + "Independent" + ] + }, + { + "breed": "Ragdoll", + "id": "ragdoll", + "size": "Medium", + "origin": "United States", + "lifespan": "15 years", + "temperament": [ + "Calm", + "Affectionate", + "Social" + ] + } +]