Skip to content

Commit 3f02d5f

Browse files
authored
feat: support new location for content config (#12475)
* feat: support new location for content config * Test fixes * Handle missing dir * Handle missing content dir * chore: changes from review * Revert legacy fixtures * Clarify changeset
1 parent 18a04c0 commit 3f02d5f

File tree

39 files changed

+79
-37
lines changed

39 files changed

+79
-37
lines changed

.changeset/thirty-clocks-jump.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'astro': minor
3+
---
4+
5+
Changes the default content config location from `src/content/config.*` to `src/content.config.*`.
6+
7+
The previous location is still supported, and is required if the `legacy.collections` flag is enabled.
File renamed without changes.
File renamed without changes.

packages/astro/src/content/runtime.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,22 @@ type GlobResult = Record<string, LazyImport>;
2626
type CollectionToEntryMap = Record<string, GlobResult>;
2727
type GetEntryImport = (collection: string, lookupId: string) => Promise<LazyImport>;
2828

29+
export function getImporterFilename() {
30+
// The 4th line in the stack trace should be the importer filename
31+
const stackLine = new Error().stack?.split('\n')?.[3];
32+
if (!stackLine) {
33+
return null;
34+
}
35+
// Extract the relative path from the stack line
36+
const match = /\/(src\/.*?):\d+:\d+/.exec(stackLine);
37+
return match?.[1] ?? null;
38+
}
39+
2940
export function defineCollection(config: any) {
3041
if ('loader' in config) {
3142
if (config.type && config.type !== CONTENT_LAYER_TYPE) {
3243
throw new AstroUserError(
33-
'Collections that use the Content Layer API must have a `loader` defined and no `type` set.',
34-
"Check your collection definitions in `src/content/config.*`.'",
44+
`Collections that use the Content Layer API must have a \`loader\` defined and no \`type\` set. Check your collection definitions in ${getImporterFilename() ?? 'your content config file'}.`,
3545
);
3646
}
3747
config.type = CONTENT_LAYER_TYPE;

packages/astro/src/content/server-listeners.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,16 @@ export async function attachContentServerListeners({
2424
settings,
2525
}: ContentServerListenerParams) {
2626
const contentPaths = getContentPaths(settings.config, fs);
27-
28-
if (fs.existsSync(contentPaths.contentDir)) {
27+
if (!settings.config.legacy?.collections) {
28+
const contentGenerator = await createContentTypesGenerator({
29+
fs,
30+
settings,
31+
logger,
32+
viteServer,
33+
contentConfigObserver: globalContentConfigObserver,
34+
});
35+
await contentGenerator.init();
36+
} else if (fs.existsSync(contentPaths.contentDir)) {
2937
logger.debug(
3038
'content',
3139
`Watching ${cyan(

packages/astro/src/content/types-generator.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,12 @@ export async function createContentTypesGenerator({
8686
async function init(): Promise<
8787
{ typesGenerated: true } | { typesGenerated: false; reason: 'no-content-dir' }
8888
> {
89-
if (!fs.existsSync(contentPaths.contentDir)) {
90-
return { typesGenerated: false, reason: 'no-content-dir' };
91-
}
92-
9389
events.push({ name: 'add', entry: contentPaths.config.url });
9490

9591
if (settings.config.legacy.collections) {
92+
if (!fs.existsSync(contentPaths.contentDir)) {
93+
return { typesGenerated: false, reason: 'no-content-dir' };
94+
}
9695
const globResult = await glob('**', {
9796
cwd: fileURLToPath(contentPaths.contentDir),
9897
fs: {

packages/astro/src/content/utils.ts

+14-8
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ export async function autogenerateCollections({
597597
}) as any,
598598
};
599599
}
600-
if (!usesContentLayer) {
600+
if (!usesContentLayer && fs.existsSync(contentDir)) {
601601
// If the user hasn't defined any collections using the content layer, we'll try and help out by checking for
602602
// any orphaned folders in the content directory and creating collections for them.
603603
const orphanedCollections = [];
@@ -623,7 +623,7 @@ export async function autogenerateCollections({
623623
console.warn(
624624
`
625625
Auto-generating collections for folders in "src/content/" that are not defined as collections.
626-
This is deprecated, so you should define these collections yourself in "src/content/config.ts".
626+
This is deprecated, so you should define these collections yourself in "src/content.config.ts".
627627
The following collections have been auto-generated: ${orphanedCollections
628628
.map((name) => green(name))
629629
.join(', ')}\n`,
@@ -715,10 +715,10 @@ export type ContentPaths = {
715715
};
716716

717717
export function getContentPaths(
718-
{ srcDir }: Pick<AstroConfig, 'root' | 'srcDir'>,
718+
{ srcDir, legacy }: Pick<AstroConfig, 'root' | 'srcDir' | 'legacy'>,
719719
fs: typeof fsMod = fsMod,
720720
): ContentPaths {
721-
const configStats = search(fs, srcDir);
721+
const configStats = search(fs, srcDir, legacy?.collections);
722722
const pkgBase = new URL('../../', import.meta.url);
723723
return {
724724
contentDir: new URL('./content/', srcDir),
@@ -728,10 +728,16 @@ export function getContentPaths(
728728
config: configStats,
729729
};
730730
}
731-
function search(fs: typeof fsMod, srcDir: URL) {
732-
const paths = ['config.mjs', 'config.js', 'config.mts', 'config.ts'].map(
733-
(p) => new URL(`./content/${p}`, srcDir),
734-
);
731+
function search(fs: typeof fsMod, srcDir: URL, legacy?: boolean) {
732+
const paths = [
733+
...(legacy
734+
? []
735+
: ['content.config.mjs', 'content.config.js', 'content.config.mts', 'content.config.ts']),
736+
'content/config.mjs',
737+
'content/config.js',
738+
'content/config.mts',
739+
'content/config.ts',
740+
].map((p) => new URL(`./${p}`, srcDir));
735741
for (const file of paths) {
736742
if (fs.existsSync(file)) {
737743
return { exists: true, url: file };

packages/astro/src/core/errors/errors-data.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1446,7 +1446,8 @@ export const GenerateContentTypesError = {
14461446
title: 'Failed to generate content types.',
14471447
message: (errorMessage: string) =>
14481448
`\`astro sync\` command failed to generate content collection types: ${errorMessage}`,
1449-
hint: 'This error is often caused by a syntax error inside your content, or your content configuration file. Check your `src/content/config.*` file for typos.',
1449+
hint: (fileName?: string) =>
1450+
`This error is often caused by a syntax error inside your content, or your content configuration file. Check your ${fileName ?? 'content config'} file for typos.`,
14501451
} satisfies ErrorData;
14511452
/**
14521453
* @docs
@@ -1458,7 +1459,7 @@ export const GenerateContentTypesError = {
14581459
* @docs
14591460
* @description
14601461
* Astro encountered an unknown error loading your content collections.
1461-
* This can be caused by certain errors inside your `src/content/config.ts` file or some internal errors.
1462+
* This can be caused by certain errors inside your `src/content.config.ts` file or some internal errors.
14621463
*
14631464
* If you can reliably cause this error to happen, we'd appreciate if you could [open an issue](https://astro.build/issues/)
14641465
*/
@@ -1501,7 +1502,7 @@ export const GetEntryDeprecationError = {
15011502
* @description
15021503
* A Markdown or MDX entry does not match its collection schema.
15031504
* Make sure that all required fields are present, and that all fields are of the correct type.
1504-
* You can check against the collection schema in your `src/content/config.*` file.
1505+
* You can check against the collection schema in your `src/content.config.*` file.
15051506
* See the [Content collections documentation](https://docs.astro.build/en/guides/content-collections/) for more information.
15061507
*/
15071508
export const InvalidContentEntryFrontmatterError = {
@@ -1528,7 +1529,7 @@ export const InvalidContentEntryFrontmatterError = {
15281529
* @description
15291530
* A content entry does not match its collection schema.
15301531
* Make sure that all required fields are present, and that all fields are of the correct type.
1531-
* You can check against the collection schema in your `src/content/config.*` file.
1532+
* You can check against the collection schema in your `src/content.config.*` file.
15321533
* See the [Content collections documentation](https://docs.astro.build/en/guides/content-collections/) for more information.
15331534
*/
15341535
export const InvalidContentEntryDataError = {
@@ -1553,7 +1554,7 @@ export const InvalidContentEntryDataError = {
15531554
* @description
15541555
* A content entry does not match its collection schema.
15551556
* Make sure that all required fields are present, and that all fields are of the correct type.
1556-
* You can check against the collection schema in your `src/content/config.*` file.
1557+
* You can check against the collection schema in your `src/content.config.*` file.
15571558
* See the [Content collections documentation](https://docs.astro.build/en/guides/content-collections/) for more information.
15581559
*/
15591560
export const ContentEntryDataError = {

packages/astro/src/core/sync/index.ts

+14-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { resolveConfig } from '../config/config.js';
2121
import { createNodeLogger } from '../config/logging.js';
2222
import { createSettings } from '../config/settings.js';
2323
import { createVite } from '../create-vite.js';
24-
import { collectErrorMetadata } from '../errors/dev/utils.js';
2524
import {
2625
AstroError,
2726
AstroErrorData,
@@ -31,7 +30,6 @@ import {
3130
isAstroError,
3231
} from '../errors/index.js';
3332
import type { Logger } from '../logger/core.js';
34-
import { formatErrorMessage } from '../messages.js';
3533
import { createRouteManifest } from '../routing/index.js';
3634
import { ensureProcessNodeEnv } from '../util.js';
3735

@@ -255,7 +253,20 @@ async function syncContentCollections(
255253
if (isAstroError(e)) {
256254
throw e;
257255
}
258-
const hint = AstroUserError.is(e) ? e.hint : AstroErrorData.GenerateContentTypesError.hint;
256+
let configFile
257+
try {
258+
const contentPaths = getContentPaths(settings.config, fs);
259+
if(contentPaths.config.exists) {
260+
const matches = /\/(src\/.+)/.exec(contentPaths.config.url.href);
261+
if (matches) {
262+
configFile = matches[1]
263+
}
264+
}
265+
} catch {
266+
// ignore
267+
}
268+
269+
const hint = AstroUserError.is(e) ? e.hint : AstroErrorData.GenerateContentTypesError.hint(configFile);
259270
throw new AstroError(
260271
{
261272
...AstroErrorData.GenerateContentTypesError,

packages/astro/src/types/public/config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1707,7 +1707,7 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
17071707
* When you are ready to remove this flag and migrate to the new Content Layer API for your legacy collections, you must define a collection for any directories in `src/content/` that you want to continue to use as a collection. It is sufficient to declare an empty collection, and Astro will implicitly generate an appropriate definition for your legacy collections:
17081708
*
17091709
* ```js
1710-
* // src/content/config.ts
1710+
* // src/content.config.ts
17111711
* import { defineCollection, z } from 'astro:content';
17121712
*
17131713
* const blog = defineCollection({ })

packages/astro/test/astro-sync.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ describe('astro sync', () => {
209209
assert.fail();
210210
}
211211
});
212-
it('Does not throw if a virtual module is imported in content/config.ts', async () => {
212+
it('Does not throw if a virtual module is imported in content.config.ts', async () => {
213213
try {
214214
await fixture.load('./fixtures/astro-env-content-collections/');
215215
fixture.clean();

packages/astro/test/content-layer.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ describe('Content Layer', () => {
285285
it('clears the store on new build if the config has changed', async () => {
286286
let newJson = devalue.parse(await fixture.readFile('/collections.json'));
287287
assert.equal(newJson.increment.data.lastValue, 1);
288-
await fixture.editFile('src/content/config.ts', (prev) => {
288+
await fixture.editFile('src/content.config.ts', (prev) => {
289289
return `${prev}\nexport const foo = 'bar';`;
290290
});
291291
await fixture.build();

packages/astro/test/fixtures/content-layer-rendering/src/content/config.ts packages/astro/test/fixtures/content-layer-rendering/src/content.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { glob } from 'astro/loaders';
44
const reptiles = defineCollection({
55
loader: glob({
66
pattern: '*.mdx',
7-
base: new URL('../../content-outside-src-mdx', import.meta.url),
7+
base: new URL('../content-outside-src-mdx', import.meta.url),
88
}),
99
schema: () =>
1010
z.object({

packages/astro/test/fixtures/content-layer/src/content/config.ts packages/astro/test/fixtures/content-layer/src/content.config.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { defineCollection, z, reference } from 'astro:content';
22
import { file, glob } from 'astro/loaders';
3-
import { loader } from '../loaders/post-loader.js';
3+
import { loader } from './loaders/post-loader.js';
44
import { parse as parseToml } from 'toml';
55

66
const blog = defineCollection({
@@ -141,7 +141,7 @@ const birds = defineCollection({
141141
});
142142

143143
// Absolute paths should also work
144-
const absoluteRoot = new URL('space', import.meta.url);
144+
const absoluteRoot = new URL('content/space', import.meta.url);
145145

146146
const spacecraft = defineCollection({
147147
loader: glob({ pattern: '*.md', base: absoluteRoot }),

packages/astro/test/units/content-collections/frontmatter.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe('frontmatter', () => {
1010
title: One
1111
---
1212
`,
13-
'/src/content/config.ts': `\
13+
'/src/content.config.ts': `\
1414
import { defineCollection, z } from 'astro:content';
1515
1616
const posts = defineCollection({

packages/astro/test/units/content-collections/get-entry-type.test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const fixtures = [
88
title: 'Without any underscore above the content directory tree',
99
contentPaths: {
1010
config: {
11-
url: new URL('src/content/config.ts', import.meta.url),
11+
url: new URL('src/content.config.ts', import.meta.url),
1212
exists: true,
1313
},
1414
contentDir: new URL('src/content/', import.meta.url),

packages/astro/test/units/dev/collections-mixed-content-errors.test.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ name: Ben
5757
# Ben
5858
`,
5959
'/src/content/authors/tony.json': `{ "name": "Tony" }`,
60-
'/src/content/config.ts': `\
60+
'/src/content.config.ts': `\
6161
import { z, defineCollection } from 'astro:content';
6262
6363
const authors = defineCollection({
@@ -85,7 +85,7 @@ title: Post
8585
# Post
8686
`,
8787
'/src/content/blog/post.yaml': `title: YAML Post`,
88-
'/src/content/config.ts': `\
88+
'/src/content.config.ts': `\
8989
import { z, defineCollection } from 'astro:content';
9090
9191
const blog = defineCollection({
@@ -128,7 +128,7 @@ export const collections = { banners };
128128
...baseFileTree,
129129
// Add placeholder to ensure directory exists
130130
'/src/content/i18n/_placeholder.txt': 'Need content here',
131-
'/src/content/config.ts': `\
131+
'/src/content.config.ts': `\
132132
import { z, defineCollection } from 'astro:content';
133133
134134
const i18n = defineCollection({

packages/astro/test/units/dev/collections-renderentry.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ describe('Content Collections - render()', () => {
166166
it('can be used in a slot', async () => {
167167
const fixture = await createFixture({
168168
...baseFileTree,
169-
'/src/content/config.ts': `
169+
'/src/content.config.ts': `
170170
import { z, defineCollection } from 'astro:content';
171171
172172
const blog = defineCollection({
@@ -233,7 +233,7 @@ describe('Content Collections - render()', () => {
233233
it('can be called from any js/ts file', async () => {
234234
const fixture = await createFixture({
235235
...baseFileTree,
236-
'/src/content/config.ts': `
236+
'/src/content.config.ts': `
237237
import { z, defineCollection } from 'astro:content';
238238
239239
const blog = defineCollection({

0 commit comments

Comments
 (0)