mirror of
https://github.com/withastro/astro.git
synced 2025-03-24 23:21:57 -05:00
fix(astro): type generation for empty collections (#11264)
* fix(astro): type generation for empty collections * Update .changeset/light-bugs-shake.md Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> --------- Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
This commit is contained in:
parent
246bd7a516
commit
5a9c9a60e7
4 changed files with 68 additions and 21 deletions
5
.changeset/light-bugs-shake.md
Normal file
5
.changeset/light-bugs-shake.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes type generation for empty content collections
|
|
@ -35,18 +35,18 @@ type DataEntryMetadata = Record<string, never>;
|
|||
type ContentEntryMetadata = { slug: string };
|
||||
type CollectionEntryMap = {
|
||||
[collection: string]:
|
||||
| {
|
||||
type: 'unknown';
|
||||
entries: Record<string, never>;
|
||||
}
|
||||
| {
|
||||
type: 'content';
|
||||
entries: Record<string, ContentEntryMetadata>;
|
||||
}
|
||||
| {
|
||||
type: 'data';
|
||||
entries: Record<string, DataEntryMetadata>;
|
||||
};
|
||||
| {
|
||||
type: 'unknown';
|
||||
entries: Record<string, never>;
|
||||
}
|
||||
| {
|
||||
type: 'content';
|
||||
entries: Record<string, ContentEntryMetadata>;
|
||||
}
|
||||
| {
|
||||
type: 'data';
|
||||
entries: Record<string, DataEntryMetadata>;
|
||||
};
|
||||
};
|
||||
|
||||
type CreateContentGeneratorParams = {
|
||||
|
@ -425,16 +425,21 @@ async function writeContentFiles({
|
|||
const resolvedType: 'content' | 'data' =
|
||||
collection.type === 'unknown'
|
||||
? // Add empty / unknown collections to the data type map by default
|
||||
// This ensures `getCollection('empty-collection')` doesn't raise a type error
|
||||
collectionConfig?.type ?? 'data'
|
||||
// This ensures `getCollection('empty-collection')` doesn't raise a type error
|
||||
collectionConfig?.type ?? 'data'
|
||||
: collection.type;
|
||||
|
||||
const collectionEntryKeys = Object.keys(collection.entries).sort();
|
||||
const dataType = collectionConfig?.schema ? `InferEntrySchema<${collectionKey}>` : 'any';
|
||||
switch (resolvedType) {
|
||||
case 'content':
|
||||
if (collectionEntryKeys.length === 0) {
|
||||
contentTypesStr += `${collectionKey}: Record<string, {\n id: string;\n slug: string;\n body: string;\n collection: ${collectionKey};\n data: ${dataType};\n render(): Render[".md"];\n}>;\n`;
|
||||
break;
|
||||
}
|
||||
contentTypesStr += `${collectionKey}: {\n`;
|
||||
for (const entryKey of Object.keys(collection.entries).sort()) {
|
||||
for (const entryKey of collectionEntryKeys) {
|
||||
const entryMetadata = collection.entries[entryKey];
|
||||
const dataType = collectionConfig?.schema ? `InferEntrySchema<${collectionKey}>` : 'any';
|
||||
const renderType = `{ render(): Render[${JSON.stringify(
|
||||
path.extname(JSON.parse(entryKey))
|
||||
)}] }`;
|
||||
|
@ -445,10 +450,14 @@ async function writeContentFiles({
|
|||
contentTypesStr += `};\n`;
|
||||
break;
|
||||
case 'data':
|
||||
dataTypesStr += `${collectionKey}: {\n`;
|
||||
for (const entryKey of Object.keys(collection.entries).sort()) {
|
||||
const dataType = collectionConfig?.schema ? `InferEntrySchema<${collectionKey}>` : 'any';
|
||||
dataTypesStr += `${entryKey}: {\n id: ${entryKey};\n collection: ${collectionKey};\n data: ${dataType}\n};\n`;
|
||||
if (collectionEntryKeys.length === 0) {
|
||||
dataTypesStr += `${collectionKey}: Record<string, {\n id: string;\n collection: ${collectionKey};\n data: ${dataType};\n}>;\n`;
|
||||
} else {
|
||||
dataTypesStr += `${collectionKey}: {\n`;
|
||||
for (const entryKey of collectionEntryKeys) {
|
||||
dataTypesStr += `${entryKey}: {\n id: ${entryKey};\n collection: ${collectionKey};\n data: ${dataType}\n};\n`;
|
||||
}
|
||||
dataTypesStr += `};\n`;
|
||||
}
|
||||
|
||||
if (settings.config.experimental.contentCollectionJsonSchema && collectionConfig?.schema) {
|
||||
|
@ -481,7 +490,6 @@ async function writeContentFiles({
|
|||
);
|
||||
}
|
||||
}
|
||||
dataTypesStr += `};\n`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,32 @@ describe('astro sync', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('Writes types for empty collections', async () => {
|
||||
await fixture.whenSyncing('./fixtures/content-collections-empty-dir/');
|
||||
fixture.thenFileShouldExist('.astro/types.d.ts');
|
||||
fixture.thenFileContentShouldInclude(
|
||||
'.astro/types.d.ts',
|
||||
`"blog": Record<string, {
|
||||
id: string;
|
||||
slug: string;
|
||||
body: string;
|
||||
collection: "blog";
|
||||
data: InferEntrySchema<"blog">;
|
||||
render(): Render[".md"];
|
||||
}>;`,
|
||||
'Types file does not include empty collection type'
|
||||
);
|
||||
fixture.thenFileContentShouldInclude(
|
||||
'.astro/types.d.ts',
|
||||
`"blogMeta": Record<string, {
|
||||
id: string;
|
||||
collection: "blogMeta";
|
||||
data: InferEntrySchema<"blogMeta">;
|
||||
}>;`,
|
||||
'Types file does not include empty collection type'
|
||||
);
|
||||
});
|
||||
|
||||
it('Adds type reference to `src/env.d.ts`', async () => {
|
||||
await fixture.whenSyncing('./fixtures/content-collections/');
|
||||
fixture.thenFileShouldExist('src/env.d.ts');
|
||||
|
|
|
@ -6,6 +6,14 @@ const blog = defineCollection({
|
|||
}),
|
||||
});
|
||||
|
||||
const blogMeta = defineCollection({
|
||||
type: 'data',
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
blog,
|
||||
blogMeta,
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue