Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: defer validation of reference entries #12990

Merged
merged 1 commit into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/heavy-fireants-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes a bug that caused references to be incorrectly reported as invalid
19 changes: 2 additions & 17 deletions packages/astro/src/content/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 };
Expand Down
18 changes: 18 additions & 0 deletions packages/astro/test/content-layer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
77 changes: 24 additions & 53 deletions packages/astro/test/fixtures/content-layer/src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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' }),
Expand Down
51 changes: 51 additions & 0 deletions packages/astro/test/fixtures/content-layer/src/data/cats.json
Original file line number Diff line number Diff line change
@@ -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"
]
}
]
Loading