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

feat: support new location for content config #12475

Merged
merged 7 commits into from
Nov 21, 2024
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
7 changes: 7 additions & 0 deletions .changeset/thirty-clocks-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'astro': minor
---

Changes the default content config location from `src/content/config.*` to `src/content.config.*`.

The previous location is still supported, and is required if the `legacy.collections` flag is enabled.
14 changes: 12 additions & 2 deletions packages/astro/src/content/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,22 @@ type GlobResult = Record<string, LazyImport>;
type CollectionToEntryMap = Record<string, GlobResult>;
type GetEntryImport = (collection: string, lookupId: string) => Promise<LazyImport>;

export function getImporterFilename() {
// The 4th line in the stack trace should be the importer filename
const stackLine = new Error().stack?.split('\n')?.[3];
if (!stackLine) {
return null;
}
// Extract the relative path from the stack line
const match = /\/(src\/.*?):\d+:\d+/.exec(stackLine);
return match?.[1] ?? null;
}

export function defineCollection(config: any) {
if ('loader' in config) {
if (config.type && config.type !== CONTENT_LAYER_TYPE) {
throw new AstroUserError(
'Collections that use the Content Layer API must have a `loader` defined and no `type` set.',
"Check your collection definitions in `src/content/config.*`.'",
`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'}.`,
);
}
config.type = CONTENT_LAYER_TYPE;
Expand Down
12 changes: 10 additions & 2 deletions packages/astro/src/content/server-listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ export async function attachContentServerListeners({
settings,
}: ContentServerListenerParams) {
const contentPaths = getContentPaths(settings.config, fs);

if (fs.existsSync(contentPaths.contentDir)) {
if (!settings.config.legacy?.collections) {
const contentGenerator = await createContentTypesGenerator({
fs,
settings,
logger,
viteServer,
contentConfigObserver: globalContentConfigObserver,
});
await contentGenerator.init();
} else if (fs.existsSync(contentPaths.contentDir)) {
logger.debug(
'content',
`Watching ${cyan(
Expand Down
7 changes: 3 additions & 4 deletions packages/astro/src/content/types-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,12 @@ export async function createContentTypesGenerator({
async function init(): Promise<
{ typesGenerated: true } | { typesGenerated: false; reason: 'no-content-dir' }
> {
if (!fs.existsSync(contentPaths.contentDir)) {
return { typesGenerated: false, reason: 'no-content-dir' };
}

events.push({ name: 'add', entry: contentPaths.config.url });

if (settings.config.legacy.collections) {
if (!fs.existsSync(contentPaths.contentDir)) {
return { typesGenerated: false, reason: 'no-content-dir' };
}
const globResult = await glob('**', {
cwd: fileURLToPath(contentPaths.contentDir),
fs: {
Expand Down
22 changes: 14 additions & 8 deletions packages/astro/src/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ export async function autogenerateCollections({
}) as any,
};
}
if (!usesContentLayer) {
if (!usesContentLayer && fs.existsSync(contentDir)) {
// If the user hasn't defined any collections using the content layer, we'll try and help out by checking for
// any orphaned folders in the content directory and creating collections for them.
const orphanedCollections = [];
Expand All @@ -623,7 +623,7 @@ export async function autogenerateCollections({
console.warn(
`
Auto-generating collections for folders in "src/content/" that are not defined as collections.
This is deprecated, so you should define these collections yourself in "src/content/config.ts".
This is deprecated, so you should define these collections yourself in "src/content.config.ts".
The following collections have been auto-generated: ${orphanedCollections
.map((name) => green(name))
.join(', ')}\n`,
Expand Down Expand Up @@ -715,10 +715,10 @@ export type ContentPaths = {
};

export function getContentPaths(
{ srcDir }: Pick<AstroConfig, 'root' | 'srcDir'>,
{ srcDir, legacy }: Pick<AstroConfig, 'root' | 'srcDir' | 'legacy'>,
fs: typeof fsMod = fsMod,
): ContentPaths {
const configStats = search(fs, srcDir);
const configStats = search(fs, srcDir, legacy?.collections);
const pkgBase = new URL('../../', import.meta.url);
return {
contentDir: new URL('./content/', srcDir),
Expand All @@ -728,10 +728,16 @@ export function getContentPaths(
config: configStats,
};
}
function search(fs: typeof fsMod, srcDir: URL) {
const paths = ['config.mjs', 'config.js', 'config.mts', 'config.ts'].map(
(p) => new URL(`./content/${p}`, srcDir),
);
function search(fs: typeof fsMod, srcDir: URL, legacy?: boolean) {
const paths = [
...(legacy
? []
: ['content.config.mjs', 'content.config.js', 'content.config.mts', 'content.config.ts']),
'content/config.mjs',
'content/config.js',
'content/config.mts',
'content/config.ts',
].map((p) => new URL(`./${p}`, srcDir));
for (const file of paths) {
if (fs.existsSync(file)) {
return { exists: true, url: file };
Expand Down
11 changes: 6 additions & 5 deletions packages/astro/src/core/errors/errors-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,8 @@ export const GenerateContentTypesError = {
title: 'Failed to generate content types.',
message: (errorMessage: string) =>
`\`astro sync\` command failed to generate content collection types: ${errorMessage}`,
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.',
hint: (fileName?: string) =>
`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.`,
} satisfies ErrorData;
/**
* @docs
Expand All @@ -1458,7 +1459,7 @@ export const GenerateContentTypesError = {
* @docs
* @description
* Astro encountered an unknown error loading your content collections.
* This can be caused by certain errors inside your `src/content/config.ts` file or some internal errors.
* This can be caused by certain errors inside your `src/content.config.ts` file or some internal errors.
*
* If you can reliably cause this error to happen, we'd appreciate if you could [open an issue](https://astro.build/issues/)
*/
Expand Down Expand Up @@ -1501,7 +1502,7 @@ export const GetEntryDeprecationError = {
* @description
* A Markdown or MDX entry does not match its collection schema.
* Make sure that all required fields are present, and that all fields are of the correct type.
* You can check against the collection schema in your `src/content/config.*` file.
* You can check against the collection schema in your `src/content.config.*` file.
* See the [Content collections documentation](https://docs.astro.build/en/guides/content-collections/) for more information.
*/
export const InvalidContentEntryFrontmatterError = {
Expand All @@ -1528,7 +1529,7 @@ export const InvalidContentEntryFrontmatterError = {
* @description
* A content entry does not match its collection schema.
* Make sure that all required fields are present, and that all fields are of the correct type.
* You can check against the collection schema in your `src/content/config.*` file.
* You can check against the collection schema in your `src/content.config.*` file.
* See the [Content collections documentation](https://docs.astro.build/en/guides/content-collections/) for more information.
*/
export const InvalidContentEntryDataError = {
Expand All @@ -1553,7 +1554,7 @@ export const InvalidContentEntryDataError = {
* @description
* A content entry does not match its collection schema.
* Make sure that all required fields are present, and that all fields are of the correct type.
* You can check against the collection schema in your `src/content/config.*` file.
* You can check against the collection schema in your `src/content.config.*` file.
* See the [Content collections documentation](https://docs.astro.build/en/guides/content-collections/) for more information.
*/
export const ContentEntryDataError = {
Expand Down
17 changes: 14 additions & 3 deletions packages/astro/src/core/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { resolveConfig } from '../config/config.js';
import { createNodeLogger } from '../config/logging.js';
import { createSettings } from '../config/settings.js';
import { createVite } from '../create-vite.js';
import { collectErrorMetadata } from '../errors/dev/utils.js';
import {
AstroError,
AstroErrorData,
Expand All @@ -31,7 +30,6 @@ import {
isAstroError,
} from '../errors/index.js';
import type { Logger } from '../logger/core.js';
import { formatErrorMessage } from '../messages.js';
import { createRouteManifest } from '../routing/index.js';
import { ensureProcessNodeEnv } from '../util.js';

Expand Down Expand Up @@ -255,7 +253,20 @@ async function syncContentCollections(
if (isAstroError(e)) {
throw e;
}
const hint = AstroUserError.is(e) ? e.hint : AstroErrorData.GenerateContentTypesError.hint;
let configFile
try {
const contentPaths = getContentPaths(settings.config, fs);
if(contentPaths.config.exists) {
const matches = /\/(src\/.+)/.exec(contentPaths.config.url.href);
if (matches) {
configFile = matches[1]
}
}
} catch {
// ignore
}

const hint = AstroUserError.is(e) ? e.hint : AstroErrorData.GenerateContentTypesError.hint(configFile);
throw new AstroError(
{
...AstroErrorData.GenerateContentTypesError,
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1706,7 +1706,7 @@ export interface ViteUserConfig extends OriginalViteUserConfig {
* 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:
*
* ```js
* // src/content/config.ts
* // src/content.config.ts
* import { defineCollection, z } from 'astro:content';
*
* const blog = defineCollection({ })
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/test/astro-sync.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ describe('astro sync', () => {
assert.fail();
}
});
it('Does not throw if a virtual module is imported in content/config.ts', async () => {
it('Does not throw if a virtual module is imported in content.config.ts', async () => {
try {
await fixture.load('./fixtures/astro-env-content-collections/');
fixture.clean();
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/test/content-layer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ describe('Content Layer', () => {
it('clears the store on new build if the config has changed', async () => {
let newJson = devalue.parse(await fixture.readFile('/collections.json'));
assert.equal(newJson.increment.data.lastValue, 1);
await fixture.editFile('src/content/config.ts', (prev) => {
await fixture.editFile('src/content.config.ts', (prev) => {
return `${prev}\nexport const foo = 'bar';`;
});
await fixture.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { glob } from 'astro/loaders';
const reptiles = defineCollection({
loader: glob({
pattern: '*.mdx',
base: new URL('../../content-outside-src-mdx', import.meta.url),
base: new URL('../content-outside-src-mdx', import.meta.url),
}),
schema: () =>
z.object({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineCollection, z, reference } from 'astro:content';
import { file, glob } from 'astro/loaders';
import { loader } from '../loaders/post-loader.js';
import { loader } from './loaders/post-loader.js';
import { parse as parseToml } from 'toml';

const blog = defineCollection({
Expand Down Expand Up @@ -141,7 +141,7 @@ const birds = defineCollection({
});

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

const spacecraft = defineCollection({
loader: glob({ pattern: '*.md', base: absoluteRoot }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('frontmatter', () => {
title: One
---
`,
'/src/content/config.ts': `\
'/src/content.config.ts': `\
import { defineCollection, z } from 'astro:content';

const posts = defineCollection({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const fixtures = [
title: 'Without any underscore above the content directory tree',
contentPaths: {
config: {
url: new URL('src/content/config.ts', import.meta.url),
url: new URL('src/content.config.ts', import.meta.url),
exists: true,
},
contentDir: new URL('src/content/', import.meta.url),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ name: Ben
# Ben
`,
'/src/content/authors/tony.json': `{ "name": "Tony" }`,
'/src/content/config.ts': `\
'/src/content.config.ts': `\
import { z, defineCollection } from 'astro:content';

const authors = defineCollection({
Expand Down Expand Up @@ -85,7 +85,7 @@ title: Post
# Post
`,
'/src/content/blog/post.yaml': `title: YAML Post`,
'/src/content/config.ts': `\
'/src/content.config.ts': `\
import { z, defineCollection } from 'astro:content';

const blog = defineCollection({
Expand Down Expand Up @@ -128,7 +128,7 @@ export const collections = { banners };
...baseFileTree,
// Add placeholder to ensure directory exists
'/src/content/i18n/_placeholder.txt': 'Need content here',
'/src/content/config.ts': `\
'/src/content.config.ts': `\
import { z, defineCollection } from 'astro:content';

const i18n = defineCollection({
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/test/units/dev/collections-renderentry.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ describe('Content Collections - render()', () => {
it('can be used in a slot', async () => {
const fixture = await createFixture({
...baseFileTree,
'/src/content/config.ts': `
'/src/content.config.ts': `
import { z, defineCollection } from 'astro:content';

const blog = defineCollection({
Expand Down Expand Up @@ -233,7 +233,7 @@ describe('Content Collections - render()', () => {
it('can be called from any js/ts file', async () => {
const fixture = await createFixture({
...baseFileTree,
'/src/content/config.ts': `
'/src/content.config.ts': `
import { z, defineCollection } from 'astro:content';

const blog = defineCollection({
Expand Down
Loading