From 0665caadb667e140f7b7c518c45cc18510ade880 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Mon, 15 Sep 2025 16:27:25 +0200 Subject: [PATCH 01/36] chore: changeset --- .changeset/spotty-toes-know.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/spotty-toes-know.md diff --git a/.changeset/spotty-toes-know.md b/.changeset/spotty-toes-know.md new file mode 100644 index 000000000000..c2e96e46a35f --- /dev/null +++ b/.changeset/spotty-toes-know.md @@ -0,0 +1,5 @@ +--- +'astro': major +--- + +TODO: From 845172faabb093a0dd01728181760a0729a9e8cd Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Mon, 15 Sep 2025 17:10:04 +0200 Subject: [PATCH 02/36] feat: enable prerelease mode (#14384) --- .changeset/config.json | 2 +- .changeset/pre.json | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .changeset/pre.json diff --git a/.changeset/config.json b/.changeset/config.json index be86265817aa..902828cfe8b7 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -4,7 +4,7 @@ "commit": false, "linked": [], "access": "public", - "baseBranch": "origin/main", + "baseBranch": "origin/next", "updateInternalDependencies": "patch", "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { "onlyUpdatePeerDependentsWhenOutOfRange": true diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000000..f67a865e0787 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,31 @@ +{ + "mode": "pre", + "tag": "next", + "initialVersions": { + "astro": "5.13.7", + "@astrojs/prism": "3.3.0", + "@astrojs/rss": "4.0.12", + "create-astro": "4.13.1", + "@astrojs/db": "0.17.2", + "@astrojs/alpinejs": "0.4.9", + "@astrojs/cloudflare": "12.6.8", + "@astrojs/markdoc": "0.15.6", + "@astrojs/mdx": "4.3.5", + "@astrojs/netlify": "6.5.10", + "@astrojs/node": "9.4.3", + "@astrojs/partytown": "2.1.4", + "@astrojs/preact": "4.1.1", + "@astrojs/react": "4.3.1", + "@astrojs/sitemap": "3.5.1", + "@astrojs/solid-js": "5.1.1", + "@astrojs/svelte": "7.1.1", + "@astrojs/vercel": "8.2.7", + "@astrojs/vue": "5.1.1", + "@astrojs/internal-helpers": "0.7.2", + "@astrojs/markdown-remark": "6.3.6", + "@astrojs/telemetry": "3.3.0", + "@astrojs/underscore-redirects": "1.0.0", + "@astrojs/upgrade": "0.6.2" + }, + "changesets": [] +} From 0758881b7798649386fd0c95a85f9bd30af26a1e Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Fri, 19 Sep 2025 14:41:06 +0200 Subject: [PATCH 03/36] Delete .changeset/spotty-toes-know.md --- .changeset/spotty-toes-know.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .changeset/spotty-toes-know.md diff --git a/.changeset/spotty-toes-know.md b/.changeset/spotty-toes-know.md deleted file mode 100644 index c2e96e46a35f..000000000000 --- a/.changeset/spotty-toes-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': major ---- - -TODO: From c69c7de1ffeff29f919d97c262f245927556f875 Mon Sep 17 00:00:00 2001 From: ellielok Date: Fri, 19 Sep 2025 23:11:36 +1000 Subject: [PATCH 04/36] Remove deprecated ViewTransitions component (#14400) Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/rich-horses-begin.md | 26 +++++++++++++++++++ packages/astro/client.d.ts | 4 --- .../transitions/vite-plugin-transitions.ts | 5 +--- 3 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 .changeset/rich-horses-begin.md diff --git a/.changeset/rich-horses-begin.md b/.changeset/rich-horses-begin.md new file mode 100644 index 000000000000..580a90ef933a --- /dev/null +++ b/.changeset/rich-horses-begin.md @@ -0,0 +1,26 @@ +--- +'astro': major +--- + +Removes the deprecated `` component + +In Astro 5.0, the `` component was renamed to `` to clarify the role of the component. The new name makes it more clear that the features you get from Astro's `` routing component are slightly different from the native CSS-based MPA router. However, a deprecated version of the `` component still existed and may have functioned in Astro 5.x. + +Astro 6.0 removes the `` component entirely and it can no longer be used in your project. Update to the `` component to continue to use these features. + +#### What should I do? + +Replace all occurrences of the `ViewTransitions` import and component with `ClientRouter`: + +```diff +// src/layouts/MyLayout.astro" +- import { ViewTransitions } from 'astro:transitions'; ++ import { ClientRouter } from 'astro:transitions'; + + + ... +- ++ + + +``` diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts index 24a8585de51f..ca9078d6a675 100644 --- a/packages/astro/client.d.ts +++ b/packages/astro/client.d.ts @@ -122,10 +122,6 @@ declare module 'astro:transitions' { export const createAnimationScope: TransitionModule['createAnimationScope']; type ClientRouterModule = typeof import('./components/ClientRouter.astro'); - /** - * @deprecated The ViewTransitions component has been renamed to ClientRouter - */ - export const ViewTransitions: ClientRouterModule['default']; export const ClientRouter: ClientRouterModule['default']; } diff --git a/packages/astro/src/transitions/vite-plugin-transitions.ts b/packages/astro/src/transitions/vite-plugin-transitions.ts index d72eb0dc9417..c2ec27e77ba4 100644 --- a/packages/astro/src/transitions/vite-plugin-transitions.ts +++ b/packages/astro/src/transitions/vite-plugin-transitions.ts @@ -30,10 +30,7 @@ export default function astroTransitions({ settings }: { settings: AstroSettings return { code: ` export * from "astro/virtual-modules/transitions.js"; - export { - default as ViewTransitions, - default as ClientRouter - } from "astro/components/ClientRouter.astro"; + export { default as ClientRouter } from "astro/components/ClientRouter.astro"; `, }; } From 2570c4bc26896f28e7e320f3da62ff9ba1b2d49d Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Tue, 23 Sep 2025 12:06:32 +0200 Subject: [PATCH 05/36] feat!: clean up internal symbols usage (#14420) --- packages/astro/src/core/middleware/index.ts | 15 +++++---------- packages/astro/src/core/render-context.ts | 10 +--------- .../deps/test-adapter/server.js | 12 +++++------- packages/astro/test/test-adapter.js | 9 +++++++-- .../integrations/cloudflare/src/utils/handler.ts | 3 +-- packages/integrations/netlify/src/index.ts | 7 ++++--- packages/integrations/netlify/src/ssr-function.ts | 9 +++++---- 7 files changed, 28 insertions(+), 37 deletions(-) diff --git a/packages/astro/src/core/middleware/index.ts b/packages/astro/src/core/middleware/index.ts index 19a89828fe2d..904774e5552b 100644 --- a/packages/astro/src/core/middleware/index.ts +++ b/packages/astro/src/core/middleware/index.ts @@ -6,7 +6,7 @@ import { } from '../../i18n/utils.js'; import type { MiddlewareHandler, Params, RewritePayload } from '../../types/public/common.js'; import type { APIContext, AstroSharedContextCsp } from '../../types/public/context.js'; -import { ASTRO_VERSION, clientLocalsSymbol } from '../constants.js'; +import { ASTRO_VERSION } from '../constants.js'; import { AstroCookies } from '../cookies/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; import { getClientIpAddress } from '../routing/request.js'; @@ -43,7 +43,7 @@ export type CreateContext = { /** * Initial value of the locals */ - locals: App.Locals; + locals?: App.Locals; }; /** @@ -54,7 +54,7 @@ function createContext({ params = {}, userDefinedLocales = [], defaultLocale = '', - locals, + locals = {}, }: CreateContext): APIContext { let preferredLocale: string | undefined = undefined; let preferredLocaleList: string[] | undefined = undefined; @@ -110,15 +110,10 @@ function createContext({ return clientIpAddress; }, get locals() { - // TODO: deprecate this usage. This is used only by the edge middleware for now, so its usage should be basically none. - let _locals = locals ?? Reflect.get(request, clientLocalsSymbol); - if (locals === undefined) { - _locals = {}; - } - if (typeof _locals !== 'object') { + if (typeof locals !== 'object') { throw new AstroError(AstroErrorData.LocalsNotAnObject); } - return _locals; + return locals; }, set locals(_) { throw new AstroError(AstroErrorData.LocalsReassigned); diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index b42c0fdbdf2b..3fc04db5803a 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -22,7 +22,6 @@ import type { RouteData, SSRResult } from '../types/public/internal.js'; import type { SSRActions } from './app/types.js'; import { ASTRO_VERSION, - clientAddressSymbol, REROUTE_DIRECTIVE_HEADER, REWRITE_DIRECTIVE_HEADER_KEY, REWRITE_DIRECTIVE_HEADER_VALUE, @@ -751,7 +750,7 @@ export class RenderContext { } getClientAddress() { - const { pipeline, request, routeData, clientAddress } = this; + const { pipeline, routeData, clientAddress } = this; if (routeData.prerender) { throw new AstroError({ @@ -764,13 +763,6 @@ export class RenderContext { return clientAddress; } - // TODO: Legacy, should not need to get here. - // Some adapters set this symbol so we can't remove support yet. - // Adapters should be updated to provide it via RenderOptions instead. - if (clientAddressSymbol in request) { - return Reflect.get(request, clientAddressSymbol) as string; - } - if (pipeline.adapterName) { throw new AstroError({ ...AstroErrorData.ClientAddressNotAvailable, diff --git a/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js index 0515aaaa1657..0aba9ad5610f 100644 --- a/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js +++ b/packages/astro/test/fixtures/ssr-prerender-chunks/deps/test-adapter/server.js @@ -26,12 +26,6 @@ import { App } from 'astro/app'; } } - Reflect.set( - request, - Symbol.for('astro.clientAddress'), - request.headers.get('cf-connecting-ip') - ); - const locals = { runtime: { env: env, @@ -44,7 +38,11 @@ import { App } from 'astro/app'; }, }; - const response = await app.render(request, { routeData, locals }); + const response = await app.render(request, { + routeData, + locals, + clientAddress: request.headers.get('cf-connecting-ip'), + }); if (app.setCookieHeaders) { for (const setCookieHeader of app.setCookieHeaders(response)) { diff --git a/packages/astro/test/test-adapter.js b/packages/astro/test/test-adapter.js index c5f48d17646b..6189b3623971 100644 --- a/packages/astro/test/test-adapter.js +++ b/packages/astro/test/test-adapter.js @@ -81,12 +81,17 @@ export default function ({ return new Response(data); } + return super.render(request, { + routeData, + locals, + addCookieHeader, + prerenderedErrorPageFetch, ${ provideAddress - ? `request[Symbol.for('astro.clientAddress')] = clientAddress ?? '0.0.0.0';` + ? `clientAddress: clientAddress ?? '0.0.0.0',` : '' } - return super.render(request, { routeData, locals, addCookieHeader, prerenderedErrorPageFetch }); + }); } } diff --git a/packages/integrations/cloudflare/src/utils/handler.ts b/packages/integrations/cloudflare/src/utils/handler.ts index 90e2d020745f..d1c40df542f2 100644 --- a/packages/integrations/cloudflare/src/utils/handler.ts +++ b/packages/integrations/cloudflare/src/utils/handler.ts @@ -64,8 +64,6 @@ export async function handle( } } - Reflect.set(request, Symbol.for('astro.clientAddress'), request.headers.get('cf-connecting-ip')); - const locals: Runtime = { runtime: { env: env, @@ -92,6 +90,7 @@ export async function handle( prerenderedErrorPageFetch: async (url) => { return env.ASSETS.fetch(url.replace(/\.html$/, '')); }, + clientAddress: request.headers.get('cf-connecting-ip') ?? undefined, }, ); diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts index 77d24f6edcfb..8105361055e7 100644 --- a/packages/integrations/netlify/src/index.ts +++ b/packages/integrations/netlify/src/index.ts @@ -698,10 +698,11 @@ export default function netlifyIntegration( if (existingSessionModule) { server.moduleGraph.invalidateModule(existingSessionModule); } + + const clientLocalsSymbol = Symbol.for('astro.locals'); + server.middlewares.use((req, _res, next) => { - const locals = Symbol.for('astro.locals'); - Reflect.set(req, locals, { - ...Reflect.get(req, locals), + Reflect.set(req, clientLocalsSymbol, { netlify: { context: getLocalDevNetlifyContext(req) }, }); next(); diff --git a/packages/integrations/netlify/src/ssr-function.ts b/packages/integrations/netlify/src/ssr-function.ts index 5ea2e97f1142..bc7d712f5ad1 100644 --- a/packages/integrations/netlify/src/ssr-function.ts +++ b/packages/integrations/netlify/src/ssr-function.ts @@ -12,8 +12,6 @@ export interface Args { middlewareSecret: string; } -const clientAddressSymbol = Symbol.for('astro.clientAddress'); - export const createExports = (manifest: SSRManifest, { middlewareSecret }: Args) => { const app = new App(manifest); @@ -30,7 +28,6 @@ export const createExports = (manifest: SSRManifest, { middlewareSecret }: Args) }); } - Reflect.set(request, clientAddressSymbol, context.ip); let locals: Record = {}; const astroLocalsHeader = request.headers.get('x-astro-locals'); @@ -46,7 +43,11 @@ export const createExports = (manifest: SSRManifest, { middlewareSecret }: Args) locals.netlify = { context }; - const response = await app.render(request, { routeData, locals }); + const response = await app.render(request, { + routeData, + locals, + clientAddress: context.ip + }); if (app.setCookieHeaders) { for (const setCookieHeader of app.setCookieHeaders(response)) { From 3bda3ce4edcb1bd1349890c6ed8110f05954c791 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Tue, 23 Sep 2025 15:33:34 +0100 Subject: [PATCH 06/36] fix: remove legacy content collections (#14407) * wip * wip: remove legacy content collection support * Fix types * Update tests in integrations * Format * Legacy type * Fix default * Update examples * Better errors for collections without loaders * Sooo many fixture updates * Test fixes * Formatting and tests * Fix vitest test * Update image data tests * Update more tests * More test fixes * Test fixes * Changeset * Update errors --- .changeset/young-banks-camp.md | 18 + benchmark/make-project/markdown-cc1.js | 24 +- benchmark/make-project/markdown-cc2.js | 2 +- benchmark/make-project/mdx-cc1.js | 24 +- benchmark/make-project/mdx-cc2.js | 2 +- benchmark/make-project/memory-default.js | 24 +- examples/with-markdoc/src/content.config.ts | 5 +- examples/with-markdoc/src/pages/index.astro | 4 +- .../{content/config.ts => content.config.ts} | 3 +- .../{content/config.ts => content.config.ts} | 3 +- .../content-collections/src/content.config.ts | 9 + .../content-collections/src/content/config.ts | 6 - .../content-collections/src/pages/index.astro | 6 +- .../fixtures/md/src/ContentRenderer.astro | 3 +- .../fixtures/mdoc/src/ContentRenderer.astro | 3 +- .../fixtures/mdx/src/ContentRenderer.astro | 3 +- packages/astro/src/config/index.ts | 13 +- .../src/config/vite-plugin-content-listen.ts | 41 -- packages/astro/src/content/config.ts | 40 +- packages/astro/src/content/loaders/glob.ts | 41 +- packages/astro/src/content/runtime.ts | 333 +++---------- .../astro/src/content/server-listeners.ts | 125 +---- packages/astro/src/content/types-generator.ts | 121 +---- packages/astro/src/content/utils.ts | 216 ++------- .../vite-plugin-content-virtual-mod.ts | 170 +------ .../astro/src/core/config/schemas/base.ts | 10 +- packages/astro/src/core/errors/errors-data.ts | 61 ++- packages/astro/src/core/sync/index.ts | 17 +- packages/astro/src/types/public/config.ts | 43 +- .../astro/src/virtual-modules/live-config.ts | 2 - packages/astro/templates/content/module.mjs | 71 +-- packages/astro/templates/content/types.d.ts | 79 +-- packages/astro/test/astro-sync.test.js | 11 +- packages/astro/test/config-vite.test.js | 12 +- .../content-collection-references.test.js | 8 +- .../test/content-collections-render.test.js | 23 +- .../astro/test/content-collections.test.js | 89 +--- packages/astro/test/core-image.test.js | 15 +- .../astro/test/css-inline-stylesheets.test.js | 5 +- .../astro-assets-dir/src/content.config.ts | 2 + .../astro-assets-prefix/src/content.config.ts | 2 + .../fixtures/astro-mode/src/content.config.ts | 50 ++ .../src/content.config.ts | 4 + .../src/content.config.ts | 6 +- .../src/pages/welcome-data.json.js | 3 +- .../src/pages/welcome.astro | 2 +- .../src/content.config.ts | 2 + .../src/pages/docs.astro | 6 +- .../src/content.config.ts | 3 +- .../src/content.config.ts | 4 +- .../{content/config.ts => content.config.ts} | 2 + .../src/pages/index.astro | 4 +- .../src/content.config.ts | 3 +- .../src/content.config.ts | 2 + .../src/pages/docs.astro | 6 +- .../src/content/config.mjs | 2 + .../src/pages/index.astro | 4 +- .../package.json | 8 - .../src/content/blog/introduction.md | 5 - .../src/content/config.mts | 11 - .../src/pages/index.astro | 5 - .../content-collections/src/content.config.ts | 29 +- .../src/content/without-config/columbia.md | 15 - .../src/content/without-config/enterprise.md | 14 - .../promo/_launch-week-styles.css | 3 - .../without-config/promo/launch week.mdx | 14 - .../src/pages/collections.json.js | 14 +- .../src/pages/entries.json.js | 13 +- .../src/pages/with-scripts/[...slug].astro | 6 +- .../fixtures/content-collections/src/utils.js | 8 - .../src/content.config.ts | 2 +- .../content-intellisense/src/utils.js | 8 - .../{content/config.ts => content.config.ts} | 4 +- .../pages/deprecated-getdataentrybyid.astro | 18 + .../src/pages/deprecated-getentrybyslug.astro | 18 + .../src/pages/posts/[...slug].astro | 11 +- .../src/content.config.ts | 2 + .../src/pages/posts/[...slug].astro | 6 +- .../test/fixtures/content/astro.config.mjs | 4 - .../src/content.config.ts} | 4 +- .../src/content/blog/with-layout-prop.md | 9 - .../pages/launch-week-component-scripts.astro | 6 +- .../pages/launch-week-components-export.astro | 6 +- .../content/src/pages/launch-week.astro | 7 +- .../content/src/pages/with-layout-prop.astro | 9 - .../core-image-base/src/content.config.ts | 2 + .../src/pages/blog/[...slug].astro | 6 +- .../core-image-deletion/src/content.config.ts | 3 +- .../src/pages/blog/[slug].astro | 6 +- .../core-image-errors/astro.config.mjs | 4 - .../{content/config.ts => content.config.ts} | 7 +- .../src/pages/blog/[...slug].astro | 6 +- .../core-image-ssg/src/content.config.ts | 2 + .../src/pages/blog/[...slug].astro | 6 +- .../src/content.config.ts} | 4 +- .../src/pages/blog/[...slug].astro | 6 +- .../test/fixtures/core-image/astro.config.mjs | 4 - .../{content/config.ts => content.config.ts} | 5 +- .../core-image/src/content/blog/one.md | 3 +- .../core-image/src/pages/blog/[...slug].astro | 10 +- .../css-inline-stylesheets-2/astro.config.mjs | 5 +- .../src/content.config.ts | 13 + .../src/content/en/endeavour.md | 1 - .../src/pages}/endeavour.md | 1 + .../src/pages/index.astro | 9 +- .../css-inline-stylesheets-3/astro.config.mjs | 5 +- .../src/content/en/endeavour.md | 3 +- .../src/pages/index.astro | 6 +- .../css-inline-stylesheets/astro.config.mjs | 4 - .../src/content.config.ts | 13 + .../src/content/en/endeavour.md | 1 - .../src/pages}/endeavour.md | 1 + .../src/pages/index.astro | 9 +- .../src/content.config.ts | 11 +- .../src/pages/translations/by-id.json.js | 4 +- .../data-collections/src/content.config.ts | 16 +- .../src/pages/translations/by-id.json.js | 4 +- .../astro.config.mjs | 13 - .../legacy-content-collections/package.json | 9 - .../src/assets/the-future.jpg | Bin 22792 -> 0 bytes .../src/components/ScriptCompA.astro | 1 - .../src/components/ScriptCompB.astro | 1 - .../src/content/config.ts | 62 --- .../src/content/with-custom-slugs/one.md | 5 - .../src/content/with-custom-slugs/three.md | 5 - .../src/content/with-custom-slugs/two.md | 5 - .../src/content/with-data/one.json | 3 - .../src/content/with-data/three.json | 3 - .../src/content/with-data/two.json | 3 - .../src/content/with-schema-config/_ignore.md | 0 .../with-schema-config/_ignore/file.md | 0 .../with-schema-config/_ignore/ignore/file.md | 0 .../src/content/with-schema-config/four%.md | 8 - .../src/content/with-schema-config/one.md | 8 - .../src/content/with-schema-config/three.md | 8 - .../src/content/with-schema-config/two.md | 8 - .../src/content/with-scripts/one.mdx | 7 - .../src/content/with-symlinked-content | 1 - .../src/content/with-symlinked-data | 1 - .../content/with-union-schema/newsletter.md | 6 - .../src/content/with-union-schema/post.md | 7 - .../src/content/without-config/columbia.md | 15 - .../src/content/without-config/enterprise.md | 14 - .../promo/_launch-week-styles.css | 3 - .../without-config/promo/launch week.mdx | 14 - .../src/pages/collections.json.js | 25 - .../src/pages/entries.json.js | 21 - .../src/pages/index.astro | 24 - .../src/pages/propagation.astro | 22 - .../src/pages/with-scripts/[...slug].astro | 21 - .../legacy-content-collections/src/utils.js | 8 - .../content-collection/first.md | 6 - .../content-collection/second.md | 6 - .../content-collection/third.md | 6 - .../data-collection/welcome.json | 4 - .../legacy-data-collections/astro.config.mjs | 9 - .../legacy-data-collections/package.json | 15 - .../authors-without-config/Ben Holmes.yml | 2 - .../authors-without-config/Fred K Schott.yml | 2 - .../authors-without-config/Nate Moore.yml | 2 - .../src/content/config.ts | 20 - .../src/content/docs/example.md | 3 - .../src/content/i18n/en.json | 6 - .../src/content/i18n/es.json | 6 - .../src/content/i18n/fr.yaml | 3 - .../src/pages/authors/[id].json.js | 18 - .../src/pages/authors/all.json.js | 6 - .../src/pages/translations/[lang].json.js | 18 - .../src/pages/translations/all.json.js | 6 - .../src/pages/translations/by-id.json.js | 6 - .../test/legacy-content-collections.test.js | 456 ------------------ .../test/legacy-data-collections.test.js | 159 ------ .../content-collections/frontmatter.test.js | 2 + .../get-entry-type.test.js | 2 +- .../collections-mixed-content-errors.test.js | 147 ------ .../units/dev/collections-renderentry.test.js | 298 ------------ packages/astro/test/vitest.test.js | 17 +- .../{content/config.ts => content.config.ts} | 2 + .../src/pages/blog/[...slug].astro | 4 +- .../markdoc/test/content-collections.test.js | 9 +- .../{content/config.ts => content.config.ts} | 2 + .../src/pages/collection.json.js | 3 +- .../src/pages/entry.json.js | 7 +- .../fixtures/content-collections/utils.js | 8 - .../headings-custom/src/content.config.ts | 10 + .../headings-custom/src/content/config.ts | 7 - .../headings-custom/src/pages/[slug].astro | 6 +- .../fixtures/headings/src/content.config.ts | 10 + .../fixtures/headings/src/content/config.ts | 7 - .../fixtures/headings/src/pages/[slug].astro | 6 +- .../image-assets-custom/src/content.config.ts | 10 + .../image-assets-custom/src/content/config.ts | 7 - .../image-assets-custom/src/pages/index.astro | 6 +- .../image-assets/src/content.config.ts | 10 + .../image-assets/src/content/config.ts | 7 - .../image-assets/src/pages/index.astro | 6 +- .../propagated-assets/src/content.config.ts | 8 + .../propagated-assets/src/pages/[slug].astro | 6 +- .../render with-space/src/content.config.ts | 10 + .../render with-space/src/content/config.ts | 7 - .../render with-space/src/pages/index.astro | 6 +- .../render-html/src/content.config.ts | 10 + .../render-html/src/content/config.ts | 7 - .../render-html/src/pages/[slug].astro | 8 +- .../render-null/src/content.config.ts | 10 + .../render-null/src/content/config.ts | 7 - .../render-null/src/pages/index.astro | 6 +- .../render-partials/src/content.config.ts | 10 + .../render-partials/src/content/config.ts | 7 - .../render-partials/src/pages/index.astro | 6 +- .../render-simple/src/content.config.ts | 10 + .../render-simple/src/content/config.ts | 7 - .../render-simple/src/pages/index.astro | 6 +- .../render-typographer/src/content.config.ts | 10 + .../render-typographer/src/content/config.ts | 7 - .../render-typographer/src/pages/index.astro | 6 +- .../src/content.config.ts | 10 + .../src/content/config.ts | 7 - .../src/pages/index.astro | 6 +- .../render-with-config/src/content.config.ts | 10 + .../render-with-config/src/content/config.ts | 7 - .../render-with-config/src/pages/index.astro | 6 +- .../src/content.config.ts | 10 + .../src/content/config.ts | 7 - .../src/pages/index.astro | 6 +- .../src/content.config.ts | 10 + .../src/content/config.ts | 7 - .../src/pages/index.astro | 6 +- .../{content/config.ts => content.config.ts} | 2 + .../variables/src/content/blog/entry.mdoc | 1 - .../fixtures/variables/src/pages/index.astro | 6 +- .../markdoc/test/variables.test.js | 6 +- .../{content/config.ts => content.config.ts} | 0 .../fixtures/mdx-images/src/content.config.ts | 8 + .../fixtures/mdx-images/src/content/config.ts | 5 - .../{content/config.js => content.config.js} | 3 +- .../src/pages/broken.astro | 4 +- pnpm-lock.yaml | 21 - 238 files changed, 936 insertions(+), 3280 deletions(-) create mode 100644 .changeset/young-banks-camp.md rename packages/astro/e2e/fixtures/actions-blog/src/{content/config.ts => content.config.ts} (77%) rename packages/astro/e2e/fixtures/actions-react-19/src/{content/config.ts => content.config.ts} (77%) create mode 100644 packages/astro/e2e/fixtures/content-collections/src/content.config.ts delete mode 100644 packages/astro/e2e/fixtures/content-collections/src/content/config.ts delete mode 100644 packages/astro/src/config/vite-plugin-content-listen.ts create mode 100644 packages/astro/test/fixtures/astro-mode/src/content.config.ts rename packages/astro/test/fixtures/content-collections-empty-md-file/src/{content/config.ts => content.config.ts} (61%) delete mode 100644 packages/astro/test/fixtures/content-collections-with-config-mts/package.json delete mode 100644 packages/astro/test/fixtures/content-collections-with-config-mts/src/content/blog/introduction.md delete mode 100644 packages/astro/test/fixtures/content-collections-with-config-mts/src/content/config.mts delete mode 100644 packages/astro/test/fixtures/content-collections-with-config-mts/src/pages/index.astro delete mode 100644 packages/astro/test/fixtures/content-collections/src/content/without-config/columbia.md delete mode 100644 packages/astro/test/fixtures/content-collections/src/content/without-config/enterprise.md delete mode 100644 packages/astro/test/fixtures/content-collections/src/content/without-config/promo/_launch-week-styles.css delete mode 100644 packages/astro/test/fixtures/content-collections/src/content/without-config/promo/launch week.mdx delete mode 100644 packages/astro/test/fixtures/content-collections/src/utils.js delete mode 100644 packages/astro/test/fixtures/content-intellisense/src/utils.js rename packages/astro/test/fixtures/content-ssr-integration/src/{content/config.ts => content.config.ts} (64%) create mode 100644 packages/astro/test/fixtures/content-ssr-integration/src/pages/deprecated-getdataentrybyid.astro create mode 100644 packages/astro/test/fixtures/content-ssr-integration/src/pages/deprecated-getentrybyslug.astro rename packages/astro/test/fixtures/{core-image-svg/src/content/config.ts => content/src/content.config.ts} (59%) delete mode 100644 packages/astro/test/fixtures/content/src/content/blog/with-layout-prop.md delete mode 100644 packages/astro/test/fixtures/content/src/pages/with-layout-prop.astro rename packages/astro/test/fixtures/core-image-errors/src/{content/config.ts => content.config.ts} (66%) rename packages/astro/test/fixtures/{content/src/content/config.ts => core-image-svg/src/content.config.ts} (59%) rename packages/astro/test/fixtures/core-image/src/{content/config.ts => content.config.ts} (73%) create mode 100644 packages/astro/test/fixtures/css-inline-stylesheets-2/src/content.config.ts rename packages/astro/test/fixtures/{content-collections/src/content/without-config => css-inline-stylesheets-2/src/pages}/endeavour.md (97%) create mode 100644 packages/astro/test/fixtures/css-inline-stylesheets/src/content.config.ts rename packages/astro/test/fixtures/{legacy-content-collections/src/content/without-config => css-inline-stylesheets/src/pages}/endeavour.md (97%) delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/astro.config.mjs delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/package.json delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/assets/the-future.jpg delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/components/ScriptCompA.astro delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/components/ScriptCompB.astro delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/config.ts delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-custom-slugs/one.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-custom-slugs/three.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-custom-slugs/two.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-data/one.json delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-data/three.json delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-data/two.json delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-schema-config/_ignore.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-schema-config/_ignore/file.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-schema-config/_ignore/ignore/file.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-schema-config/four%.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-schema-config/one.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-schema-config/three.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-schema-config/two.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-scripts/one.mdx delete mode 120000 packages/astro/test/fixtures/legacy-content-collections/src/content/with-symlinked-content delete mode 120000 packages/astro/test/fixtures/legacy-content-collections/src/content/with-symlinked-data delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-union-schema/newsletter.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/with-union-schema/post.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/without-config/columbia.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/without-config/enterprise.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/without-config/promo/_launch-week-styles.css delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/content/without-config/promo/launch week.mdx delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/pages/collections.json.js delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/pages/entries.json.js delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/pages/index.astro delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/pages/propagation.astro delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/pages/with-scripts/[...slug].astro delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/src/utils.js delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/first.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/second.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/third.md delete mode 100644 packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/data-collection/welcome.json delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/astro.config.mjs delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/package.json delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Ben Holmes.yml delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Fred K Schott.yml delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Nate Moore.yml delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/content/config.ts delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/content/docs/example.md delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/en.json delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/es.json delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/fr.yaml delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/pages/authors/[id].json.js delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/pages/authors/all.json.js delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/[lang].json.js delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/all.json.js delete mode 100644 packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/by-id.json.js delete mode 100644 packages/astro/test/legacy-content-collections.test.js delete mode 100644 packages/astro/test/legacy-data-collections.test.js delete mode 100644 packages/astro/test/units/dev/collections-mixed-content-errors.test.js delete mode 100644 packages/astro/test/units/dev/collections-renderentry.test.js rename packages/integrations/cloudflare/test/fixtures/compile-image-service/src/{content/config.ts => content.config.ts} (79%) rename packages/integrations/markdoc/test/fixtures/content-collections/src/{content/config.ts => content.config.ts} (68%) delete mode 100644 packages/integrations/markdoc/test/fixtures/content-collections/utils.js create mode 100644 packages/integrations/markdoc/test/fixtures/headings-custom/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/headings-custom/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/headings/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/headings/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/image-assets-custom/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/image-assets-custom/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/image-assets/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/image-assets/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/propagated-assets/src/content.config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render with-space/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render with-space/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render-html/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render-html/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render-null/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render-null/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render-partials/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render-partials/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render-simple/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render-simple/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render-typographer/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render-typographer/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-components/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render-with-components/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-config/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render-with-config/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/content/config.ts create mode 100644 packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/content.config.ts delete mode 100644 packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/content/config.ts rename packages/integrations/markdoc/test/fixtures/variables/src/{content/config.ts => content.config.ts} (66%) rename packages/integrations/mdx/test/fixtures/css-head-mdx/src/{content/config.ts => content.config.ts} (100%) create mode 100644 packages/integrations/mdx/test/fixtures/mdx-images/src/content.config.ts delete mode 100644 packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts rename packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/{content/config.js => content.config.js} (67%) diff --git a/.changeset/young-banks-camp.md b/.changeset/young-banks-camp.md new file mode 100644 index 000000000000..7a7f95f4de06 --- /dev/null +++ b/.changeset/young-banks-camp.md @@ -0,0 +1,18 @@ +--- +'astro': major +--- + +Removes legacy content collection support. + +This PR removes a number of APIs and options related to legacy content collections that were deprecated in Astro 5, including: + +- The `legacy.collections` option, which was added in Astro 5 and caused the old content collections system to be used instead of the new one. +- Deprecated functions from `astro:content`: `getEntryBySlug` and `getDataEntryById` are both replaced by `getEntry()`, which is a drop-in replacement for both. +- Support for the old `src/content/config.*` file location. You must now use `src/content.config.*`. +- Automatically generating collections when a `src/content/` directory is present and no content config file exists. You must now explicitly define collections in `src/content.config.*`. +- Support for collections without a loader. You must now use the `glob()` loader to create collections from filesystem content. This will also mean that generated entries use the new entry format: + - The `id` field is now a slug, not a filename. You can access the filename via `entry.filePath`, which is the path relative to the site root. + - There is no longer a `slug` field – use `id` instead. + - You can no longer call `entry.render()` on content entries. Use `render(entry)` instead, imported from `astro:content`. + +For full details, see [the Astro 6 upgrade guide](https://docs.astro.build/en/guides/upgrade-to/v6/#removed-legacy-content-collections). diff --git a/benchmark/make-project/markdown-cc1.js b/benchmark/make-project/markdown-cc1.js index 1e3aaa51779b..97cd7f1712ab 100644 --- a/benchmark/make-project/markdown-cc1.js +++ b/benchmark/make-project/markdown-cc1.js @@ -34,15 +34,15 @@ ${loremIpsumMd} new URL(`./src/pages/blog/[...slug].astro`, projectDir), `\ --- -import { getCollection } from 'astro:content'; +import { getCollection, render } from 'astro:content'; export async function getStaticPaths() { const blogEntries = await getCollection('blog'); return blogEntries.map(entry => ({ - params: { slug: entry.slug }, props: { entry }, + params: { slug: entry.id }, props: { entry }, })); } const { entry } = Astro.props; -const { Content } = await entry.render(); +const { Content } = await render(entry); ---

{entry.data.title}

@@ -52,6 +52,24 @@ const { Content } = await entry.render(); await Promise.all(promises); + await fs.writeFile( + new URL(`./src/content.config.ts`, projectDir), + `\ +import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), + schema: z.object({ + title: z.string(), + }), +}); + +export const collections = { blog }; +`, + 'utf-8', + ); + await fs.writeFile( new URL('./astro.config.js', projectDir), `\ diff --git a/benchmark/make-project/markdown-cc2.js b/benchmark/make-project/markdown-cc2.js index ba60813c0d4a..ed0312397b3c 100644 --- a/benchmark/make-project/markdown-cc2.js +++ b/benchmark/make-project/markdown-cc2.js @@ -28,7 +28,7 @@ ${loremIpsumMd} } await fs.writeFile( - new URL(`./src/content/config.ts`, projectDir), + new URL(`./src/content.config.ts`, projectDir), /*ts */ ` import { defineCollection, z } from 'astro:content'; import { glob } from 'astro/loaders'; diff --git a/benchmark/make-project/mdx-cc1.js b/benchmark/make-project/mdx-cc1.js index a948ce194008..4d381d921908 100644 --- a/benchmark/make-project/mdx-cc1.js +++ b/benchmark/make-project/mdx-cc1.js @@ -34,15 +34,15 @@ ${loremIpsumMd} new URL(`./src/pages/blog/[...slug].astro`, projectDir), `\ --- -import { getCollection } from 'astro:content'; +import { getCollection, render } from 'astro:content'; export async function getStaticPaths() { const blogEntries = await getCollection('blog'); return blogEntries.map(entry => ({ - params: { slug: entry.slug }, props: { entry }, + params: { slug: entry.id }, props: { entry }, })); } const { entry } = Astro.props; -const { Content } = await entry.render(); +const { Content } = await render(entry); ---

{entry.data.title}

@@ -52,6 +52,24 @@ const { Content } = await entry.render(); await Promise.all(promises); + await fs.writeFile( + new URL(`./src/content.config.ts`, projectDir), + `\ +import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), + schema: z.object({ + title: z.string(), + }), +}); + +export const collections = { blog }; +`, + 'utf-8', + ); + await fs.writeFile( new URL('./astro.config.js', projectDir), `\ diff --git a/benchmark/make-project/mdx-cc2.js b/benchmark/make-project/mdx-cc2.js index f50b63c9ee38..666971050d27 100644 --- a/benchmark/make-project/mdx-cc2.js +++ b/benchmark/make-project/mdx-cc2.js @@ -28,7 +28,7 @@ ${loremIpsumMd} } await fs.writeFile( - new URL(`./src/content/config.ts`, projectDir), + new URL(`./src/content.config.ts`, projectDir), /*ts */ ` import { defineCollection, z } from 'astro:content'; import { glob } from 'astro/loaders'; diff --git a/benchmark/make-project/memory-default.js b/benchmark/make-project/memory-default.js index 1087c3d4aa84..a4f09f8d49af 100644 --- a/benchmark/make-project/memory-default.js +++ b/benchmark/make-project/memory-default.js @@ -50,15 +50,15 @@ ${loremIpsum} new URL(`./src/pages/blog/[...slug].astro`, projectDir), `\ --- -import { getCollection } from 'astro:content'; +import { getCollection, render } from 'astro:content'; export async function getStaticPaths() { const blogEntries = await getCollection('blog'); return blogEntries.map(entry => ({ - params: { slug: entry.slug }, props: { entry }, + params: { slug: entry.id }, props: { entry }, })); } const { entry } = Astro.props; -const { Content } = await entry.render(); +const { Content } = await render(entry); ---

{entry.data.title}

@@ -68,6 +68,24 @@ const { Content } = await entry.render(); await Promise.all(promises); + await fs.writeFile( + new URL(`./src/content.config.ts`, projectDir), + `\ +import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), + schema: z.object({ + title: z.string(), + }), +}); + +export const collections = { blog }; +`, + 'utf-8', + ); + await fs.writeFile( new URL('./astro.config.js', projectDir), `\ diff --git a/examples/with-markdoc/src/content.config.ts b/examples/with-markdoc/src/content.config.ts index a991e1ea1038..490697000a14 100644 --- a/examples/with-markdoc/src/content.config.ts +++ b/examples/with-markdoc/src/content.config.ts @@ -1,5 +1,8 @@ import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; export const collections = { - docs: defineCollection({}), + docs: defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/docs' }), + }), }; diff --git a/examples/with-markdoc/src/pages/index.astro b/examples/with-markdoc/src/pages/index.astro index 891b344de8cb..b9ddcc83cc23 100644 --- a/examples/with-markdoc/src/pages/index.astro +++ b/examples/with-markdoc/src/pages/index.astro @@ -1,12 +1,12 @@ --- -import { getEntry } from 'astro:content'; +import { getEntry, render } from 'astro:content'; import Layout from '../layouts/Layout.astro'; const intro = await getEntry('docs', 'intro'); if (!intro) { return Astro.redirect('/404'); } -const { Content } = await intro.render(); +const { Content } = await render(intro); --- diff --git a/packages/astro/e2e/fixtures/actions-blog/src/content/config.ts b/packages/astro/e2e/fixtures/actions-blog/src/content.config.ts similarity index 77% rename from packages/astro/e2e/fixtures/actions-blog/src/content/config.ts rename to packages/astro/e2e/fixtures/actions-blog/src/content.config.ts index 667a31cc7391..355d5f45e912 100644 --- a/packages/astro/e2e/fixtures/actions-blog/src/content/config.ts +++ b/packages/astro/e2e/fixtures/actions-blog/src/content.config.ts @@ -1,7 +1,8 @@ import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; const blog = defineCollection({ - type: 'content', + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), // Type-check frontmatter using a schema schema: z.object({ title: z.string(), diff --git a/packages/astro/e2e/fixtures/actions-react-19/src/content/config.ts b/packages/astro/e2e/fixtures/actions-react-19/src/content.config.ts similarity index 77% rename from packages/astro/e2e/fixtures/actions-react-19/src/content/config.ts rename to packages/astro/e2e/fixtures/actions-react-19/src/content.config.ts index 667a31cc7391..355d5f45e912 100644 --- a/packages/astro/e2e/fixtures/actions-react-19/src/content/config.ts +++ b/packages/astro/e2e/fixtures/actions-react-19/src/content.config.ts @@ -1,7 +1,8 @@ import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; const blog = defineCollection({ - type: 'content', + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), // Type-check frontmatter using a schema schema: z.object({ title: z.string(), diff --git a/packages/astro/e2e/fixtures/content-collections/src/content.config.ts b/packages/astro/e2e/fixtures/content-collections/src/content.config.ts new file mode 100644 index 000000000000..db61e3da15aa --- /dev/null +++ b/packages/astro/e2e/fixtures/content-collections/src/content.config.ts @@ -0,0 +1,9 @@ +import { defineCollection } from "astro:content"; +import { glob } from "astro/loaders"; + + +const posts = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/posts' }), +}); + +export const collections = { posts }; diff --git a/packages/astro/e2e/fixtures/content-collections/src/content/config.ts b/packages/astro/e2e/fixtures/content-collections/src/content/config.ts deleted file mode 100644 index 81f62c975b82..000000000000 --- a/packages/astro/e2e/fixtures/content-collections/src/content/config.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { defineCollection } from "astro:content"; - - -const posts = defineCollection({}); - -export const collections = { posts }; diff --git a/packages/astro/e2e/fixtures/content-collections/src/pages/index.astro b/packages/astro/e2e/fixtures/content-collections/src/pages/index.astro index 4d6f17501943..0eed75d679f2 100644 --- a/packages/astro/e2e/fixtures/content-collections/src/pages/index.astro +++ b/packages/astro/e2e/fixtures/content-collections/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from 'astro:content' +import { getEntry, render } from 'astro:content' -const post = await getEntryBySlug('posts', 'post-1') -const { Content } = await post.render(); +const post = await getEntry('posts', 'post-1') +const { Content } = await render(post); --- diff --git a/packages/astro/performance/fixtures/md/src/ContentRenderer.astro b/packages/astro/performance/fixtures/md/src/ContentRenderer.astro index 307541fcfd59..5034ff5b2272 100644 --- a/packages/astro/performance/fixtures/md/src/ContentRenderer.astro +++ b/packages/astro/performance/fixtures/md/src/ContentRenderer.astro @@ -1,12 +1,13 @@ --- import type { CollectionEntry } from 'astro:content'; +import { render } from 'astro:content'; type Props = { entry: CollectionEntry<'generated'>; } const { entry } = Astro.props as Props; -const { Content } = await entry.render(); +const { Content } = await render(entry); --- diff --git a/packages/astro/performance/fixtures/mdoc/src/ContentRenderer.astro b/packages/astro/performance/fixtures/mdoc/src/ContentRenderer.astro index fbe086bc27fb..47f8ede04fa0 100644 --- a/packages/astro/performance/fixtures/mdoc/src/ContentRenderer.astro +++ b/packages/astro/performance/fixtures/mdoc/src/ContentRenderer.astro @@ -1,5 +1,6 @@ --- import type { CollectionEntry } from 'astro:content'; +import { render } from 'astro:content'; import { Aside, Heading, HydratedLikeButton, LikeButton } from '@performance/utils'; type Props = { @@ -7,7 +8,7 @@ type Props = { } const { entry } = Astro.props as Props; -const { Content } = await entry.render(); +const { Content } = await render(entry); --- {entry.data.type === 'with-astro-components' diff --git a/packages/astro/performance/fixtures/mdx/src/ContentRenderer.astro b/packages/astro/performance/fixtures/mdx/src/ContentRenderer.astro index 6c40fe40e6a8..b05b0f8c3acc 100644 --- a/packages/astro/performance/fixtures/mdx/src/ContentRenderer.astro +++ b/packages/astro/performance/fixtures/mdx/src/ContentRenderer.astro @@ -1,5 +1,6 @@ --- import type { CollectionEntry } from 'astro:content'; +import { render } from 'astro:content'; import Title from './Title.astro'; type Props = { @@ -7,7 +8,7 @@ type Props = { } const { entry } = Astro.props as Props; -const { Content } = await entry.render(); +const { Content } = await render(entry); --- {entry.data.type === 'with-astro-components' diff --git a/packages/astro/src/config/index.ts b/packages/astro/src/config/index.ts index 53c513f3973e..13751611f479 100644 --- a/packages/astro/src/config/index.ts +++ b/packages/astro/src/config/index.ts @@ -35,21 +35,17 @@ export function getViteConfig( // Use dynamic import to avoid pulling in deps unless used const [ - fs, { mergeConfig }, { createNodeLogger }, { resolveConfig, createSettings }, { createVite }, { runHookConfigSetup, runHookConfigDone }, - { astroContentListenPlugin }, ] = await Promise.all([ - import('node:fs'), import('vite'), import('../core/config/logging.js'), import('../core/config/index.js'), import('../core/create-vite.js'), import('../integrations/hooks.js'), - import('./vite-plugin-content-listen.js'), ]); const logger = createNodeLogger(inlineAstroConfig); const { astroConfig: config } = await resolveConfig(inlineAstroConfig, cmd); @@ -58,14 +54,7 @@ export function getViteConfig( const routesList = await createRoutesList({ settings }, logger); const manifest = createDevelopmentManifest(settings); const viteConfig = await createVite( - { - plugins: config.legacy.collections - ? [ - // Initialize the content listener - astroContentListenPlugin({ settings, logger, fs }), - ] - : [], - }, + {}, { settings, command: cmd, logger, mode, sync: false, manifest, routesList }, ); await runHookConfigDone({ settings, logger }); diff --git a/packages/astro/src/config/vite-plugin-content-listen.ts b/packages/astro/src/config/vite-plugin-content-listen.ts deleted file mode 100644 index 6c0408001954..000000000000 --- a/packages/astro/src/config/vite-plugin-content-listen.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type fsMod from 'node:fs'; -import type { Plugin, ViteDevServer } from 'vite'; -import { attachContentServerListeners } from '../content/server-listeners.js'; -import type { Logger } from '../core/logger/core.js'; -import type { AstroSettings } from '../types/astro.js'; - -/** - * Listen for Astro content directory changes and generate types. - * - * This is a separate plugin for `getViteConfig` as the `attachContentServerListeners` API - * needs to be called at different times in `astro dev` and `getViteConfig`. For `astro dev`, - * it needs to be called after the Astro server is started (packages/astro/src/core/dev/dev.ts). - * For `getViteConfig`, it needs to be called after the Vite server is started. - */ -export function astroContentListenPlugin({ - settings, - logger, - fs, -}: { - settings: AstroSettings; - logger: Logger; - fs: typeof fsMod; -}): Plugin { - let server: ViteDevServer; - - return { - name: 'astro:content-listen', - apply: 'serve', - configureServer(_server) { - server = _server; - }, - async buildStart() { - await attachContentServerListeners({ - fs: fs, - settings, - logger, - viteServer: server, - }); - }, - }; -} diff --git a/packages/astro/src/content/config.ts b/packages/astro/src/content/config.ts index 4ef7bdb4c1fc..e6424b5b7b10 100644 --- a/packages/astro/src/content/config.ts +++ b/packages/astro/src/content/config.ts @@ -182,23 +182,29 @@ export function defineCollection( }); } - 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 ${importerFilename ?? 'your content config file'}.`, - ); - } - if ( - typeof config.loader === 'object' && - typeof config.loader.load !== 'function' && - ('loadEntry' in config.loader || 'loadCollection' in config.loader) - ) { - throw new AstroUserError( - `Live content collections must be defined in "src/live.config.ts" file. Check your collection definitions in "${importerFilename ?? 'your content config file'}" to ensure you are not using a live loader.`, - ); - } - config.type = CONTENT_LAYER_TYPE; + if (!('loader' in config)) { + throw new AstroError({ + ...AstroErrorData.ContentCollectionMissingLoader, + message: AstroErrorData.ContentCollectionMissingLoader.message(importerFilename), + }); + } + + if (config.type && config.type !== CONTENT_LAYER_TYPE) { + throw new AstroError({ + ...AstroErrorData.ContentCollectionInvalidType, + message: AstroErrorData.ContentCollectionInvalidType.message(config.type, importerFilename), + }); + } + + if ( + typeof config.loader === 'object' && + typeof config.loader.load !== 'function' && + ('loadEntry' in config.loader || 'loadCollection' in config.loader) + ) { + throw new AstroUserError( + `Live content collections must be defined in "src/live.config.ts" file. Check your collection definitions in "${importerFilename ?? 'your content config file'}" to ensure you are not using a live loader.`, + ); } - if (!config.type) config.type = 'content'; + config.type = CONTENT_LAYER_TYPE; return config; } diff --git a/packages/astro/src/content/loaders/glob.ts b/packages/astro/src/content/loaders/glob.ts index ab78f2a76425..a75458f095c2 100644 --- a/packages/astro/src/content/loaders/glob.ts +++ b/packages/astro/src/content/loaders/glob.ts @@ -56,14 +56,6 @@ function checkPrefix(pattern: string | Array, prefix: string) { * Loads multiple entries, using a glob pattern to match files. * @param pattern A glob pattern to match files, relative to the content directory. */ -export function glob(globOptions: GlobOptions): Loader; -/** @private */ -export function glob( - globOptions: GlobOptions & { - /** @deprecated */ - _legacy?: true; - }, -): Loader; export function glob(globOptions: GlobOptions): Loader { if (checkPrefix(globOptions.pattern, '../')) { @@ -90,9 +82,6 @@ export function glob(globOptions: GlobOptions): Loader { >(); const untouchedEntries = new Set(store.keys()); - const isLegacy = (globOptions as any)._legacy; - // If global legacy collection handling flag is *not* enabled then this loader is used to emulate them instead - const emulateLegacyCollections = !config.legacy.collections; async function syncData( entry: string, base: URL, @@ -125,17 +114,6 @@ export function glob(globOptions: GlobOptions): Loader { store.delete(oldId); } - let legacyId: string | undefined; - - if (isLegacy) { - const entryURL = new URL(encodeURI(entry), base); - const legacyOptions = getContentEntryIdAndSlug({ - entry: entryURL, - contentDir: base, - collection: '', - }); - legacyId = legacyOptions.id; - } untouchedEntries.delete(id); const existingEntry = store.get(id); @@ -165,12 +143,6 @@ export function glob(globOptions: GlobOptions): Loader { filePath, }); if (entryType.getRenderFunction) { - if (isLegacy && data.layout) { - logger.error( - `The Markdown "layout" field is not supported in content collections in Astro 5. Ignoring layout for ${JSON.stringify(entry)}. Enable "legacy.collections" if you need to use the layout field.`, - ); - } - let render = renderFunctionByContentType.get(entryType); if (!render) { render = await entryType.getRenderFunction(config); @@ -199,7 +171,6 @@ export function glob(globOptions: GlobOptions): Loader { digest, rendered, assetImports: rendered?.metadata?.imagePaths, - legacyId, }); // todo: add an explicit way to opt in to deferred rendering @@ -211,10 +182,9 @@ export function glob(globOptions: GlobOptions): Loader { filePath: relativePath, digest, deferredRender: true, - legacyId, }); } else { - store.set({ id, data: parsedData, body, filePath: relativePath, digest, legacyId }); + store.set({ id, data: parsedData, body, filePath: relativePath, digest }); } fileToIdMap.set(filePath, id); @@ -262,11 +232,6 @@ export function glob(globOptions: GlobOptions): Loader { const contentDir = new URL('content/', config.srcDir); - function isInContentDir(file: string) { - const fileUrl = new URL(file, baseDir); - return fileUrl.href.startsWith(contentDir.href); - } - const configFiles = new Set( ['config.js', 'config.ts', 'config.mjs'].map((file) => new URL(file, contentDir).href), ); @@ -281,10 +246,6 @@ export function glob(globOptions: GlobOptions): Loader { if (isConfigFile(entry)) { return; } - if (!emulateLegacyCollections && isInContentDir(entry)) { - skippedFiles.push(entry); - return; - } return limit(async () => { const entryType = configForFile(entry); await syncData(entry, baseDir, entryType); diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts index 484642fe81a4..a9cec7f6cd59 100644 --- a/packages/astro/src/content/runtime.ts +++ b/packages/astro/src/content/runtime.ts @@ -1,7 +1,6 @@ import type { MarkdownHeading } from '@astrojs/markdown-remark'; import { escape } from 'html-escaper'; import { Traverse } from 'neotraverse/modern'; -import pLimit from 'p-limit'; import { ZodIssueCode, z } from 'zod'; import type { GetImageResult, ImageMetadata } from '../assets/types.js'; import { imageSrcToImportId } from '../assets/utils/resolveImports.js'; @@ -34,7 +33,6 @@ import { LiveEntryNotFoundError, } from './loaders/errors.js'; import type { LiveLoader } from './loaders/types.js'; -import type { ContentLookupMap } from './utils.js'; export { LiveCollectionError, LiveCollectionCacheHintError, @@ -42,33 +40,11 @@ export { LiveCollectionValidationError, }; type LazyImport = () => Promise; -type GlobResult = Record; -type CollectionToEntryMap = Record; -type GetEntryImport = (collection: string, lookupId: string) => Promise; type LiveCollectionConfigMap = Record< string, { loader: LiveLoader; type: typeof LIVE_CONTENT_TYPE; schema?: z.ZodType } >; -export function createCollectionToGlobResultMap({ - globResult, - contentDir, -}: { - globResult: GlobResult; - contentDir: string; -}) { - const collectionToGlobResultMap: CollectionToEntryMap = {}; - for (const key in globResult) { - const keyRelativeToContentDir = key.replace(new RegExp(`^${contentDir}`), ''); - const segments = keyRelativeToContentDir.split('/'); - if (segments.length <= 1) continue; - const collection = segments[0]; - collectionToGlobResultMap[collection] ??= {}; - collectionToGlobResultMap[collection][key] = globResult[key]; - } - return collectionToGlobResultMap; -} - const cacheHintSchema = z.object({ tags: z.array(z.string()).optional(), maxAge: z.number().optional(), @@ -115,16 +91,8 @@ async function parseLiveEntry( } export function createGetCollection({ - contentCollectionToEntryMap, - dataCollectionToEntryMap, - getRenderEntryImport, - cacheEntriesByCollection, liveCollections, }: { - contentCollectionToEntryMap: CollectionToEntryMap; - dataCollectionToEntryMap: CollectionToEntryMap; - getRenderEntryImport: GetEntryImport; - cacheEntriesByCollection: Map; liveCollections: LiveCollectionConfigMap; }) { return async function getCollection( @@ -140,12 +108,7 @@ export function createGetCollection({ const hasFilter = typeof filter === 'function'; const store = await globalDataStore.get(); - let type: 'content' | 'data'; - if (collection in contentCollectionToEntryMap) { - type = 'content'; - } else if (collection in dataCollectionToEntryMap) { - type = 'data'; - } else if (store.hasCollection(collection)) { + if (store.hasCollection(collection)) { // @ts-expect-error virtual module const { default: imageAssetMap } = await import('astro:asset-imports'); @@ -159,10 +122,6 @@ export function createGetCollection({ collection, }; - if (entry.legacyId) { - entry = emulateLegacyEntry(entry); - } - if (hasFilter && !filter(entry)) { continue; } @@ -177,143 +136,6 @@ export function createGetCollection({ ); return []; } - - const lazyImports = Object.values( - type === 'content' - ? contentCollectionToEntryMap[collection] - : dataCollectionToEntryMap[collection], - ); - let entries: any[] = []; - // Cache `getCollection()` calls in production only - // prevents stale cache in development - if (!import.meta.env?.DEV && cacheEntriesByCollection.has(collection)) { - entries = cacheEntriesByCollection.get(collection)!; - } else { - const limit = pLimit(10); - entries = await Promise.all( - lazyImports.map((lazyImport) => - limit(async () => { - const entry = await lazyImport(); - return type === 'content' - ? { - id: entry.id, - slug: entry.slug, - body: entry.body, - collection: entry.collection, - data: entry.data, - async render() { - return render({ - collection: entry.collection, - id: entry.id, - renderEntryImport: await getRenderEntryImport(collection, entry.slug), - }); - }, - } - : { - id: entry.id, - collection: entry.collection, - data: entry.data, - }; - }), - ), - ); - cacheEntriesByCollection.set(collection, entries); - } - if (hasFilter) { - return entries.filter(filter); - } else { - // Clone the array so users can safely mutate it. - // slice() is faster than ...spread for large arrays. - return entries.slice(); - } - }; -} - -export function createGetEntryBySlug({ - getEntryImport, - getRenderEntryImport, - collectionNames, - getEntry, -}: { - getEntryImport: GetEntryImport; - getRenderEntryImport: GetEntryImport; - collectionNames: Set; - getEntry: ReturnType; -}) { - return async function getEntryBySlug(collection: string, slug: string) { - const store = await globalDataStore.get(); - - if (!collectionNames.has(collection)) { - if (store.hasCollection(collection)) { - const entry = await getEntry(collection, slug); - if (entry && 'slug' in entry) { - return entry; - } - throw new AstroError({ - ...AstroErrorData.GetEntryDeprecationError, - message: AstroErrorData.GetEntryDeprecationError.message(collection, 'getEntryBySlug'), - }); - } - console.warn( - `The collection ${JSON.stringify(collection)} does not exist. Please ensure it is defined in your content config.`, - ); - return undefined; - } - - const entryImport = await getEntryImport(collection, slug); - if (typeof entryImport !== 'function') return undefined; - - const entry = await entryImport(); - - return { - id: entry.id, - slug: entry.slug, - body: entry.body, - collection: entry.collection, - data: entry.data, - async render() { - return render({ - collection: entry.collection, - id: entry.id, - renderEntryImport: await getRenderEntryImport(collection, slug), - }); - }, - }; - }; -} - -export function createGetDataEntryById({ - getEntryImport, - collectionNames, - getEntry, -}: { - getEntryImport: GetEntryImport; - collectionNames: Set; - getEntry: ReturnType; -}) { - return async function getDataEntryById(collection: string, id: string) { - const store = await globalDataStore.get(); - - if (!collectionNames.has(collection)) { - if (store.hasCollection(collection)) { - return getEntry(collection, id); - } - console.warn( - `The collection ${JSON.stringify(collection)} does not exist. Please ensure it is defined in your content config.`, - ); - return undefined; - } - - const lazyImport = await getEntryImport(collection, id); - - if (!lazyImport) throw new Error(`Entry ${collection} → ${id} was not found.`); - const entry = await lazyImport(); - - return { - id: entry.id, - collection: entry.collection, - data: entry.data, - }; }; } @@ -334,31 +156,7 @@ type DataEntryResult = { type EntryLookupObject = { collection: string; id: string } | { collection: string; slug: string }; -function emulateLegacyEntry({ legacyId, ...entry }: DataEntry & { collection: string }) { - // Define this first so it's in scope for the render function - const legacyEntry = { - ...entry, - id: legacyId!, - slug: entry.id, - }; - return { - ...legacyEntry, - // Define separately so the render function isn't included in the object passed to `renderEntry()` - render: () => renderEntry(legacyEntry), - } as ContentEntryResult; -} - -export function createGetEntry({ - getEntryImport, - getRenderEntryImport, - collectionNames, - liveCollections, -}: { - getEntryImport: GetEntryImport; - getRenderEntryImport: GetEntryImport; - collectionNames: Set; - liveCollections: LiveCollectionConfigMap; -}) { +export function createGetEntry({ liveCollections }: { liveCollections: LiveCollectionConfigMap }) { return async function getEntry( // Can either pass collection and identifier as 2 positional args, // Or pass a single object with the collection and identifier as properties. @@ -408,53 +206,49 @@ export function createGetEntry({ // @ts-expect-error virtual module const { default: imageAssetMap } = await import('astro:asset-imports'); entry.data = updateImageReferencesInData(entry.data, entry.filePath, imageAssetMap); - if (entry.legacyId) { - return emulateLegacyEntry({ ...entry, collection }); - } - return { + const result = { ...entry, collection, } as DataEntryResult | ContentEntryResult; - } - - if (!collectionNames.has(collection)) { - console.warn( - `The collection ${JSON.stringify(collection)} does not exist. Please ensure it is defined in your content config.`, + // TODO: remove in Astro 7 + warnForPropertyAccess( + result.data, + 'slug', + `[content] Attempted to access deprecated property on "${collection}" entry.\nThe "slug" property is no longer automatically added to entries. Please use the "id" property instead.`, + ); + // TODO: remove in Astro 7 + warnForPropertyAccess( + result, + 'render', + `[content] Invalid attempt to access "render()" method on "${collection}" entry.\nTo render an entry, use "render(entry)" from "astro:content".`, ); - return undefined; + return result; } - const entryImport = await getEntryImport(collection, lookupId); - if (typeof entryImport !== 'function') return undefined; - - const entry = await entryImport(); - - if (entry._internal.type === 'content') { - return { - id: entry.id, - slug: entry.slug, - body: entry.body, - collection: entry.collection, - data: entry.data, - async render() { - return render({ - collection: entry.collection, - id: entry.id, - renderEntryImport: await getRenderEntryImport(collection, lookupId), - }); - }, - }; - } else if (entry._internal.type === 'data') { - return { - id: entry.id, - collection: entry.collection, - data: entry.data, - }; - } return undefined; }; } +function warnForPropertyAccess(entry: object, prop: string, message: string) { + // Skip if the property is already defined (it may be legitimately defined on the entry) + if (!(prop in entry)) { + let _value: any = undefined; + Object.defineProperty(entry, prop, { + get() { + // If the user sets value themselves, don't warn + if (_value === undefined) { + console.error(message); + } + return _value; + }, + set(v) { + _value = v; + }, + enumerable: false, + }); + } +} + export function createGetEntries(getEntry: ReturnType) { return async function getEntries( entries: { collection: string; id: string }[] | { collection: string; slug: string }[], @@ -745,21 +539,11 @@ function updateImageReferencesInData>( }); } -export async function renderEntry( - entry: - | DataEntry - | { render: () => Promise<{ Content: AstroComponentFactory }> } - | (DataEntry & { render: () => Promise<{ Content: AstroComponentFactory }> }), -) { +export async function renderEntry(entry: DataEntry) { if (!entry) { throw new AstroError(AstroErrorData.RenderUndefinedEntryError); } - if ('render' in entry && !('legacyId' in entry)) { - // This is an old content collection entry, so we use its render method - return entry.render(); - } - if (entry.deferredRender) { try { // @ts-expect-error virtual module @@ -882,7 +666,7 @@ async function render({ } } -export function createReference({ lookupMap }: { lookupMap: ContentLookupMap }) { +export function createReference() { return function reference(collection: string) { return z .union([ @@ -915,36 +699,10 @@ export function createReference({ lookupMap }: { lookupMap: ContentLookupMap }) }); return; } - // We won't throw if the collection is missing, because it may be a content layer collection and the store may not yet be populated. // If it is an object then we're validating later in the build, so we can check the collection at that point. - return lookup; } - // 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 }; - } - const { type, entries } = lookupMap[collection]; - const entry = entries[lookup]; - - if (!entry) { - ctx.addIssue({ - code: ZodIssueCode.custom, - message: `**${flattenedErrorPath}**: Reference to ${collection} invalid. Expected ${Object.keys( - entries, - ) - .map((c) => JSON.stringify(c)) - .join(' | ')}. Received ${JSON.stringify(lookup)}.`, - }); - return; - } - // Content is still identified by slugs, so map to a `slug` key for consistency. - if (type === 'content') { - return { slug: lookup, collection }; - } return { id: lookup, collection }; }, ); @@ -983,3 +741,20 @@ export function defineLiveCollection() { ), }); } + +export function createDeprecatedFunction(functionName: string) { + return (collection: string) => { + const error = new AstroError({ + ...AstroErrorData.GetEntryDeprecationError, + message: AstroErrorData.GetEntryDeprecationError.message(collection, functionName), + }); + + // Remove the runtime module from the stack trace + const stackLines = error.stack?.split('\n'); + if (stackLines && stackLines.length > 1) { + stackLines.splice(1, 1); + error.stack = stackLines.join('\n'); + } + throw error; + }; +} diff --git a/packages/astro/src/content/server-listeners.ts b/packages/astro/src/content/server-listeners.ts index 492a5a5160cf..8bae846dddd1 100644 --- a/packages/astro/src/content/server-listeners.ts +++ b/packages/astro/src/content/server-listeners.ts @@ -1,14 +1,9 @@ import type fsMod from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { bold, cyan, underline } from 'kleur/colors'; import type { ViteDevServer } from 'vite'; -import { loadTSConfig } from '../core/config/tsconfig.js'; import type { Logger } from '../core/logger/core.js'; -import { appendForwardSlash } from '../core/path.js'; import type { AstroSettings } from '../types/astro.js'; import { createContentTypesGenerator } from './types-generator.js'; -import { type ContentPaths, getContentPaths, globalContentConfigObserver } from './utils.js'; +import { globalContentConfigObserver } from './utils.js'; interface ContentServerListenerParams { fs: typeof fsMod; @@ -23,101 +18,29 @@ export async function attachContentServerListeners({ logger, settings, }: ContentServerListenerParams) { - const contentPaths = getContentPaths(settings.config, fs); - if (!settings.config.legacy?.collections) { - await attachListeners(); - } else if (fs.existsSync(contentPaths.contentDir)) { - logger.debug( - 'content', - `Watching ${cyan( - contentPaths.contentDir.href.replace(settings.config.root.href, ''), - )} for changes`, - ); - const maybeTsConfigStats = await getTSConfigStatsWhenAllowJsFalse({ contentPaths, settings }); - if (maybeTsConfigStats) warnAllowJsIsFalse({ ...maybeTsConfigStats, logger }); - await attachListeners(); - } else { - viteServer.watcher.on('addDir', contentDirListener); - async function contentDirListener(dir: string) { - if (appendForwardSlash(pathToFileURL(dir).href) === contentPaths.contentDir.href) { - logger.debug('content', `Content directory found. Watching for changes`); - await attachListeners(); - viteServer.watcher.removeListener('addDir', contentDirListener); - } - } - } - - async function attachListeners() { - const contentGenerator = await createContentTypesGenerator({ - fs, - settings, - logger, - viteServer, - contentConfigObserver: globalContentConfigObserver, - }); - await contentGenerator.init(); - logger.debug('content', 'Types generated'); - - viteServer.watcher.on('add', (entry) => { - contentGenerator.queueEvent({ name: 'add', entry }); - }); - viteServer.watcher.on('addDir', (entry) => - contentGenerator.queueEvent({ name: 'addDir', entry }), - ); - viteServer.watcher.on('change', (entry) => { - contentGenerator.queueEvent({ name: 'change', entry }); - }); - viteServer.watcher.on('unlink', (entry) => { - contentGenerator.queueEvent({ name: 'unlink', entry }); - }); - viteServer.watcher.on('unlinkDir', (entry) => - contentGenerator.queueEvent({ name: 'unlinkDir', entry }), - ); - } -} - -function warnAllowJsIsFalse({ - logger, - tsConfigFileName, - contentConfigFileName, -}: { - logger: Logger; - tsConfigFileName: string; - contentConfigFileName: string; -}) { - logger.warn( - 'content', - `Make sure you have the ${bold('allowJs')} compiler option set to ${bold( - 'true', - )} in your ${bold(tsConfigFileName)} file to have autocompletion in your ${bold( - contentConfigFileName, - )} file. See ${underline( - cyan('https://www.typescriptlang.org/tsconfig#allowJs'), - )} for more information.`, + const contentGenerator = await createContentTypesGenerator({ + fs, + settings, + logger, + viteServer, + contentConfigObserver: globalContentConfigObserver, + }); + await contentGenerator.init(); + logger.debug('content', 'Types generated'); + + viteServer.watcher.on('add', (entry) => { + contentGenerator.queueEvent({ name: 'add', entry }); + }); + viteServer.watcher.on('addDir', (entry) => + contentGenerator.queueEvent({ name: 'addDir', entry }), ); -} - -async function getTSConfigStatsWhenAllowJsFalse({ - contentPaths, - settings, -}: { - contentPaths: ContentPaths; - settings: AstroSettings; -}) { - const isContentConfigJsFile = ['.js', '.mjs'].some((ext) => - contentPaths.config.url.pathname.endsWith(ext), + viteServer.watcher.on('change', (entry) => { + contentGenerator.queueEvent({ name: 'change', entry }); + }); + viteServer.watcher.on('unlink', (entry) => { + contentGenerator.queueEvent({ name: 'unlink', entry }); + }); + viteServer.watcher.on('unlinkDir', (entry) => + contentGenerator.queueEvent({ name: 'unlinkDir', entry }), ); - if (!isContentConfigJsFile) return; - - const inputConfig = await loadTSConfig(fileURLToPath(settings.config.root)); - if (typeof inputConfig === 'string') return; - - const tsConfigFileName = inputConfig.tsconfigFile.split(path.sep).pop(); - if (!tsConfigFileName) return; - - const contentConfigFileName = contentPaths.config.url.pathname.split(path.sep).pop()!; - const allowJSOption = inputConfig.tsconfig.compilerOptions?.allowJs; - if (allowJSOption) return; - - return { tsConfigFileName, contentConfigFileName }; } diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts index ac6d1c51e64d..0c26a0ee8484 100644 --- a/packages/astro/src/content/types-generator.ts +++ b/packages/astro/src/content/types-generator.ts @@ -2,12 +2,11 @@ import type fsMod from 'node:fs'; import * as path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { bold, cyan } from 'kleur/colors'; -import { glob } from 'tinyglobby'; import { normalizePath, type ViteDevServer } from 'vite'; import { type ZodSchema, z } from 'zod'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { AstroError } from '../core/errors/errors.js'; -import { AstroErrorData, AstroUserError } from '../core/errors/index.js'; +import { AstroErrorData } from '../core/errors/index.js'; import type { Logger } from '../core/logger/core.js'; import { isRelativePath } from '../core/path.js'; import type { AstroSettings } from '../types/astro.js'; @@ -16,7 +15,7 @@ import { COLLECTIONS_DIR, CONTENT_LAYER_TYPE, CONTENT_TYPES_FILE, - LIVE_CONTENT_TYPE, + type LIVE_CONTENT_TYPE, VIRTUAL_MODULE_ID, } from './consts.js'; import { @@ -88,33 +87,9 @@ export async function createContentTypesGenerator({ const typeTemplateContent = await fs.promises.readFile(contentPaths.typesTemplate, 'utf-8'); - async function init(): Promise< - { typesGenerated: true } | { typesGenerated: false; reason: 'no-content-dir' } - > { + async function init(): Promise { 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), - absolute: true, - }); - - for (const fullPath of globResult) { - const entryURL = pathToFileURL(fullPath); - if (entryURL.href.startsWith(contentPaths.config.url.href)) continue; - const stat = fs.statSync(fullPath); - if (stat.isFile()) { - events.push({ name: 'add', entry: entryURL }); - } else if (stat.isDirectory()) { - events.push({ name: 'addDir', entry: entryURL }); - } - } - } await runEvents(); - return { typesGenerated: true }; } async function handleEvent(event: ContentEvent): Promise<{ shouldGenerateTypes: boolean }> { @@ -293,11 +268,7 @@ export async function createContentTypesGenerator({ name: rawEvent.name, }; - if (settings.config.legacy.collections) { - if (!event.entry.pathname.startsWith(contentPaths.contentDir.pathname)) { - return; - } - } else if (contentPaths.config.url.pathname !== event.entry.pathname) { + if (contentPaths.config.url.pathname !== event.entry.pathname) { return; } @@ -405,24 +376,24 @@ async function typeForCollection( if (collection?.schema) { return `InferEntrySchema<${collectionKey}>`; } - - if (collection?.type === CONTENT_LAYER_TYPE) { - const schema = await getContentLayerSchema(collection, collectionKey); - if (schema) { - try { - const zodToTs = await import('zod-to-ts'); - const ast = zodToTs.zodToTs(schema); - return zodToTs.printNode(ast.node); - } catch (err: any) { - // zod-to-ts is sad if we don't have TypeScript installed, but that's fine as we won't be needing types in that case - if (err.message.includes("Cannot find package 'typescript'")) { - return 'any'; - } - throw err; - } + if (!collection?.type) { + return 'any'; + } + const schema = await getContentLayerSchema(collection, collectionKey); + if (!schema) { + return 'any'; + } + try { + const zodToTs = await import('zod-to-ts'); + const ast = zodToTs.zodToTs(schema); + return zodToTs.printNode(ast.node); + } catch (err: any) { + // zod-to-ts is sad if we don't have TypeScript installed, but that's fine as we won't be needing types in that case + if (err.message.includes("Cannot find package 'typescript'")) { + return 'any'; } + throw err; } - return 'any'; } async function writeContentFiles({ @@ -446,7 +417,6 @@ async function writeContentFiles({ logger: Logger; settings: AstroSettings; }) { - let contentTypesStr = ''; let dataTypesStr = ''; const collectionSchemasDir = new URL(COLLECTIONS_DIR, settings.dotAstroDir); @@ -489,56 +459,10 @@ async function writeContentFiles({ }); return; } - const resolvedType = - 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') - : collection.type; - const collectionEntryKeys = Object.keys(collection.entries).sort(); + const dataType = await typeForCollection(collectionConfig, collectionKey); - switch (resolvedType) { - case LIVE_CONTENT_TYPE: - // This error should never be thrown, as it should have been caught earlier in the process - throw new AstroUserError( - `Invalid definition for collection ${collectionKey}: Live content collections must be defined in "src/live.config.ts"`, - ); - case 'content': - if (collectionEntryKeys.length === 0) { - contentTypesStr += `${collectionKey}: Record;\n`; - break; - } - contentTypesStr += `${collectionKey}: {\n`; - for (const entryKey of collectionEntryKeys) { - const entryMetadata = collection.entries[entryKey]; - const renderType = `{ render(): Render[${JSON.stringify( - path.extname(JSON.parse(entryKey)), - )}] }`; - - const slugType = JSON.stringify(entryMetadata.slug); - contentTypesStr += `${entryKey}: {\n id: ${entryKey};\n slug: ${slugType};\n body: string;\n collection: ${collectionKey};\n data: ${dataType}\n} & ${renderType};\n`; - } - contentTypesStr += `};\n`; - break; - case CONTENT_LAYER_TYPE: - const legacyTypes = (collectionConfig as any)?._legacy - ? 'render(): Render[".md"];\n slug: string;\n body: string;\n' - : 'body?: string;\n'; - dataTypesStr += `${collectionKey}: Record;\n`; - break; - case 'data': - if (collectionEntryKeys.length === 0) { - dataTypesStr += `${collectionKey}: Record;\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`; - } - break; - } + dataTypesStr += `${collectionKey}: Record;\n`; if ( collectionConfig && @@ -599,7 +523,6 @@ async function writeContentFiles({ } } typeTemplateContent = typeTemplateContent - .replace('// @@CONTENT_ENTRY_MAP@@', contentTypesStr) .replace('// @@DATA_ENTRY_MAP@@', dataTypesStr) .replace( "'@@CONTENT_CONFIG_TYPE@@'", diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index 851907d4c3f4..766d27eb93c4 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -11,7 +11,6 @@ import { z } from 'zod'; import { AstroError, AstroErrorData, errorMap, MarkdownError } from '../core/errors/index.js'; import { isYAMLException } from '../core/errors/utils.js'; import type { Logger } from '../core/logger/core.js'; -import { appendForwardSlash } from '../core/path.js'; import { normalizePath } from '../core/viteUtils.js'; import type { AstroSettings } from '../types/astro.js'; import type { AstroConfig } from '../types/public/config.js'; @@ -25,18 +24,8 @@ import { LIVE_CONTENT_TYPE, PROPAGATED_ASSET_FLAG, } from './consts.js'; -import { glob } from './loaders/glob.js'; import { createImage } from './runtime-assets.js'; -/** - * A map from a collection + slug to the local file path. - * This is used internally to resolve entry imports when using `getEntry()`. - * @see `templates/content/module.mjs` - */ -export type ContentLookupMap = { - [collectionName: string]: { type: 'content' | 'data'; entries: { [lookupId: string]: string } }; -}; - const entryTypeSchema = z .object({ id: z.string({ @@ -63,14 +52,6 @@ export const loaderReturnSchema = z.union([ ]); const collectionConfigParser = z.union([ - z.object({ - type: z.literal('content').optional().default('content'), - schema: z.any().optional(), - }), - z.object({ - type: z.literal('data'), - schema: z.any().optional(), - }), z.object({ type: z.literal(CONTENT_LAYER_TYPE), schema: z.any().optional(), @@ -102,8 +83,6 @@ const collectionConfigParser = z.union([ render: z.function(z.tuple([z.any()], z.unknown())).optional(), }), ]), - /** deprecated */ - _legacy: z.boolean().optional(), }), z.object({ type: z.literal(LIVE_CONTENT_TYPE).optional().default(LIVE_CONTENT_TYPE), @@ -157,14 +136,7 @@ export async function getEntryDataAndImages< experimentalSvgEnabled: boolean, pluginContext?: PluginContext, ): Promise<{ data: TOutputData; imageImports: Array }> { - let data: TOutputData; - // Legacy content collections have 'slug' removed - if (collectionConfig.type === 'content' || (collectionConfig as any)._legacy) { - const { slug, ...unvalidatedData } = entry.unvalidatedData; - data = unvalidatedData as TOutputData; - } else { - data = entry.unvalidatedData as TOutputData; - } + let data = entry.unvalidatedData as TOutputData; let schema = collectionConfig.schema; @@ -192,20 +164,6 @@ export async function getEntryDataAndImages< } if (schema) { - // Catch reserved `slug` field inside content schemas - // Note: will not warn for `z.union` or `z.intersection` schemas - if ( - collectionConfig.type === 'content' && - typeof schema === 'object' && - 'shape' in schema && - schema.shape.slug - ) { - throw new AstroError({ - ...AstroErrorData.ContentSchemaContainsSlugError, - message: AstroErrorData.ContentSchemaContainsSlugError.message(entry.collection), - }); - } - // Use `safeParseAsync` to allow async transforms let formattedError; const parsed = await (schema as z.ZodSchema).safeParseAsync(data, { @@ -220,13 +178,13 @@ export async function getEntryDataAndImages< data = parsed.data as TOutputData; } else { if (!formattedError) { - const errorType = - collectionConfig.type === 'content' - ? AstroErrorData.InvalidContentEntryFrontmatterError - : AstroErrorData.InvalidContentEntryDataError; formattedError = new AstroError({ - ...errorType, - message: errorType.message(entry.collection, entry.id, parsed.error), + ...AstroErrorData.InvalidContentEntryDataError, + message: AstroErrorData.InvalidContentEntryDataError.message( + entry.collection, + entry.id, + parsed.error, + ), location: { file: entry._internal?.filePath, line: getYAMLErrorLine( @@ -560,100 +518,6 @@ async function loadContentConfig({ } } -async function autogenerateCollections({ - config, - settings, - fs, -}: { - config?: ContentConfig; - settings: AstroSettings; - fs: typeof fsMod; -}): Promise { - if (settings.config.legacy.collections) { - return config; - } - const contentDir = new URL('./content/', settings.config.srcDir); - - const collections: Record = config?.collections ?? {}; - - const contentExts = getContentEntryExts(settings); - const dataExts = getDataEntryExts(settings); - - const contentPattern = globWithUnderscoresIgnored('', contentExts); - const dataPattern = globWithUnderscoresIgnored('', dataExts); - let usesContentLayer = false; - for (const collectionName of Object.keys(collections)) { - if ( - collections[collectionName]?.type === 'content_layer' || - collections[collectionName]?.type === 'live' - ) { - usesContentLayer = true; - // This is already a content layer, skip - continue; - } - - const isDataCollection = collections[collectionName]?.type === 'data'; - const base = new URL(`${collectionName}/`, contentDir); - // Only "content" collections need special legacy handling - const _legacy = !isDataCollection || undefined; - collections[collectionName] = { - ...collections[collectionName], - type: 'content_layer', - _legacy, - loader: glob({ - base, - pattern: isDataCollection ? dataPattern : contentPattern, - _legacy, - // Legacy data collections IDs aren't slugified - generateId: isDataCollection - ? ({ entry }) => - getDataEntryId({ - entry: new URL(entry, base), - collection: collectionName, - contentDir, - }) - : undefined, - - // Zod weirdness has trouble with typing the args to the load function - }) as any, - }; - } - 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 = []; - for (const entry of await fs.promises.readdir(contentDir, { withFileTypes: true })) { - const collectionName = entry.name; - if (['_', '.'].includes(collectionName.at(0) ?? '')) { - continue; - } - if (entry.isDirectory() && !(collectionName in collections)) { - orphanedCollections.push(collectionName); - const base = new URL(`${collectionName}/`, contentDir); - collections[collectionName] = { - type: 'content_layer', - loader: glob({ - base, - pattern: contentPattern, - _legacy: true, - }) as any, - }; - } - } - if (orphanedCollections.length > 0) { - 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". -The following collections have been auto-generated: ${orphanedCollections - .map((name) => green(name)) - .join(', ')}\n`, - ); - } - } - return { ...config, collections }; -} - export async function reloadContentConfigObserver({ observer = globalContentConfigObserver, ...loadContentConfigOpts @@ -667,11 +531,6 @@ export async function reloadContentConfigObserver({ try { let config = await loadContentConfig(loadContentConfigOpts); - config = await autogenerateCollections({ - config, - ...loadContentConfigOpts, - }); - if (config) { observer.set({ status: 'loaded', config }); } else { @@ -741,15 +600,22 @@ export type ContentPaths = { }; export function getContentPaths( - { - srcDir, - legacy, - root, - experimental, - }: Pick, + { srcDir, root, experimental }: Pick, fs: typeof fsMod = fsMod, ): ContentPaths { - const configStats = searchConfig(fs, srcDir, legacy?.collections); + const configStats = searchConfig(fs, srcDir); + + if (!configStats.exists) { + const legacyConfigStats = searchLegacyConfig(fs, srcDir); + if (legacyConfigStats.exists) { + const relativePath = path.relative(fileURLToPath(root), fileURLToPath(legacyConfigStats.url)); + throw new AstroError({ + ...AstroErrorData.LegacyContentConfigError, + message: AstroErrorData.LegacyContentConfigError.message(relativePath), + }); + } + } + const liveConfigStats = experimental?.liveContentCollections ? searchLiveConfig(fs, srcDir) : { exists: false, url: new URL('./', srcDir) }; @@ -765,19 +631,22 @@ export function getContentPaths( }; } -function searchConfig( - fs: typeof fsMod, - srcDir: URL, - legacy?: boolean, -): { exists: boolean; url: URL } { +function searchConfig(fs: typeof fsMod, srcDir: URL): { exists: boolean; url: URL } { const paths = [ - ...(legacy - ? [] - : ['content.config.mjs', 'content.config.js', 'content.config.mts', 'content.config.ts']), - 'content/config.mjs', + 'content.config.mjs', + 'content.config.js', + 'content.config.mts', + 'content.config.ts', + ]; + return search(fs, srcDir, paths); +} + +function searchLegacyConfig(fs: typeof fsMod, srcDir: URL): { exists: boolean; url: URL } { + const paths = [ + 'content/config.ts', 'content/config.js', + 'content/config.mjs', 'content/config.mts', - 'content/config.ts', ]; return search(fs, srcDir, paths); } @@ -830,13 +699,6 @@ export async function getEntrySlug({ return parseEntrySlug({ generatedSlug, frontmatterSlug, id, collection }); } -export function getExtGlob(exts: string[]) { - return exts.length === 1 - ? // Wrapping {...} breaks when there is only one extension - exts[0] - : `{${exts.join(',')}}`; -} - export function hasAssetPropagationFlag(id: string): boolean { try { return new URL(id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG); @@ -845,16 +707,6 @@ export function hasAssetPropagationFlag(id: string): boolean { } } -export function globWithUnderscoresIgnored(relContentDir: string, exts: string[]): string[] { - const extGlob = getExtGlob(exts); - const contentDir = relContentDir.length > 0 ? appendForwardSlash(relContentDir) : relContentDir; - return [ - `${contentDir}**/*${extGlob}`, - `!${contentDir}**/_*/**/*${extGlob}`, - `!${contentDir}**/_*${extGlob}`, - ]; -} - /** * Convert a platform path to a posix path. */ diff --git a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts index e244b6697ada..5c18488694f9 100644 --- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts +++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts @@ -1,9 +1,6 @@ import nodeFs from 'node:fs'; -import { extname } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { fileURLToPath } from 'node:url'; import { dataToEsm } from '@rollup/pluginutils'; -import pLimit from 'p-limit'; -import { glob } from 'tinyglobby'; import { normalizePath, type Plugin, type ViteDevServer } from 'vite'; import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { rootRelativePath } from '../core/viteUtils.js'; @@ -14,9 +11,7 @@ import { ASSET_IMPORTS_FILE, ASSET_IMPORTS_RESOLVED_STUB_ID, ASSET_IMPORTS_VIRTUAL_ID, - CONTENT_FLAG, CONTENT_RENDER_FLAG, - DATA_FLAG, DATA_STORE_VIRTUAL_ID, MODULES_IMPORTS_FILE, MODULES_MJS_ID, @@ -26,20 +21,7 @@ import { VIRTUAL_MODULE_ID, } from './consts.js'; import { getDataStoreFile } from './content-layer.js'; -import { - type ContentLookupMap, - getContentEntryIdAndSlug, - getContentPaths, - getDataEntryExts, - getDataEntryId, - getEntryCollectionName, - getEntryConfigByExtMap, - getEntrySlug, - getEntryType, - getExtGlob, - globWithUnderscoresIgnored, - isDeferredModule, -} from './utils.js'; +import { getContentPaths, isDeferredModule } from './utils.js'; interface AstroContentVirtualModPluginParams { settings: AstroSettings; @@ -129,17 +111,10 @@ export function astroContentVirtualModPlugin({ }, async load(id, args) { if (id === RESOLVED_VIRTUAL_MODULE_ID) { - const lookupMap = settings.config.legacy.collections - ? await generateLookupMap({ - settings, - fs, - }) - : {}; const isClient = !args?.ssr; const code = await generateContentEntryFile({ settings, fs, - lookupMap, isClient, }); @@ -213,40 +188,15 @@ export function astroContentVirtualModPlugin({ async function generateContentEntryFile({ settings, - lookupMap, isClient, }: { settings: AstroSettings; fs: typeof nodeFs; - lookupMap: ContentLookupMap; isClient: boolean; }) { const contentPaths = getContentPaths(settings.config); const relContentDir = rootRelativePath(settings.config.root, contentPaths.contentDir); - let contentEntryGlobResult = '""'; - let dataEntryGlobResult = '""'; - let renderEntryGlobResult = '""'; - if (settings.config.legacy.collections) { - const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes); - const contentEntryExts = [...contentEntryConfigByExt.keys()]; - const dataEntryExts = getDataEntryExts(settings); - const createGlob = (value: string[], flag: string) => - `import.meta.glob(${JSON.stringify(value)}, { query: { ${flag}: true } })`; - contentEntryGlobResult = createGlob( - globWithUnderscoresIgnored(relContentDir, contentEntryExts), - CONTENT_FLAG, - ); - dataEntryGlobResult = createGlob( - globWithUnderscoresIgnored(relContentDir, dataEntryExts), - DATA_FLAG, - ); - renderEntryGlobResult = createGlob( - globWithUnderscoresIgnored(relContentDir, contentEntryExts), - CONTENT_RENDER_FLAG, - ); - } - let virtualModContents: string; if (isClient) { throw new AstroError({ @@ -257,10 +207,6 @@ async function generateContentEntryFile({ virtualModContents = nodeFs .readFileSync(contentPaths.virtualModTemplate, 'utf-8') .replace('@@CONTENT_DIR@@', relContentDir) - .replace("'@@CONTENT_ENTRY_GLOB_PATH@@'", contentEntryGlobResult) - .replace("'@@DATA_ENTRY_GLOB_PATH@@'", dataEntryGlobResult) - .replace("'@@RENDER_ENTRY_GLOB_PATH@@'", renderEntryGlobResult) - .replace('/* @@LOOKUP_MAP_ASSIGNMENT@@ */', `lookupMap = ${JSON.stringify(lookupMap)};`) .replace( '/* @@LIVE_CONTENT_CONFIG@@ */', contentPaths.liveConfig.exists @@ -272,115 +218,3 @@ async function generateContentEntryFile({ return virtualModContents; } - -/** - * Generate a map from a collection + slug to the local file path. - * This is used internally to resolve entry imports when using `getEntry()`. - * @see `templates/content/module.mjs` - */ -async function generateLookupMap({ settings, fs }: { settings: AstroSettings; fs: typeof nodeFs }) { - const { root } = settings.config; - const contentPaths = getContentPaths(settings.config); - const relContentDir = rootRelativePath(root, contentPaths.contentDir, false); - - const contentEntryConfigByExt = getEntryConfigByExtMap(settings.contentEntryTypes); - const dataEntryExts = getDataEntryExts(settings); - - const { contentDir } = contentPaths; - - const contentEntryExts = [...contentEntryConfigByExt.keys()]; - - let lookupMap: ContentLookupMap = {}; - const contentGlob = await glob( - `${relContentDir}**/*${getExtGlob([...dataEntryExts, ...contentEntryExts])}`, - { - absolute: true, - cwd: fileURLToPath(root), - expandDirectories: false, - }, - ); - - // Run 10 at a time to prevent `await getEntrySlug` from accessing the filesystem all at once. - // Each await shouldn't take too long for the work to be noticeably slow too. - const limit = pLimit(10); - const promises: Promise[] = []; - - for (const filePath of contentGlob) { - promises.push( - limit(async () => { - const entryType = getEntryType(filePath, contentPaths, contentEntryExts, dataEntryExts); - // Globbed ignored or unsupported entry. - // Logs warning during type generation, should ignore in lookup map. - if (entryType !== 'content' && entryType !== 'data') return; - - const collection = getEntryCollectionName({ contentDir, entry: pathToFileURL(filePath) }); - if (!collection) throw UnexpectedLookupMapError; - - if (lookupMap[collection]?.type && lookupMap[collection].type !== entryType) { - throw new AstroError({ - ...AstroErrorData.MixedContentDataCollectionError, - message: AstroErrorData.MixedContentDataCollectionError.message(collection), - }); - } - - if (entryType === 'content') { - const contentEntryType = contentEntryConfigByExt.get(extname(filePath)); - if (!contentEntryType) throw UnexpectedLookupMapError; - - const { id, slug: generatedSlug } = getContentEntryIdAndSlug({ - entry: pathToFileURL(filePath), - contentDir, - collection, - }); - const slug = await getEntrySlug({ - id, - collection, - generatedSlug, - fs, - fileUrl: pathToFileURL(filePath), - contentEntryType, - }); - if (lookupMap[collection]?.entries?.[slug]) { - throw new AstroError({ - ...AstroErrorData.DuplicateContentEntrySlugError, - message: AstroErrorData.DuplicateContentEntrySlugError.message( - collection, - slug, - lookupMap[collection].entries[slug], - rootRelativePath(root, filePath), - ), - hint: - slug !== generatedSlug - ? `Check the \`slug\` frontmatter property in **${id}**.` - : undefined, - }); - } - lookupMap[collection] = { - type: 'content', - entries: { - ...lookupMap[collection]?.entries, - [slug]: rootRelativePath(root, filePath), - }, - }; - } else { - const id = getDataEntryId({ entry: pathToFileURL(filePath), contentDir, collection }); - lookupMap[collection] = { - type: 'data', - entries: { - ...lookupMap[collection]?.entries, - [id]: rootRelativePath(root, filePath), - }, - }; - } - }), - ); - } - - await Promise.all(promises); - return lookupMap; -} - -const UnexpectedLookupMapError = new AstroError({ - ...AstroErrorData.UnknownContentCollectionError, - message: `Unexpected error while parsing content entry IDs and slugs.`, -}); diff --git a/packages/astro/src/core/config/schemas/base.ts b/packages/astro/src/core/config/schemas/base.ts index 3b6d12f0d278..fa3c7dde5f4e 100644 --- a/packages/astro/src/core/config/schemas/base.ts +++ b/packages/astro/src/core/config/schemas/base.ts @@ -83,9 +83,7 @@ export const ASTRO_CONFIG_DEFAULTS = { integrations: [], markdown: markdownConfigDefaults, vite: {}, - legacy: { - collections: false, - }, + legacy: {}, redirects: {}, security: { checkOrigin: true, @@ -515,11 +513,7 @@ export const AstroConfigSchema = z.object({ `Invalid or outdated experimental feature.\nCheck for incorrect spelling or outdated Astro version.\nSee https://docs.astro.build/en/reference/experimental-flags/ for a list of all current experiments.`, ) .default({}), - legacy: z - .object({ - collections: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.legacy.collections), - }) - .default({}), + legacy: z.object({}).default({}), }); export type AstroConfigType = z.infer; diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index e8348355da46..386b19443426 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -1588,14 +1588,14 @@ export const RenderUndefinedEntryError = { /** * @docs * @description - * The `getDataEntryById` and `getEntryBySlug` functions are deprecated and cannot be used with content layer collections. Use the `getEntry` function instead. + * The `getDataEntryById` and `getEntryBySlug` functions are deprecated and cannot be used with content collections. Use the `getEntry` function instead. */ export const GetEntryDeprecationError = { name: 'GetEntryDeprecationError', title: 'Invalid use of `getDataEntryById` or `getEntryBySlug` function.', message: (collection: string, method: string) => `The \`${method}\` function is deprecated and cannot be used to query the "${collection}" collection. Use \`getEntry\` instead.`, - hint: 'Use the `getEntry` or `getCollection` functions to query content layer collections.', + hint: 'See https://docs.astro.build/en/guides/upgrade-to/v6/#removed-legacy-content-collections for more information.', } satisfies ErrorData; /** @@ -1610,6 +1610,7 @@ export const GetEntryDeprecationError = { * 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. * See the [Content collections documentation](https://docs.astro.build/en/guides/content-collections/) for more information. + * @deprecated This error only applies to legacy content collections which were removed in Astro 6. */ export const InvalidContentEntryFrontmatterError = { name: 'InvalidContentEntryFrontmatterError', @@ -1629,7 +1630,7 @@ export const InvalidContentEntryFrontmatterError = { * @docs * @message * **Example error message:**
- * **blog** → **post** frontmatter does not match collection schema.
+ * **blog** → **post** data does not match collection schema.
* "title" is required.
* "date" must be a valid date. * @description @@ -1651,6 +1652,60 @@ export const InvalidContentEntryDataError = { hint: 'See https://docs.astro.build/en/guides/content-collections/ for more information on content schemas.', } satisfies ErrorData; +/** + * @docs + * @message + * **Example error message:**
+ * Found legacy content config file in "src/content/config.ts". Please move this file to "src/content.config.ts" and ensure each collection has a loader defined.
+ * @description + * A legacy content config file was found. Move the file to `src/content.config.ts` and update any collection definitions if needed. + * See the [Astro 6 migration guide](https://docs.astro.build/en/guides/upgrade-to/v6/#removed-legacy-content-collections) for more information. + */ + +export const LegacyContentConfigError = { + name: 'LegacyContentConfigError', + title: 'Legacy content config file found.', + message: (filename: string) => + `Found legacy content config file in "${filename}". Please move this file to "src/content.config.${filename.split('.').at(-1)}" and ensure each collection has a loader defined.`, + hint: 'See https://docs.astro.build/en/guides/upgrade-to/v6/#removed-legacy-content-collections for more information on updating collections.', +} satisfies ErrorData; + +/** + * @docs + * @message + * **Example error message:**
+ * Collections must have a `loader` defined. Check your collection definitions in your content config file.
+ * @description + * A content collection is missing a `loader` definition. Make sure that each collection in your content config file has a `loader`. + * See the [Content collections documentation](https://docs.astro.build/en/guides/content-collections/) for more information. + */ + +export const ContentCollectionMissingLoader = { + name: 'ContentCollectionMissingLoader', + title: 'Content collection is missing a `loader` definition.', + message: (file = 'your content config file') => + `Collections must have a \`loader\` defined. Check your collection definitions in ${file}.`, + hint: 'See https://docs.astro.build/en/guides/content-collections/ for more information on content loaders and https://docs.astro.build/en/guides/upgrade-to/v6/#removed-legacy-content-collections for more information on migrating from legacy collections.', +} satisfies ErrorData; + +/** + * @docs + * @message + * **Example error message:**
+ * Invalid collection type "data". Remove the type from your collection definition in your content config file. + * @description + * Content collections should no longer have a `type` field. Remove this field from your content config file. + * See the [Astro 6 migration guide](https://docs.astro.build/en/guides/upgrade-to/v6/#removed-legacy-content-collections) for more information. + */ + +export const ContentCollectionInvalidType = { + name: 'ContentCollectionInvalidType', + title: 'Content collection has an invalid `type` field.', + message: (type: string, file = 'your content config file') => + `Invalid collection type "${type}". Remove the type from your collection definition in ${file}.`, + hint: 'See https://docs.astro.build/en/guides/upgrade-to/v6/#removed-legacy-content-collections for more information on migrating from legacy collections.', +} satisfies ErrorData; + /** * @docs * @message diff --git a/packages/astro/src/core/sync/index.ts b/packages/astro/src/core/sync/index.ts index deab6e6e3db6..d5a71016c637 100644 --- a/packages/astro/src/core/sync/index.ts +++ b/packages/astro/src/core/sync/index.ts @@ -169,12 +169,7 @@ export async function syncInternal({ settings.timer.end('Sync content layer'); } else { const paths = getContentPaths(settings.config, fs); - if ( - paths.config.exists || - paths.liveConfig.exists || - // Legacy collections don't require a config file - (settings.config.legacy?.collections && fs.existsSync(paths.contentDir)) - ) { + if (paths.config.exists || paths.liveConfig.exists) { // We only create the reference, without a stub to avoid overriding the // already generated types settings.injectedTypes.push({ @@ -273,20 +268,12 @@ async function syncContentCollections( settings, viteServer: tempViteServer, }); - const typesResult = await contentTypesGenerator.init(); + await contentTypesGenerator.init(); const contentConfig = globalContentConfigObserver.get(); if (contentConfig.status === 'error') { throw contentConfig.error; } - - if (typesResult.typesGenerated === false) { - switch (typesResult.reason) { - case 'no-content-dir': - default: - logger.debug('types', 'No content directory found. Skipping type generation.'); - } - } } catch (e) { const safeError = createSafeError(e) as ErrorWithMetadata; if (isAstroError(e)) { diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index 59bfe4b1729e..9efc3504195a 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -1995,48 +1995,7 @@ export interface ViteUserConfig extends OriginalViteUserConfig { * These flags allow you to opt in to some deprecated or otherwise outdated behavior of Astro * in the latest version, so that you can continue to upgrade and take advantage of new Astro releases. */ - legacy?: { - /** - * - * @name legacy.collections - * @type {boolean} - * @default `false` - * @version 5.0.0 - * @description - * Enable legacy behavior for content collections. - * - * ```js - * // astro.config.mjs - * import { defineConfig } from 'astro/config'; - * export default defineConfig({ - * legacy: { - * collections: true - * } - * }); - * ``` - * - * If enabled, `data` and `content` collections (only) are handled using the legacy content collections implementation. Collections with a `loader` (only) will continue to use the Content Layer API instead. Both kinds of collections may exist in the same project, each using their respective implementations. - * - * The following limitations continue to exist: - * - * - Any legacy (`type: 'content'` or `type: 'data'`) collections must continue to be located in the `src/content/` directory. - * - These legacy collections will not be transformed to implicitly use the `glob()` loader, and will instead be handled by legacy code. - * - Collections using the Content Layer API (with a `loader` defined) are forbidden in `src/content/`, but may exist anywhere else in your project. - * - * 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 - * import { defineCollection, z } from 'astro:content'; - * - * const blog = defineCollection({ }) - * - * export const collections = { blog }; - * ``` - * - */ - collections?: boolean; - }; + legacy?: Record; // Currently no legacy flags are available. /** * diff --git a/packages/astro/src/virtual-modules/live-config.ts b/packages/astro/src/virtual-modules/live-config.ts index 989761b8e98c..687aab2d6c01 100644 --- a/packages/astro/src/virtual-modules/live-config.ts +++ b/packages/astro/src/virtual-modules/live-config.ts @@ -17,8 +17,6 @@ function createErrorFunction(message: string) { export const getCollection = createErrorFunction('getCollection'); export const render = createErrorFunction('render'); export const getEntry = createErrorFunction('getEntry'); -export const getEntryBySlug = createErrorFunction('getEntryBySlug'); -export const getDataEntryById = createErrorFunction('getDataEntryById'); export const getEntries = createErrorFunction('getEntries'); export const reference = createErrorFunction('reference'); export const getLiveCollection = createErrorFunction('getLiveCollection'); diff --git a/packages/astro/templates/content/module.mjs b/packages/astro/templates/content/module.mjs index 327c4a144376..00e4496c8d9c 100644 --- a/packages/astro/templates/content/module.mjs +++ b/packages/astro/templates/content/module.mjs @@ -1,11 +1,9 @@ // astro-head-inject import { - createCollectionToGlobResultMap, + createDeprecatedFunction, createGetCollection, - createGetDataEntryById, createGetEntries, createGetEntry, - createGetEntryBySlug, createGetLiveCollection, createGetLiveEntry, createReference, @@ -20,76 +18,17 @@ export { z } from 'astro/zod'; /* @@LIVE_CONTENT_CONFIG@@ */ -const contentDir = '@@CONTENT_DIR@@'; - -const contentEntryGlob = '@@CONTENT_ENTRY_GLOB_PATH@@'; -const contentCollectionToEntryMap = createCollectionToGlobResultMap({ - globResult: contentEntryGlob, - contentDir, -}); - -const dataEntryGlob = '@@DATA_ENTRY_GLOB_PATH@@'; -const dataCollectionToEntryMap = createCollectionToGlobResultMap({ - globResult: dataEntryGlob, - contentDir, -}); -const collectionToEntryMap = createCollectionToGlobResultMap({ - globResult: { ...contentEntryGlob, ...dataEntryGlob }, - contentDir, -}); - -let lookupMap = {}; -/* @@LOOKUP_MAP_ASSIGNMENT@@ */ - -const collectionNames = new Set(Object.keys(lookupMap)); - -function createGlobLookup(glob) { - return async (collection, lookupId) => { - const filePath = lookupMap[collection]?.entries[lookupId]; - - if (!filePath) return undefined; - return glob[collection][filePath]; - }; -} - -const renderEntryGlob = '@@RENDER_ENTRY_GLOB_PATH@@'; -const collectionToRenderEntryMap = createCollectionToGlobResultMap({ - globResult: renderEntryGlob, - contentDir, -}); - -const cacheEntriesByCollection = new Map(); export const getCollection = createGetCollection({ - contentCollectionToEntryMap, - dataCollectionToEntryMap, - getRenderEntryImport: createGlobLookup(collectionToRenderEntryMap), - cacheEntriesByCollection, liveCollections, }); export const getEntry = createGetEntry({ - getEntryImport: createGlobLookup(collectionToEntryMap), - getRenderEntryImport: createGlobLookup(collectionToRenderEntryMap), - collectionNames, liveCollections, }); -export const getEntryBySlug = createGetEntryBySlug({ - getEntryImport: createGlobLookup(contentCollectionToEntryMap), - getRenderEntryImport: createGlobLookup(collectionToRenderEntryMap), - collectionNames, - getEntry, -}); - -export const getDataEntryById = createGetDataEntryById({ - getEntryImport: createGlobLookup(dataCollectionToEntryMap), - collectionNames, - getEntry, -}); - export const getEntries = createGetEntries(getEntry); -export const reference = createReference({ lookupMap }); +export const reference = createReference(); export const getLiveCollection = createGetLiveCollection({ liveCollections, @@ -98,3 +37,9 @@ export const getLiveCollection = createGetLiveCollection({ export const getLiveEntry = createGetLiveEntry({ liveCollections, }); + +// TODO: remove in Astro 7 +export const getEntryBySlug = createDeprecatedFunction('getEntryBySlug'); + +// TODO: remove in Astro 7 +export const getDataEntryById = createDeprecatedFunction('getDataEntryById'); diff --git a/packages/astro/templates/content/types.d.ts b/packages/astro/templates/content/types.d.ts index 291d49a54e16..12d008e3d6e3 100644 --- a/packages/astro/templates/content/types.d.ts +++ b/packages/astro/templates/content/types.d.ts @@ -20,16 +20,10 @@ declare module 'astro:content' { declare module 'astro:content' { type Flatten = T extends { [K: string]: infer U } ? U : never; - export type CollectionKey = keyof AnyEntryMap; - export type CollectionEntry = Flatten; - - export type ContentCollectionKey = keyof ContentEntryMap; - export type DataCollectionKey = keyof DataEntryMap; + export type CollectionKey = keyof DataEntryMap; + export type CollectionEntry = Flatten; type AllValuesOf = T extends any ? T[keyof T] : never; - type ValidContentEntrySlug = AllValuesOf< - ContentEntryMap[C] - >['slug']; export type ReferenceDataEntry< C extends CollectionKey, @@ -38,41 +32,17 @@ declare module 'astro:content' { collection: C; id: E; }; - export type ReferenceContentEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}) = string, - > = { - collection: C; - slug: E; - }; + export type ReferenceLiveEntry = { collection: C; id: string; }; - /** @deprecated Use `getEntry` instead. */ - export function getEntryBySlug< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - collection: C, - // Note that this has to accept a regular string too, for SSR - entrySlug: E, - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; - - /** @deprecated Use `getEntry` instead. */ - export function getDataEntryById( - collection: C, - entryId: E, - ): Promise>; - - export function getCollection>( + export function getCollection>( collection: C, filter?: (entry: CollectionEntry) => entry is E, ): Promise; - export function getCollection( + export function getCollection( collection: C, filter?: (entry: CollectionEntry) => unknown, ): Promise[]>; @@ -84,14 +54,6 @@ declare module 'astro:content' { import('astro').LiveDataCollectionResult, LiveLoaderErrorType> >; - export function getEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - entry: ReferenceContentEntry, - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; export function getEntry< C extends keyof DataEntryMap, E extends keyof DataEntryMap[C] | (string & {}), @@ -100,15 +62,6 @@ declare module 'astro:content' { ): E extends keyof DataEntryMap[C] ? Promise : Promise | undefined>; - export function getEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - collection: C, - slug: E, - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; export function getEntry< C extends keyof DataEntryMap, E extends keyof DataEntryMap[C] | (string & {}), @@ -126,24 +79,19 @@ declare module 'astro:content' { ): Promise, LiveLoaderErrorType>>; /** Resolve an array of entry references from the same collection */ - export function getEntries( - entries: ReferenceContentEntry>[], - ): Promise[]>; export function getEntries( entries: ReferenceDataEntry[], ): Promise[]>; - export function render( - entry: AnyEntryMap[C][string], + export function render( + entry: DataEntryMap[C][string], ): Promise; - export function reference( + export function reference( collection: C, ): import('astro/zod').ZodEffects< import('astro/zod').ZodString, - C extends keyof ContentEntryMap - ? ReferenceContentEntry> - : ReferenceDataEntry + ReferenceDataEntry >; // Allow generic `string` to avoid excessive type errors in the config // if `dev` is not running to update as you edit. @@ -153,20 +101,14 @@ declare module 'astro:content' { ): import('astro/zod').ZodEffects; type ReturnTypeOrOriginal = T extends (...args: any[]) => infer R ? R : T; - type InferEntrySchema = import('astro/zod').infer< + type InferEntrySchema = import('astro/zod').infer< ReturnTypeOrOriginal['schema']> >; - type ContentEntryMap = { - // @@CONTENT_ENTRY_MAP@@ - }; - type DataEntryMap = { // @@DATA_ENTRY_MAP@@ }; - type AnyEntryMap = ContentEntryMap & DataEntryMap; - type ExtractLoaderTypes = T extends import('astro/loaders').LiveLoader< infer TData, infer TEntryFilter, @@ -175,7 +117,6 @@ declare module 'astro:content' { > ? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError } : { data: never; entryFilter: never; collectionFilter: never; error: never }; - type ExtractDataType = ExtractLoaderTypes['data']; type ExtractEntryFilterType = ExtractLoaderTypes['entryFilter']; type ExtractCollectionFilterType = ExtractLoaderTypes['collectionFilter']; type ExtractErrorType = ExtractLoaderTypes['error']; diff --git a/packages/astro/test/astro-sync.test.js b/packages/astro/test/astro-sync.test.js index 3e5a9bcbf330..43a00b355fa2 100644 --- a/packages/astro/test/astro-sync.test.js +++ b/packages/astro/test/astro-sync.test.js @@ -150,9 +150,7 @@ describe('astro sync', () => { '.astro/content.d.ts', `"blog": Record; rendered?: RenderedContent; @@ -172,13 +170,6 @@ describe('astro sync', () => { 'Types file does not include empty collection type', ); }); - - it('does not write individual types for entries when emulating legacy collections', async () => { - await fixture.load('./fixtures/content-collections/'); - fixture.clean(); - await fixture.whenSyncing(); - fixture.thenFileContentShouldNotInclude('.astro/content.d.ts', 'id: "one.md"'); - }); }); describe('astro:env', () => { diff --git a/packages/astro/test/config-vite.test.js b/packages/astro/test/config-vite.test.js index 90d3487f9f1b..f14712b2d724 100644 --- a/packages/astro/test/config-vite.test.js +++ b/packages/astro/test/config-vite.test.js @@ -1,5 +1,6 @@ import assert from 'node:assert/strict'; -import { before, describe, it } from 'node:test'; +import { after, before, describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; import * as cheerio from 'cheerio'; import { defaultClientConditions, resolveConfig } from 'vite'; import { getViteConfig } from '../dist/config/index.js'; @@ -25,6 +26,12 @@ describe('Vite Config', async () => { }); describe('getViteConfig', () => { + let originalCwd; + before(() => { + originalCwd = process.cwd(); + // We chdir because otherwise it sets the wrong root in the site config + process.chdir(fileURLToPath(new URL('./fixtures/config-vite/', import.meta.url))); + }); it('Does not change the default config.', async () => { const command = 'serve'; const mode = 'test'; @@ -36,4 +43,7 @@ describe('getViteConfig', () => { 'astro', ]); }); + after(() => { + process.chdir(originalCwd); + }); }); diff --git a/packages/astro/test/content-collection-references.test.js b/packages/astro/test/content-collection-references.test.js index 8e5510cebf03..58fd163ac31e 100644 --- a/packages/astro/test/content-collection-references.test.js +++ b/packages/astro/test/content-collection-references.test.js @@ -82,17 +82,15 @@ describe('Content Collections - references', () => { body: fixLineEndings(body).trim(), })); assert.deepEqual( - topLevelInfo.map(({ id, slug, body, collection }) => ({ id, slug, body, collection })), + topLevelInfo.map(({ id, body, collection }) => ({ id, body, collection })), [ { - id: 'related-1.md', - slug: 'related-1', + id: 'related-1', body: '# Related post 1\n\nThis is related to the welcome post.', collection: 'blog', }, { - id: 'related-2.md', - slug: 'related-2', + id: 'related-2', body: '# Related post 2\n\nThis is related to the welcome post.', collection: 'blog', }, diff --git a/packages/astro/test/content-collections-render.test.js b/packages/astro/test/content-collections-render.test.js index 31ed04a15ae5..259034218721 100644 --- a/packages/astro/test/content-collections-render.test.js +++ b/packages/astro/test/content-collections-render.test.js @@ -37,25 +37,6 @@ describe('Content Collections - render()', () => { assert.equal($('link[rel=stylesheet]').length, 0); }); - it('De-duplicates CSS used both in layout and directly in target page', async () => { - const html = await fixture.readFile('/with-layout-prop/index.html'); - const $ = cheerio.load(html); - - const set = new Set(); - - $('link[rel=stylesheet]').each((_, linkEl) => { - const href = linkEl.attribs.href; - assert.equal(set.has(href), false); - set.add(href); - }); - - $('style').each((_, styleEl) => { - const textContent = styleEl.children[0].data; - assert.equal(set.has(textContent), false); - set.add(textContent); - }); - }); - it('Includes component scripts for rendered entry', async () => { const html = await fixture.readFile('/launch-week-component-scripts/index.html'); const $ = cheerio.load(html); @@ -165,7 +146,7 @@ describe('Content Collections - render()', () => { let response = await app.render(request); let html = await response.text(); let $ = cheerio.load(html); - assert.equal($('li').first().text(), 'With Layout Prop'); + assert.equal($('li').first().text(), 'Launch week!'); request = new Request('http://example.com/'); response = await app.render(request); @@ -232,7 +213,7 @@ describe('Content Collections - render()', () => { assert.equal(h2.attr('data-components-export-applied'), 'true'); }); - it('Supports layout prop with recursive getCollection() call', async () => { + it.skip('Supports layout prop with recursive getCollection() call - DEPRECATED: layout props removed in Astro 5', async () => { const response = await fixture.fetch('/with-layout-prop', { method: 'GET' }); assert.equal(response.status, 200); diff --git a/packages/astro/test/content-collections.test.js b/packages/astro/test/content-collections.test.js index 70383e728ccb..d01f9f05ec6b 100644 --- a/packages/astro/test/content-collections.test.js +++ b/packages/astro/test/content-collections.test.js @@ -21,47 +21,13 @@ describe('Content Collections', () => { json = devalue.parse(rawJson); }); - it('Returns `without config` collection', async () => { - assert.ok(json.hasOwnProperty('withoutConfig')); - assert.equal(Array.isArray(json.withoutConfig), true); - - const ids = json.withoutConfig.map((item) => item.id); - assert.deepEqual( - ids.sort(), - [ - 'columbia.md', - 'endeavour.md', - 'enterprise.md', - // Spaces allowed in IDs - 'promo/launch week.mdx', - ].sort(), - ); - }); - - it('Handles spaces in `without config` slugs', async () => { - assert.ok(json.hasOwnProperty('withoutConfig')); - assert.equal(Array.isArray(json.withoutConfig), true); - - const slugs = json.withoutConfig.map((item) => item.slug); - assert.deepEqual( - slugs.sort(), - [ - 'columbia', - 'endeavour', - 'enterprise', - // "launch week.mdx" is converted to "launch-week.mdx" - 'promo/launch-week', - ].sort(), - ); - }); - it('Returns `with schema` collection', async () => { assert.ok(json.hasOwnProperty('withSchemaConfig')); assert.equal(Array.isArray(json.withSchemaConfig), true); const ids = json.withSchemaConfig.map((item) => item.id); const publishedDates = json.withSchemaConfig.map((item) => item.data.publishedAt); - assert.deepEqual(ids.sort(), ['four%.md', 'one.md', 'three.md', 'two.md'].sort()); + assert.deepEqual(ids.sort(), ['four%', 'one', 'three', 'two'].sort()); assert.equal( publishedDates.every((date) => date instanceof Date), true, @@ -79,10 +45,10 @@ describe('Content Collections', () => { }); it('Returns `with custom slugs` collection', async () => { - assert.ok(json.hasOwnProperty('withSlugConfig')); - assert.equal(Array.isArray(json.withSlugConfig), true); + assert.ok(json.hasOwnProperty('withCustomSlugs')); + assert.equal(Array.isArray(json.withCustomSlugs), true); - const slugs = json.withSlugConfig.map((item) => item.slug); + const slugs = json.withCustomSlugs.map((item) => item.id); assert.deepEqual(slugs.sort(), ['fancy-one', 'excellent-three', 'interesting-two'].sort()); }); @@ -90,14 +56,14 @@ describe('Content Collections', () => { assert.ok(json.hasOwnProperty('withUnionSchema')); assert.equal(Array.isArray(json.withUnionSchema), true); - const post = json.withUnionSchema.find((item) => item.id === 'post.md'); + const post = json.withUnionSchema.find((item) => item.id === 'post'); assert.notEqual(post, undefined); assert.deepEqual(post.data, { type: 'post', title: 'My Post', description: 'This is my post', }); - const newsletter = json.withUnionSchema.find((item) => item.id === 'newsletter.md'); + const newsletter = json.withUnionSchema.find((item) => item.id === 'newsletter'); assert.notEqual(newsletter, undefined); assert.deepEqual(newsletter.data, { type: 'newsletter', @@ -109,9 +75,9 @@ describe('Content Collections', () => { assert.ok(json.hasOwnProperty('withSymlinkedContent')); assert.equal(Array.isArray(json.withSymlinkedContent), true); const ids = json.withSymlinkedContent.map((item) => item.id); - assert.deepEqual(ids.sort(), ['first.md', 'second.md', 'third.md'].sort()); + assert.deepEqual(ids.sort(), ['first', 'second', 'third'].sort()); assert.equal( - json.withSymlinkedContent.find(({ id }) => id === 'first.md').data.title, + json.withSymlinkedContent.find(({ id }) => id === 'first').data.title, 'First Blog', ); }); @@ -147,7 +113,7 @@ describe('Content Collections', () => { it('Returns `with schema` collection entry', async () => { assert.ok(json.hasOwnProperty('oneWithSchemaConfig')); - assert.equal(json.oneWithSchemaConfig.id, 'one.md'); + assert.equal(json.oneWithSchemaConfig.id, 'one'); assert.equal(json.oneWithSchemaConfig.data.publishedAt instanceof Date, true); assert.equal( json.oneWithSchemaConfig.data.publishedAt.toISOString(), @@ -156,13 +122,13 @@ describe('Content Collections', () => { }); it('Returns `with custom slugs` collection entry', async () => { - assert.ok(json.hasOwnProperty('twoWithSlugConfig')); - assert.equal(json.twoWithSlugConfig.slug, 'interesting-two'); + assert.ok(json.hasOwnProperty('twoWithCustomSlugs')); + assert.equal(json.twoWithCustomSlugs.id, 'interesting-two'); }); it('Returns `with union schema` collection entry', async () => { assert.ok(json.hasOwnProperty('postWithUnionSchema')); - assert.equal(json.postWithUnionSchema.id, 'post.md'); + assert.equal(json.postWithUnionSchema.id, 'post'); assert.deepEqual(json.postWithUnionSchema.data, { type: 'post', title: 'My Post', @@ -256,8 +222,8 @@ describe('Content Collections', () => { assert.equal(error, null); }); }); - describe('With config.mjs', () => { - it("Errors when frontmatter doesn't match schema", async () => { + describe('With legacy config', () => { + it('Throws if legacy config file is found', async () => { const fixture = await loadFixture({ root: './fixtures/content-collections-with-config-mjs/', }); @@ -267,21 +233,10 @@ describe('Content Collections', () => { } catch (e) { error = e.message; } - assert.match(error, /\*\*title\*\*: Expected type `"string"`, received `"number"`/); - }); - }); - describe('With config.mts', () => { - it("Errors when frontmatter doesn't match schema", async () => { - const fixture = await loadFixture({ - root: './fixtures/content-collections-with-config-mts/', - }); - let error; - try { - await fixture.build({ force: true }); - } catch (e) { - error = e.message; - } - assert.match(error, /\*\*title\*\*: Expected type `"string"`, received `"number"`/); + assert.equal( + error, + 'Found legacy content config file in "src/content/config.mjs". Please move this file to "src/content.config.mjs" and ensure each collection has a loader defined.', + ); }); }); @@ -382,6 +337,14 @@ describe('Content Collections', () => { ); } }); + + it('Throws deprecation error when using getEntryBySlug', async () => { + const request = new Request('http://example.com/deprecated-getentrybyslug'); + const response = await app.render(request); + + // Should return 500 status when getEntryBySlug is used with new content collections + assert.equal(response.status, 500); + }); }); describe('Base configuration', () => { diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js index 9be37c7bef6a..e88533fffcc3 100644 --- a/packages/astro/test/core-image.test.js +++ b/packages/astro/test/core-image.test.js @@ -600,12 +600,12 @@ describe('astro:image', () => { it('Adds the tags', () => { let $img = $('img'); - assert.equal($img.length, 8); + assert.equal($img.length, 7); }); it('image in cc folder is processed', () => { let $imgs = $('img'); - let $blogfolderimg = $($imgs[7]); + let $blogfolderimg = $($imgs[6]); assert.equal($blogfolderimg.attr('src').includes('blogfolder.jpg'), true); assert.equal($blogfolderimg.attr('src').endsWith('f=webp'), true); }); @@ -615,11 +615,6 @@ describe('astro:image', () => { assert.equal($img.attr('src').startsWith('/'), true); }); - it('has proper source for refined image', () => { - let $img = $('#refined-image img'); - assert.equal($img.attr('src').startsWith('/'), true); - }); - it('has proper sources for array of images', () => { let $img = $('#array-of-images img'); const imgsSrcs = []; @@ -806,7 +801,11 @@ describe('astro:image', () => { await res.text(); assert.equal(logs.length, 1); - assert.equal(logs[0].message.includes('does not exist. Is the path correct?'), true); + assert.ok( + logs[0].message.includes( + 'Could not find requested image `./does-not-exist.jpg`. Does it exist\?', + ), + ); }); it('properly error image in Markdown content is not found', async () => { diff --git a/packages/astro/test/css-inline-stylesheets.test.js b/packages/astro/test/css-inline-stylesheets.test.js index 7bae334e14cc..a4dd33c5adba 100644 --- a/packages/astro/test/css-inline-stylesheets.test.js +++ b/packages/astro/test/css-inline-stylesheets.test.js @@ -110,11 +110,10 @@ describe('Setting inlineStylesheets to auto in static output', () => { it('Renders some - - -
-
- - diff --git a/packages/astro/test/fixtures/legacy-content-collections/src/pages/propagation.astro b/packages/astro/test/fixtures/legacy-content-collections/src/pages/propagation.astro deleted file mode 100644 index 3775697acd00..000000000000 --- a/packages/astro/test/fixtures/legacy-content-collections/src/pages/propagation.astro +++ /dev/null @@ -1,22 +0,0 @@ ---- -import { getCollection } from "astro:content"; -const posts = await getCollection("with-schema-config"); ---- - - - -
-
Hello World
- Styles? -
- - - - diff --git a/packages/astro/test/fixtures/legacy-content-collections/src/pages/with-scripts/[...slug].astro b/packages/astro/test/fixtures/legacy-content-collections/src/pages/with-scripts/[...slug].astro deleted file mode 100644 index 893cbb9c61ff..000000000000 --- a/packages/astro/test/fixtures/legacy-content-collections/src/pages/with-scripts/[...slug].astro +++ /dev/null @@ -1,21 +0,0 @@ ---- -import { getCollection } from 'astro:content'; - -export async function getStaticPaths() { - const blogEntries = await getCollection('with-scripts'); - return blogEntries.map(entry => ({ - params: { slug: entry.slug }, props: { entry }, - })); -} - -const { entry } = Astro.props; - -const { Content } = await entry.render(); -const { title } = entry.data; ---- - -
-

This is a content collection post

-

{title}

- -
diff --git a/packages/astro/test/fixtures/legacy-content-collections/src/utils.js b/packages/astro/test/fixtures/legacy-content-collections/src/utils.js deleted file mode 100644 index 3a6244327862..000000000000 --- a/packages/astro/test/fixtures/legacy-content-collections/src/utils.js +++ /dev/null @@ -1,8 +0,0 @@ -export function stripRenderFn(entryWithRender) { - const { render, ...entry } = entryWithRender; - return entry; -} - -export function stripAllRenderFn(collection = []) { - return collection.map(stripRenderFn); -} diff --git a/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/first.md b/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/first.md deleted file mode 100644 index 0ecb2d8587b0..000000000000 --- a/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/first.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "First Blog" -date: 2024-04-05 ---- - -First blog content. diff --git a/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/second.md b/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/second.md deleted file mode 100644 index dcded99ccf63..000000000000 --- a/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/second.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "Second Blog" -date: 2024-04-06 ---- - -Second blog content. diff --git a/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/third.md b/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/third.md deleted file mode 100644 index 1adee317378b..000000000000 --- a/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/content-collection/third.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "Third Blog" -date: 2024-04-07 ---- - -Third blog content. diff --git a/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/data-collection/welcome.json b/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/data-collection/welcome.json deleted file mode 100644 index 8ab06ff1f24f..000000000000 --- a/packages/astro/test/fixtures/legacy-content-collections/symlinked-collections/data-collection/welcome.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "alt": "Futuristic landscape with chrome buildings and blue skies", - "src": "../../assets/the-future.jpg" -} diff --git a/packages/astro/test/fixtures/legacy-data-collections/astro.config.mjs b/packages/astro/test/fixtures/legacy-data-collections/astro.config.mjs deleted file mode 100644 index 3f4722ff1bb4..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/astro.config.mjs +++ /dev/null @@ -1,9 +0,0 @@ -// @ts-check -import { defineConfig } from 'astro/config'; - -// https://astro.build/config -export default defineConfig({ - legacy: { - collections: true, - }, -}); diff --git a/packages/astro/test/fixtures/legacy-data-collections/package.json b/packages/astro/test/fixtures/legacy-data-collections/package.json deleted file mode 100644 index 48fdc63bed82..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "@test/legacy-data-collections", - "type": "module", - "version": "0.0.1", - "private": true, - "scripts": { - "dev": "astro dev", - "build": "astro build", - "preview": "astro preview", - "astro": "astro" - }, - "dependencies": { - "astro": "workspace:*" - } -} diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Ben Holmes.yml b/packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Ben Holmes.yml deleted file mode 100644 index 54e6743d96cc..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Ben Holmes.yml +++ /dev/null @@ -1,2 +0,0 @@ -name: Ben J Holmes -twitter: https://twitter.com/bholmesdev diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Fred K Schott.yml b/packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Fred K Schott.yml deleted file mode 100644 index 0b51067d9529..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Fred K Schott.yml +++ /dev/null @@ -1,2 +0,0 @@ -name: Fred K Schott -twitter: https://twitter.com/FredKSchott diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Nate Moore.yml b/packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Nate Moore.yml deleted file mode 100644 index 953f348a08f8..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/content/authors-without-config/Nate Moore.yml +++ /dev/null @@ -1,2 +0,0 @@ -name: Nate Something Moore -twitter: https://twitter.com/n_moore diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/content/config.ts b/packages/astro/test/fixtures/legacy-data-collections/src/content/config.ts deleted file mode 100644 index 5f3de9423806..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/content/config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { defineCollection, z } from 'astro:content'; - -const docs = defineCollection({ - type: 'content', - schema: z.object({ - title: z.string(), - }) -}); - -const i18n = defineCollection({ - type: 'data', - schema: z.object({ - homepage: z.object({ - greeting: z.string(), - preamble: z.string(), - }) - }), -}); - -export const collections = { docs, i18n }; diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/content/docs/example.md b/packages/astro/test/fixtures/legacy-data-collections/src/content/docs/example.md deleted file mode 100644 index 356e65f64b6a..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/content/docs/example.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: The future of content ---- diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/en.json b/packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/en.json deleted file mode 100644 index 51d127f4a744..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/en.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "homepage": { - "greeting": "Hello World!", - "preamble": "Welcome to the future of content." - } -} diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/es.json b/packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/es.json deleted file mode 100644 index bf4c7af0fd05..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/es.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "homepage": { - "greeting": "¡Hola Mundo!", - "preamble": "Bienvenido al futuro del contenido." - } -} diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/fr.yaml b/packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/fr.yaml deleted file mode 100644 index 90a86d411f6e..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/content/i18n/fr.yaml +++ /dev/null @@ -1,3 +0,0 @@ -homepage: - greeting: "Bonjour le monde!" - preamble: "Bienvenue dans le futur du contenu." diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/pages/authors/[id].json.js b/packages/astro/test/fixtures/legacy-data-collections/src/pages/authors/[id].json.js deleted file mode 100644 index 8d5365a2eb2d..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/pages/authors/[id].json.js +++ /dev/null @@ -1,18 +0,0 @@ -import { getEntry } from 'astro:content'; - -const ids = ['Ben Holmes', 'Fred K Schott', 'Nate Moore']; - -export function getStaticPaths() { - return ids.map((id) => ({ params: { id } })); -} - -/** @param {import('astro').APIContext} params */ -export async function GET({ params }) { - const { id } = params; - const author = await getEntry('authors-without-config', id); - if (!author) { - return Response.json({ error: `Author ${id} Not found` }); - } else { - return Response.json(author); - } -} diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/pages/authors/all.json.js b/packages/astro/test/fixtures/legacy-data-collections/src/pages/authors/all.json.js deleted file mode 100644 index 79dd8cd9dd3c..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/pages/authors/all.json.js +++ /dev/null @@ -1,6 +0,0 @@ -import { getCollection } from 'astro:content'; - -export async function GET() { - const authors = await getCollection('authors-without-config'); - return Response.json(authors); -} diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/[lang].json.js b/packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/[lang].json.js deleted file mode 100644 index c6b0cfff669b..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/[lang].json.js +++ /dev/null @@ -1,18 +0,0 @@ -import { getEntry } from 'astro:content'; - -const langs = ['en', 'es', 'fr']; - -export function getStaticPaths() { - return langs.map((lang) => ({ params: { lang } })); -} - -/** @param {import('astro').APIContext} params */ -export async function GET({ params }) { - const { lang } = params; - const translations = await getEntry('i18n', lang); - if (!translations) { - return Response.json({ error: `Translation ${lang} Not found` }); - } else { - return Response.json(translations); - } -} diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/all.json.js b/packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/all.json.js deleted file mode 100644 index f1ebb15b761e..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/all.json.js +++ /dev/null @@ -1,6 +0,0 @@ -import { getCollection } from 'astro:content'; - -export async function GET() { - const translations = await getCollection('i18n'); - return Response.json(translations); -} diff --git a/packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/by-id.json.js b/packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/by-id.json.js deleted file mode 100644 index 5f71c80e9f7b..000000000000 --- a/packages/astro/test/fixtures/legacy-data-collections/src/pages/translations/by-id.json.js +++ /dev/null @@ -1,6 +0,0 @@ -import { getDataEntryById } from 'astro:content'; - -export async function GET() { - const item = await getDataEntryById('i18n', 'en'); - return Response.json(item); -} diff --git a/packages/astro/test/legacy-content-collections.test.js b/packages/astro/test/legacy-content-collections.test.js deleted file mode 100644 index e4327ceb3de9..000000000000 --- a/packages/astro/test/legacy-content-collections.test.js +++ /dev/null @@ -1,456 +0,0 @@ -import assert from 'node:assert/strict'; -import { before, describe, it } from 'node:test'; -import * as cheerio from 'cheerio'; -import * as devalue from 'devalue'; -import testAdapter from './test-adapter.js'; -import { preventNodeBuiltinDependencyPlugin } from './test-plugins.js'; -import { loadFixture } from './test-utils.js'; - -describe('Legacy Content Collections', () => { - describe('Query', () => { - let fixture; - before(async () => { - fixture = await loadFixture({ root: './fixtures/legacy-content-collections/' }); - await fixture.build(); - }); - - describe('Collection', () => { - let json; - before(async () => { - const rawJson = await fixture.readFile('/collections.json'); - json = devalue.parse(rawJson); - }); - - it('Returns `without config` collection', async () => { - assert.ok(json.hasOwnProperty('withoutConfig')); - assert.equal(Array.isArray(json.withoutConfig), true); - - const ids = json.withoutConfig.map((item) => item.id); - assert.deepEqual( - ids.sort(), - [ - 'columbia.md', - 'endeavour.md', - 'enterprise.md', - // Spaces allowed in IDs - 'promo/launch week.mdx', - ].sort(), - ); - }); - - it('Handles spaces in `without config` slugs', async () => { - assert.ok(json.hasOwnProperty('withoutConfig')); - assert.equal(Array.isArray(json.withoutConfig), true); - - const slugs = json.withoutConfig.map((item) => item.slug); - assert.deepEqual( - slugs.sort(), - [ - 'columbia', - 'endeavour', - 'enterprise', - // "launch week.mdx" is converted to "launch-week.mdx" - 'promo/launch-week', - ].sort(), - ); - }); - - it('Passes legacy entry to filter function', async () => { - assert.ok(json.hasOwnProperty('filtered')); - assert.ok(Array.isArray(json.filtered)); - assert.ok(json.filtered.length > 0); - }); - - it('Returns `with schema` collection', async () => { - assert.ok(json.hasOwnProperty('withSchemaConfig')); - assert.equal(Array.isArray(json.withSchemaConfig), true); - - const ids = json.withSchemaConfig.map((item) => item.id); - const publishedDates = json.withSchemaConfig.map((item) => item.data.publishedAt); - assert.deepEqual(ids.sort(), ['four%.md', 'one.md', 'three.md', 'two.md'].sort()); - assert.equal( - publishedDates.every((date) => date instanceof Date), - true, - 'Not all publishedAt dates are Date objects', - ); - assert.deepEqual( - publishedDates.map((date) => date.toISOString()), - [ - '2021-01-01T00:00:00.000Z', - '2021-01-01T00:00:00.000Z', - '2021-01-03T00:00:00.000Z', - '2021-01-02T00:00:00.000Z', - ], - ); - }); - - it('Returns `with custom slugs` collection', async () => { - assert.ok(json.hasOwnProperty('withSlugConfig')); - assert.equal(Array.isArray(json.withSlugConfig), true); - - const slugs = json.withSlugConfig.map((item) => item.slug); - assert.deepEqual(slugs, ['fancy-one', 'excellent-three', 'interesting-two']); - }); - - it('Returns `with union schema` collection', async () => { - assert.ok(json.hasOwnProperty('withUnionSchema')); - assert.equal(Array.isArray(json.withUnionSchema), true); - - const post = json.withUnionSchema.find((item) => item.id === 'post.md'); - assert.notEqual(post, undefined); - assert.deepEqual(post.data, { - type: 'post', - title: 'My Post', - description: 'This is my post', - }); - const newsletter = json.withUnionSchema.find((item) => item.id === 'newsletter.md'); - assert.notEqual(newsletter, undefined); - assert.deepEqual(newsletter.data, { - type: 'newsletter', - subject: 'My Newsletter', - }); - }); - - it('Handles symlinked content', async () => { - assert.ok(json.hasOwnProperty('withSymlinkedContent')); - assert.equal(Array.isArray(json.withSymlinkedContent), true); - const ids = json.withSymlinkedContent.map((item) => item.id); - assert.deepEqual(ids.sort(), ['first.md', 'second.md', 'third.md'].sort()); - assert.equal( - json.withSymlinkedContent.find(({ id }) => id === 'first.md').data.title, - 'First Blog', - ); - }); - - it('Handles symlinked data', async () => { - assert.ok(json.hasOwnProperty('withSymlinkedData')); - assert.equal(Array.isArray(json.withSymlinkedData), true); - - const ids = json.withSymlinkedData.map((item) => item.id); - assert.deepEqual(ids, ['welcome']); - assert.equal( - json.withSymlinkedData[0].data.alt, - 'Futuristic landscape with chrome buildings and blue skies', - ); - assert.notEqual(json.withSymlinkedData[0].data.src.src, undefined); - }); - }); - - describe('Propagation', () => { - it('Applies styles', async () => { - const html = await fixture.readFile('/propagation/index.html'); - const $ = cheerio.load(html); - assert.equal($('style').text().includes('content:"works!"'), true); - }); - }); - - describe('Entry', () => { - let json; - before(async () => { - const rawJson = await fixture.readFile('/entries.json'); - json = devalue.parse(rawJson); - }); - - it('Returns `without config` collection entry', async () => { - assert.ok(json.hasOwnProperty('columbiaWithoutConfig')); - assert.equal(json.columbiaWithoutConfig.id, 'columbia.md'); - }); - - it('Returns `with schema` collection entry', async () => { - assert.ok(json.hasOwnProperty('oneWithSchemaConfig')); - assert.equal(json.oneWithSchemaConfig.id, 'one.md'); - assert.equal(json.oneWithSchemaConfig.data.publishedAt instanceof Date, true); - assert.equal( - json.oneWithSchemaConfig.data.publishedAt.toISOString(), - '2021-01-01T00:00:00.000Z', - ); - }); - - it('Returns `with custom slugs` collection entry', async () => { - assert.ok(json.hasOwnProperty('twoWithSlugConfig')); - assert.equal(json.twoWithSlugConfig.slug, 'interesting-two'); - }); - - it('Returns `with union schema` collection entry', async () => { - assert.ok(json.hasOwnProperty('postWithUnionSchema')); - assert.equal(json.postWithUnionSchema.id, 'post.md'); - assert.deepEqual(json.postWithUnionSchema.data, { - type: 'post', - title: 'My Post', - description: 'This is my post', - }); - }); - }); - - describe('Scripts', () => { - it('Contains all the scripts imported by components', async () => { - const html = await fixture.readFile('/with-scripts/one/index.html'); - const $ = cheerio.load(html); - assert.equal($('script').length, 2); - // Read the scripts' content - const scriptsCode = $('script') - .map((_, el) => $(el).text()) - .toArray() - .join('\n'); - assert.match(scriptsCode, /ScriptCompA/); - assert.match(scriptsCode, /ScriptCompB/); - }); - }); - }); - - const blogSlugToContents = { - 'first-post': { - title: 'First post', - element: 'blockquote', - content: 'First post loaded: yes!', - }, - 'second-post': { - title: 'Second post', - element: 'blockquote', - content: 'Second post loaded: yes!', - }, - 'third-post': { - title: 'Third post', - element: 'blockquote', - content: 'Third post loaded: yes!', - }, - 'using-mdx': { - title: 'Using MDX', - element: 'a[href="#"]', - content: 'Embedded component in MDX', - }, - }; - - describe('Static paths integration', () => { - let fixture; - - before(async () => { - fixture = await loadFixture({ - root: './fixtures/content-static-paths-integration/', - legacy: { - collections: true, - }, - }); - await fixture.build(); - }); - - it('Generates expected pages', async () => { - for (const slug in blogSlugToContents) { - assert.equal(fixture.pathExists(`/posts/${slug}`), true); - } - }); - - it('Renders titles', async () => { - for (const slug in blogSlugToContents) { - const post = await fixture.readFile(`/posts/${slug}/index.html`); - const $ = cheerio.load(post); - assert.equal($('h1').text(), blogSlugToContents[slug].title); - } - }); - - it('Renders content', async () => { - for (const slug in blogSlugToContents) { - const post = await fixture.readFile(`/posts/${slug}/index.html`); - const $ = cheerio.load(post); - assert.equal( - $(blogSlugToContents[slug].element).text().trim(), - blogSlugToContents[slug].content, - ); - } - }); - }); - - describe('With spaces in path', () => { - it('Does not throw', async () => { - const fixture = await loadFixture({ - root: './fixtures/content with spaces in folder name/', - legacy: { - collections: true, - }, - }); - let error = null; - try { - await fixture.build({ force: true }); - } catch (e) { - error = e.message; - } - assert.equal(error, null); - }); - }); - describe('With config.mjs', () => { - it("Errors when frontmatter doesn't match schema", async () => { - const fixture = await loadFixture({ - root: './fixtures/content-collections-with-config-mjs/', - legacy: { - collections: true, - }, - }); - let error; - try { - await fixture.build(); - } catch (e) { - error = e.message; - } - assert.match(error, /\*\*title\*\*: Expected type `"string"`, received `"number"`/); - }); - }); - describe('With config.mts', () => { - it("Errors when frontmatter doesn't match schema", async () => { - const fixture = await loadFixture({ - root: './fixtures/content-collections-with-config-mts/', - legacy: { - collections: true, - }, - }); - let error; - try { - await fixture.build(); - } catch (e) { - error = e.message; - } - assert.match(error, /\*\*title\*\*: Expected type `"string"`, received `"number"`/); - }); - }); - - describe('With empty markdown file', () => { - it('Throws the right error', async () => { - const fixture = await loadFixture({ - root: './fixtures/content-collections-empty-md-file/', - legacy: { - collections: true, - }, - }); - let error; - try { - await fixture.build(); - } catch (e) { - error = e.message; - } - assert.equal(error.includes('**title**: Required'), true); - }); - }); - - describe('With empty collections directory', () => { - it('Handles the empty directory correctly', async () => { - const fixture = await loadFixture({ - root: './fixtures/content-collections-empty-dir/', - legacy: { - collections: true, - }, - }); - let error; - try { - await fixture.build(); - } catch (e) { - error = e.message; - } - assert.equal(error, undefined); - - const html = await fixture.readFile('/index.html'); - const $ = cheerio.load(html); - const h1 = $('h1'); - assert.equal(h1.text(), 'Entries length: 0'); - assert.equal(h1.attr('data-entries'), '[]'); - }); - }); - - describe('SSR integration', () => { - let app; - - before(async () => { - const fixture = await loadFixture({ - root: './fixtures/content-ssr-integration/', - output: 'server', - adapter: testAdapter(), - vite: { - plugins: [preventNodeBuiltinDependencyPlugin()], - }, - legacy: { - collections: true, - }, - }); - await fixture.build(); - app = await fixture.loadTestAdapterApp(); - }); - - it('Responds 200 for expected pages', async () => { - for (const slug in blogSlugToContents) { - const request = new Request('http://example.com/posts/' + slug); - const response = await app.render(request); - assert.equal(response.status, 200); - } - }); - - it('Renders titles', async () => { - for (const slug in blogSlugToContents) { - const request = new Request('http://example.com/posts/' + slug); - const response = await app.render(request); - const body = await response.text(); - const $ = cheerio.load(body); - assert.equal($('h1').text(), blogSlugToContents[slug].title); - } - }); - - it('Renders content', async () => { - for (const slug in blogSlugToContents) { - const request = new Request('http://example.com/posts/' + slug); - const response = await app.render(request); - const body = await response.text(); - const $ = cheerio.load(body); - assert.equal( - $(blogSlugToContents[slug].element).text().trim(), - blogSlugToContents[slug].content, - ); - } - }); - }); - - describe('Base configuration', () => { - let fixture; - - before(async () => { - fixture = await loadFixture({ - root: './fixtures/content-collections-base/', - legacy: { - collections: true, - }, - }); - await fixture.build(); - }); - - it('Includes base in links', async () => { - const html = await fixture.readFile('/docs/index.html'); - const $ = cheerio.load(html); - assert.equal($('link').attr('href').startsWith('/docs'), true); - }); - - it('Includes base in scripts', async () => { - const html = await fixture.readFile('/docs/index.html'); - const $ = cheerio.load(html); - assert.equal($('script').attr('src').startsWith('/docs'), true); - }); - }); - - describe('Mutation', () => { - let fixture; - - before(async () => { - fixture = await loadFixture({ - root: './fixtures/content-collections-mutation/', - legacy: { - collections: true, - }, - }); - await fixture.build(); - }); - - it('Does not mutate cached collection', async () => { - const html = await fixture.readFile('/index.html'); - const index = cheerio.load(html)('h2:first').text(); - const html2 = await fixture.readFile('/another_page/index.html'); - const anotherPage = cheerio.load(html2)('h2:first').text(); - - assert.equal(index, anotherPage); - }); - }); -}); diff --git a/packages/astro/test/legacy-data-collections.test.js b/packages/astro/test/legacy-data-collections.test.js deleted file mode 100644 index 6bd5f19493c9..000000000000 --- a/packages/astro/test/legacy-data-collections.test.js +++ /dev/null @@ -1,159 +0,0 @@ -import assert from 'node:assert/strict'; -import { before, describe, it } from 'node:test'; -import { loadFixture } from './test-utils.js'; - -const authorIds = ['Ben Holmes', 'Fred K Schott', 'Nate Moore']; -const translationIds = ['en', 'es', 'fr']; - -describe('Content Collections - legacy data collections', () => { - let fixture; - before(async () => { - fixture = await loadFixture({ root: './fixtures/legacy-data-collections/' }); - await fixture.build({ force: true }); - }); - - describe('Authors Collection', () => { - let json; - before(async () => { - const rawJson = await fixture.readFile('/authors/all.json'); - json = JSON.parse(rawJson); - }); - - it('Returns', async () => { - assert.equal(Array.isArray(json), true); - assert.equal(json.length, 3); - }); - - it('Generates correct ids', async () => { - const ids = json.map((item) => item.id).sort(); - assert.deepEqual(ids, ['Ben Holmes', 'Fred K Schott', 'Nate Moore']); - }); - - it('Generates correct data', async () => { - const names = json.map((item) => item.data.name); - assert.deepEqual(names, ['Ben J Holmes', 'Fred K Schott', 'Nate Something Moore']); - - const twitterUrls = json.map((item) => item.data.twitter); - assert.deepEqual(twitterUrls, [ - 'https://twitter.com/bholmesdev', - 'https://twitter.com/FredKSchott', - 'https://twitter.com/n_moore', - ]); - }); - }); - - describe('getDataEntryById', () => { - let json; - before(async () => { - const rawJson = await fixture.readFile('/translations/by-id.json'); - json = JSON.parse(rawJson); - }); - it('Grabs the item by the base file name', () => { - assert.equal(json.id, 'en'); - }); - }); - - describe('Authors Entry', () => { - for (const authorId of authorIds) { - let json; - before(async () => { - const rawJson = await fixture.readFile(`/authors/${authorId}.json`); - json = JSON.parse(rawJson); - }); - - it(`Returns ${authorId}`, async () => { - assert.equal(json.hasOwnProperty('id'), true); - assert.equal(json.id, authorId); - }); - - it(`Generates correct data for ${authorId}`, async () => { - assert.equal(json.hasOwnProperty('data'), true); - assert.equal(json.data.hasOwnProperty('name'), true); - assert.equal(json.data.hasOwnProperty('twitter'), true); - - switch (authorId) { - case 'Ben Holmes': - assert.equal(json.data.name, 'Ben J Holmes'); - assert.equal(json.data.twitter, 'https://twitter.com/bholmesdev'); - break; - case 'Fred K Schott': - assert.equal(json.data.name, 'Fred K Schott'); - assert.equal(json.data.twitter, 'https://twitter.com/FredKSchott'); - break; - case 'Nate Moore': - assert.equal(json.data.name, 'Nate Something Moore'); - assert.equal(json.data.twitter, 'https://twitter.com/n_moore'); - break; - } - }); - } - }); - - describe('Translations Collection', () => { - let json; - before(async () => { - const rawJson = await fixture.readFile('/translations/all.json'); - json = JSON.parse(rawJson); - }); - - it('Returns', async () => { - assert.equal(Array.isArray(json), true); - assert.equal(json.length, 3); - }); - - it('Generates correct ids', async () => { - const ids = json.map((item) => item.id).sort(); - assert.deepEqual(ids, translationIds); - }); - - it('Generates correct data', async () => { - const sorted = json.sort((a, b) => a.id.localeCompare(b.id)); - const homepageGreetings = sorted.map((item) => item.data.homepage?.greeting); - assert.deepEqual(homepageGreetings, ['Hello World!', '¡Hola Mundo!', 'Bonjour le monde!']); - - const homepagePreambles = sorted.map((item) => item.data.homepage?.preamble); - assert.deepEqual(homepagePreambles, [ - 'Welcome to the future of content.', - 'Bienvenido al futuro del contenido.', - 'Bienvenue dans le futur du contenu.', - ]); - }); - }); - - describe('Translations Entry', () => { - for (const translationId of translationIds) { - let json; - before(async () => { - const rawJson = await fixture.readFile(`/translations/${translationId}.json`); - json = JSON.parse(rawJson); - }); - - it(`Returns ${translationId}`, async () => { - assert.equal(json.hasOwnProperty('id'), true); - assert.equal(json.id, translationId); - }); - - it(`Generates correct data for ${translationId}`, async () => { - assert.equal(json.hasOwnProperty('data'), true); - assert.equal(json.data.hasOwnProperty('homepage'), true); - assert.equal(json.data.homepage.hasOwnProperty('greeting'), true); - assert.equal(json.data.homepage.hasOwnProperty('preamble'), true); - - switch (translationId) { - case 'en': - assert.equal(json.data.homepage.greeting, 'Hello World!'); - assert.equal(json.data.homepage.preamble, 'Welcome to the future of content.'); - break; - case 'es': - assert.equal(json.data.homepage.greeting, '¡Hola Mundo!'); - assert.equal(json.data.homepage.preamble, 'Bienvenido al futuro del contenido.'); - break; - case 'fr': - assert.equal(json.data.homepage.greeting, 'Bonjour le monde!'); - assert.equal(json.data.homepage.preamble, 'Bienvenue dans le futur du contenu.'); - break; - } - }); - } - }); -}); diff --git a/packages/astro/test/units/content-collections/frontmatter.test.js b/packages/astro/test/units/content-collections/frontmatter.test.js index feffabf9567a..30e32f867dd4 100644 --- a/packages/astro/test/units/content-collections/frontmatter.test.js +++ b/packages/astro/test/units/content-collections/frontmatter.test.js @@ -12,8 +12,10 @@ describe('frontmatter', () => { `, '/src/content.config.ts': `\ import { defineCollection, z } from 'astro:content'; + import { glob } from 'astro/loaders'; const posts = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/posts' }), schema: z.string() }); diff --git a/packages/astro/test/units/content-collections/get-entry-type.test.js b/packages/astro/test/units/content-collections/get-entry-type.test.js index d8dcb459b85c..45d3c57081c9 100644 --- a/packages/astro/test/units/content-collections/get-entry-type.test.js +++ b/packages/astro/test/units/content-collections/get-entry-type.test.js @@ -19,7 +19,7 @@ const fixtures = [ title: 'With underscore levels 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), diff --git a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js b/packages/astro/test/units/dev/collections-mixed-content-errors.test.js deleted file mode 100644 index 295662c93110..000000000000 --- a/packages/astro/test/units/dev/collections-mixed-content-errors.test.js +++ /dev/null @@ -1,147 +0,0 @@ -import * as assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; -import _sync from '../../../dist/core/sync/index.js'; -import { createFixture } from '../test-utils.js'; - -async function sync(root) { - try { - await _sync({ - root, - logLevel: 'silent', - }); - return 0; - } catch { - return 1; - } -} - -const baseFileTree = { - '/astro.config.mjs': `export default { legacy: { collections: true }}`, - '/src/content/authors/placeholder.json': `{ "name": "Placeholder" }`, - '/src/content/blog/placeholder.md': `\ ---- -title: Placeholder post ---- -`, - '/src/pages/authors.astro': `\ ---- -import { getCollection } from 'astro:content'; -try { - await getCollection('authors') -} catch (e) { - return e -} ---- - -

Worked

-`, - '/src/pages/blog.astro': `\ ---- -import { getCollection } from 'astro:content'; - -await getCollection('blog') ---- - -

Worked

`, -}; - -describe('Content Collections - mixed content errors', () => { - it('raises "mixed content" error when content in data collection', async () => { - const fixture = await createFixture({ - ...baseFileTree, - '/src/content/authors/ben.md': `\ ---- -name: Ben ---- - -# Ben -`, - '/src/content/authors/tony.json': `{ "name": "Tony" }`, - '/src/content.config.ts': `\ -import { z, defineCollection } from 'astro:content'; - -const authors = defineCollection({ - type: 'data', - schema: z.object({ - name: z.string(), - }), -}); - -export const collections = { authors }; -`, - }); - - assert.equal(await sync(fixture.path), 1); - }); - - it('raises "mixed content" error when data in content collection', async () => { - const fixture = await createFixture({ - ...baseFileTree, - '/src/content/blog/post.md': `\ ---- -title: Post ---- - -# Post -`, - '/src/content/blog/post.yaml': `title: YAML Post`, - '/src/content.config.ts': `\ -import { z, defineCollection } from 'astro:content'; - -const blog = defineCollection({ - type: 'content', - schema: z.object({ - title: z.string(), - }), -}); - -export const collections = { blog }; -`, - }); - - assert.equal(await sync(fixture.path), 1); - }); - - it('raises error when data collection configured as content collection', async () => { - const fixture = await createFixture({ - ...baseFileTree, - '/src/content/banners/welcome.json': `{ "src": "/example", "alt": "Welcome" }`, - '/src/content/config.ts': `\ -import { z, defineCollection } from 'astro:content'; - -const banners = defineCollection({ - schema: z.object({ - src: z.string(), - alt: z.string(), - }), -}); - -export const collections = { banners }; -`, - }); - - assert.equal(await sync(fixture.path), 1); - }); - - it('does not raise error for empty collection with config', async () => { - const fixture = await createFixture({ - ...baseFileTree, - // Add placeholder to ensure directory exists - '/src/content/i18n/_placeholder.txt': 'Need content here', - '/src/content.config.ts': `\ -import { z, defineCollection } from 'astro:content'; - -const i18n = defineCollection({ - type: 'data', - schema: z.object({ - greeting: z.string(), - }), -}); - -export const collections = { i18n }; -`, - }); - - assert.equal(await sync(fixture.path), 0); - }); -}); diff --git a/packages/astro/test/units/dev/collections-renderentry.test.js b/packages/astro/test/units/dev/collections-renderentry.test.js deleted file mode 100644 index 298c433b8b16..000000000000 --- a/packages/astro/test/units/dev/collections-renderentry.test.js +++ /dev/null @@ -1,298 +0,0 @@ -import * as assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; -import * as cheerio from 'cheerio'; - -import { attachContentServerListeners } from '../../../dist/content/server-listeners.js'; -import { createFixture, createRequestAndResponse, runInContainer } from '../test-utils.js'; - -const baseFileTree = { - 'astro.config.mjs': `\ -import mdx from '@astrojs/mdx'; -export default { - integrations: [mdx()], - legacy: { - // Enable legacy content collections as we test layout fields - collections: true - } -}; -`, - '/src/content/blog/promo/_launch-week-styles.css': `\ -body { - font-family: 'Comic Sans MS', sans-serif; -} -`, - '/src/content/blog/promo/launch-week.mdx': `\ ---- -title: 'Launch week!' -description: 'Join us for the exciting launch of SPACE BLOG' -publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' -tags: ['announcement'] ---- - -import './_launch-week-styles.css'; - -Join us for the space blog launch! - -- THIS THURSDAY -- Houston, TX -- Dress code: **interstellar casual** ✨ -`, -}; - -/** @type {typeof runInContainer} */ -async function runInContainerWithContentListeners(params, callback) { - return await runInContainer(params, async (container) => { - await attachContentServerListeners(container); - await callback(container); - }); -} - -describe('Content Collections - render()', () => { - it('can be called in a page component', async () => { - const fixture = await createFixture({ - ...baseFileTree, - '/src/content/config.ts': ` - import { z, defineCollection } from 'astro:content'; - - const blog = defineCollection({ - schema: z.object({ - title: z.string(), - description: z.string().max(60, 'For SEO purposes, keep descriptions short!'), - }), - }); - - export const collections = { blog }; - `, - '/src/pages/index.astro': ` - --- - import { getCollection } from 'astro:content'; - const blog = await getCollection('blog'); - const launchWeekEntry = blog.find(post => post.id === 'promo/launch-week.mdx'); - const { Content } = await launchWeekEntry.render(); - --- - - Testing - -

testing

- - - - `, - }); - - await runInContainerWithContentListeners( - { - inlineConfig: { - root: fixture.path, - vite: { server: { middlewareMode: true } }, - }, - }, - async (container) => { - const { req, res, done, text } = createRequestAndResponse({ - method: 'GET', - url: '/', - }); - container.handle(req, res); - await done; - const html = await text(); - - const $ = cheerio.load(html); - // Rendered the content - assert.equal($('ul li').length, 3); - - // Rendered the styles - assert.equal($('style').length, 1); - }, - ); - }); - - it('can be used in a layout component', async () => { - const fixture = await createFixture({ - ...baseFileTree, - '/src/components/Layout.astro': ` - --- - import { getCollection } from 'astro:content'; - const blog = await getCollection('blog'); - const launchWeekEntry = blog.find(post => post.id === 'promo/launch-week.mdx'); - const { Content } = await launchWeekEntry.render(); - --- - - - - -
- -
- - - - `, - '/src/pages/index.astro': ` - --- - import Layout from '../components/Layout.astro'; - --- - -

Index page

-
- `, - }); - - await runInContainerWithContentListeners( - { - inlineConfig: { - root: fixture.path, - vite: { server: { middlewareMode: true } }, - }, - }, - async (container) => { - const { req, res, done, text } = createRequestAndResponse({ - method: 'GET', - url: '/', - }); - container.handle(req, res); - await done; - const html = await text(); - - const $ = cheerio.load(html); - // Rendered the content - assert.equal($('ul li').length, 3); - - // Rendered the styles - assert.equal($('style').length, 1); - }, - ); - }); - - it('can be used in a slot', async () => { - const fixture = await createFixture({ - ...baseFileTree, - '/src/content.config.ts': ` - import { z, defineCollection } from 'astro:content'; - - const blog = defineCollection({ - schema: z.object({ - title: z.string(), - description: z.string().max(60, 'For SEO purposes, keep descriptions short!'), - }), - }); - - export const collections = { blog }; - `, - '/src/components/Layout.astro': ` - - - - -
- -
- - - `, - '/src/pages/index.astro': ` - --- - import Layout from '../components/Layout.astro'; - import { getCollection } from 'astro:content'; - const blog = await getCollection('blog'); - const launchWeekEntry = blog.find(post => post.id === 'promo/launch-week.mdx'); - const { Content } = await launchWeekEntry.render(); - --- - -

Index page

- -
- `, - }); - - await runInContainerWithContentListeners( - { - inlineConfig: { - root: fixture.path, - vite: { server: { middlewareMode: true } }, - }, - }, - async (container) => { - const { req, res, done, text } = createRequestAndResponse({ - method: 'GET', - url: '/', - }); - container.handle(req, res); - await done; - const html = await text(); - - const $ = cheerio.load(html); - // Rendered the content - assert.equal($('ul li').length, 3); - - // Rendered the styles - assert.equal($('style').length, 1); - }, - ); - }); - - it('can be called from any js/ts file', async () => { - const fixture = await createFixture({ - ...baseFileTree, - '/src/content.config.ts': ` - import { z, defineCollection } from 'astro:content'; - - const blog = defineCollection({ - schema: z.object({ - title: z.string(), - description: z.string().max(60, 'For SEO purposes, keep descriptions short!'), - }), - }); - - export const collections = { blog }; - `, - '/src/launch-week.ts': ` - import { getCollection } from 'astro:content'; - - export let Content; - - const blog = await getCollection('blog'); - const launchWeekEntry = blog.find(post => post.id === 'promo/launch-week.mdx'); - const mod = await launchWeekEntry.render(); - - Content = mod.Content; - `, - '/src/pages/index.astro': ` - --- - import { Content } from '../launch-week.ts'; - --- - - Testing - -

Testing

- - - - `, - }); - - await runInContainerWithContentListeners( - { - inlineConfig: { - root: fixture.path, - vite: { server: { middlewareMode: true } }, - }, - }, - async (container) => { - const { req, res, done, text } = createRequestAndResponse({ - method: 'GET', - url: '/', - }); - container.handle(req, res); - await done; - const html = await text(); - - const $ = cheerio.load(html); - // Rendered the content - assert.equal($('ul li').length, 3); - - // Rendered the styles - assert.equal($('style').length, 1); - }, - ); - }); -}); diff --git a/packages/astro/test/vitest.test.js b/packages/astro/test/vitest.test.js index c7c4180f3e81..083b45dada3a 100644 --- a/packages/astro/test/vitest.test.js +++ b/packages/astro/test/vitest.test.js @@ -1,15 +1,18 @@ import assert from 'node:assert/strict'; -import { describe, it } from 'node:test'; +import { after, before, describe, it } from 'node:test'; import { fileURLToPath } from 'node:url'; import { createVitest } from 'vitest/node'; describe('vitest', () => { - it('basics', async () => { - const config = new URL('./fixtures/vitest/vitest.config.js', import.meta.url); + let originalCwd; + before(() => { + originalCwd = process.cwd(); + // We chdir rather than setting the root in vitest, because otherwise it sets the wrong root in the site config + process.chdir(fileURLToPath(new URL('./fixtures/vitest/', import.meta.url))); + }); + it('basics', async () => { const vitest = await createVitest('test', { - config: fileURLToPath(config), - root: fileURLToPath(new URL('./fixtures/vitest/', import.meta.url)), watch: false, }); @@ -21,4 +24,8 @@ describe('vitest', () => { await vitest.close(); } }); + + after(() => { + process.chdir(originalCwd); + }); }); diff --git a/packages/integrations/cloudflare/test/fixtures/compile-image-service/src/content/config.ts b/packages/integrations/cloudflare/test/fixtures/compile-image-service/src/content.config.ts similarity index 79% rename from packages/integrations/cloudflare/test/fixtures/compile-image-service/src/content/config.ts rename to packages/integrations/cloudflare/test/fixtures/compile-image-service/src/content.config.ts index ce06042f6ae3..8692f75e2a18 100644 --- a/packages/integrations/cloudflare/test/fixtures/compile-image-service/src/content/config.ts +++ b/packages/integrations/cloudflare/test/fixtures/compile-image-service/src/content.config.ts @@ -1,7 +1,9 @@ import type { ImageMetadata } from 'astro'; import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; const blog = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), schema: ({ image }) => z.object({ image: z diff --git a/packages/integrations/cloudflare/test/fixtures/compile-image-service/src/pages/blog/[...slug].astro b/packages/integrations/cloudflare/test/fixtures/compile-image-service/src/pages/blog/[...slug].astro index 6246901f0cdf..5aa37bc37e94 100644 --- a/packages/integrations/cloudflare/test/fixtures/compile-image-service/src/pages/blog/[...slug].astro +++ b/packages/integrations/cloudflare/test/fixtures/compile-image-service/src/pages/blog/[...slug].astro @@ -1,5 +1,5 @@ --- -import { getEntry, type CollectionEntry } from "astro:content"; +import { getEntry, render, type CollectionEntry } from "astro:content"; export const prerender = false; @@ -8,7 +8,7 @@ type Props = CollectionEntry<"blog">; const post = await getEntry("blog", Astro.params.slug!); if (!post) return Astro.rewrite("/404"); -const { Content } = await post.render(); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/content-collections.test.js b/packages/integrations/markdoc/test/content-collections.test.js index 48e97d45d670..d9e88c868e65 100644 --- a/packages/integrations/markdoc/test/content-collections.test.js +++ b/packages/integrations/markdoc/test/content-collections.test.js @@ -78,8 +78,7 @@ describe('Markdoc - Content Collections', () => { }); const post1Entry = { - id: 'post-1.mdoc', - slug: 'post-1', + id: 'post-1', collection: 'blog', data: { schemaWorks: true, @@ -92,8 +91,7 @@ const post1Entry = { }; const post2Entry = { - id: 'post-2.mdoc', - slug: 'post-2', + id: 'post-2', collection: 'blog', data: { schemaWorks: true, @@ -106,8 +104,7 @@ const post2Entry = { }; const post3Entry = { - id: 'post-3.mdoc', - slug: 'post-3', + id: 'post-3', collection: 'blog', data: { schemaWorks: true, diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/content-collections/src/content.config.ts similarity index 68% rename from packages/integrations/markdoc/test/fixtures/content-collections/src/content/config.ts rename to packages/integrations/markdoc/test/fixtures/content-collections/src/content.config.ts index 3b411201a22e..bdc0b100eafd 100644 --- a/packages/integrations/markdoc/test/fixtures/content-collections/src/content/config.ts +++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/content.config.ts @@ -1,6 +1,8 @@ import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), schema: z.object({ title: z.string(), }).transform(data => ({ diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/collection.json.js b/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/collection.json.js index cb3c84652622..595145f38a29 100644 --- a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/collection.json.js +++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/collection.json.js @@ -1,8 +1,7 @@ import { getCollection } from 'astro:content'; import { stringify } from 'devalue'; -import { stripAllRenderFn } from '../../utils.js'; export async function GET() { const posts = await getCollection('blog'); - return new Response(stringify(stripAllRenderFn(posts))); + return new Response(stringify(posts)); } diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js b/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js index 53dd17013ba7..8ddc63348d13 100644 --- a/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js +++ b/packages/integrations/markdoc/test/fixtures/content-collections/src/pages/entry.json.js @@ -1,8 +1,7 @@ -import { getEntryBySlug } from 'astro:content'; +import { getEntry } from 'astro:content'; import { stringify } from 'devalue'; -import { stripRenderFn } from '../../utils.js'; export async function GET() { - const post = await getEntryBySlug('blog', 'post-1'); - return new Response(stringify(stripRenderFn(post))); + const post = await getEntry('blog', 'post-1'); + return new Response(stringify(post)); } diff --git a/packages/integrations/markdoc/test/fixtures/content-collections/utils.js b/packages/integrations/markdoc/test/fixtures/content-collections/utils.js deleted file mode 100644 index 3a6244327862..000000000000 --- a/packages/integrations/markdoc/test/fixtures/content-collections/utils.js +++ /dev/null @@ -1,8 +0,0 @@ -export function stripRenderFn(entryWithRender) { - const { render, ...entry } = entryWithRender; - return entry; -} - -export function stripAllRenderFn(collection = []) { - return collection.map(stripRenderFn); -} diff --git a/packages/integrations/markdoc/test/fixtures/headings-custom/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/headings-custom/src/content.config.ts new file mode 100644 index 000000000000..72dc34f67011 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/headings-custom/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const docs = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/docs' }), +}); + +export const collections = { + docs, +}; diff --git a/packages/integrations/markdoc/test/fixtures/headings-custom/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/headings-custom/src/content/config.ts deleted file mode 100644 index a142ace11a74..000000000000 --- a/packages/integrations/markdoc/test/fixtures/headings-custom/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const docs = defineCollection({}); - -export const collections = { - docs, -}; diff --git a/packages/integrations/markdoc/test/fixtures/headings-custom/src/pages/[slug].astro b/packages/integrations/markdoc/test/fixtures/headings-custom/src/pages/[slug].astro index 90b021e95525..47565187b3a5 100644 --- a/packages/integrations/markdoc/test/fixtures/headings-custom/src/pages/[slug].astro +++ b/packages/integrations/markdoc/test/fixtures/headings-custom/src/pages/[slug].astro @@ -1,14 +1,14 @@ --- -import { CollectionEntry, getCollection } from "astro:content"; +import { CollectionEntry, getCollection, render } from "astro:content"; export async function getStaticPaths() { const docs = await getCollection('docs'); - return docs.map(doc => ({ params: { slug: doc.slug }, props: doc })); + return docs.map(doc => ({ params: { slug: doc.id }, props: doc })); } type Props = CollectionEntry<'docs'>; -const { Content, headings } = await Astro.props.render(); +const { Content, headings } = await render(Astro.props); --- diff --git a/packages/integrations/markdoc/test/fixtures/headings/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/headings/src/content.config.ts new file mode 100644 index 000000000000..72dc34f67011 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/headings/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const docs = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/docs' }), +}); + +export const collections = { + docs, +}; diff --git a/packages/integrations/markdoc/test/fixtures/headings/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/headings/src/content/config.ts deleted file mode 100644 index a142ace11a74..000000000000 --- a/packages/integrations/markdoc/test/fixtures/headings/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const docs = defineCollection({}); - -export const collections = { - docs, -}; diff --git a/packages/integrations/markdoc/test/fixtures/headings/src/pages/[slug].astro b/packages/integrations/markdoc/test/fixtures/headings/src/pages/[slug].astro index 90b021e95525..47565187b3a5 100644 --- a/packages/integrations/markdoc/test/fixtures/headings/src/pages/[slug].astro +++ b/packages/integrations/markdoc/test/fixtures/headings/src/pages/[slug].astro @@ -1,14 +1,14 @@ --- -import { CollectionEntry, getCollection } from "astro:content"; +import { CollectionEntry, getCollection, render } from "astro:content"; export async function getStaticPaths() { const docs = await getCollection('docs'); - return docs.map(doc => ({ params: { slug: doc.slug }, props: doc })); + return docs.map(doc => ({ params: { slug: doc.id }, props: doc })); } type Props = CollectionEntry<'docs'>; -const { Content, headings } = await Astro.props.render(); +const { Content, headings } = await render(Astro.props); --- diff --git a/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/content.config.ts new file mode 100644 index 000000000000..72dc34f67011 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const docs = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/docs' }), +}); + +export const collections = { + docs, +}; diff --git a/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/content/config.ts deleted file mode 100644 index a142ace11a74..000000000000 --- a/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const docs = defineCollection({}); - -export const collections = { - docs, -}; diff --git a/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/pages/index.astro index 51810b4a8cb5..cdb9dab892f0 100644 --- a/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/image-assets-custom/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from 'astro:content'; +import { getEntry, render } from 'astro:content'; -const intro = await getEntryBySlug('docs', 'intro'); -const { Content } = await intro.render(); +const intro = await getEntry('docs', 'intro'); +const { Content } = await render(intro); --- diff --git a/packages/integrations/markdoc/test/fixtures/image-assets/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/image-assets/src/content.config.ts new file mode 100644 index 000000000000..72dc34f67011 --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/image-assets/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const docs = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/docs' }), +}); + +export const collections = { + docs, +}; diff --git a/packages/integrations/markdoc/test/fixtures/image-assets/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/image-assets/src/content/config.ts deleted file mode 100644 index a142ace11a74..000000000000 --- a/packages/integrations/markdoc/test/fixtures/image-assets/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const docs = defineCollection({}); - -export const collections = { - docs, -}; diff --git a/packages/integrations/markdoc/test/fixtures/image-assets/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/image-assets/src/pages/index.astro index 51810b4a8cb5..cdb9dab892f0 100644 --- a/packages/integrations/markdoc/test/fixtures/image-assets/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/image-assets/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from 'astro:content'; +import { getEntry, render } from 'astro:content'; -const intro = await getEntryBySlug('docs', 'intro'); -const { Content } = await intro.render(); +const intro = await getEntry('docs', 'intro'); +const { Content } = await render(intro); --- diff --git a/packages/integrations/markdoc/test/fixtures/propagated-assets/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/propagated-assets/src/content.config.ts new file mode 100644 index 000000000000..e6435a6a79da --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/propagated-assets/src/content.config.ts @@ -0,0 +1,8 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx,mdoc}', base: './src/content/blog' }), +}); + +export const collections = { blog }; \ No newline at end of file diff --git a/packages/integrations/markdoc/test/fixtures/propagated-assets/src/pages/[slug].astro b/packages/integrations/markdoc/test/fixtures/propagated-assets/src/pages/[slug].astro index baee15375526..4fb007e80b4b 100644 --- a/packages/integrations/markdoc/test/fixtures/propagated-assets/src/pages/[slug].astro +++ b/packages/integrations/markdoc/test/fixtures/propagated-assets/src/pages/[slug].astro @@ -1,15 +1,15 @@ --- -import { getCollection } from 'astro:content'; +import { getCollection, render } from 'astro:content'; export async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map((post) => ({ - params: { slug: post.slug }, + params: { slug: post.id }, props: post, })); } -const { Content } = await Astro.props.render(); +const { Content } = await render(Astro.props); --- diff --git a/packages/integrations/markdoc/test/fixtures/render with-space/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render with-space/src/content.config.ts new file mode 100644 index 000000000000..e7e7a998b90f --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render with-space/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx,mdoc}', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render with-space/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render with-space/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render with-space/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render with-space/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render with-space/src/pages/index.astro index 940eef154005..c628759e7dba 100644 --- a/packages/integrations/markdoc/test/fixtures/render with-space/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/render with-space/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from "astro:content"; +import { getEntry, render } from "astro:content"; -const post = await getEntryBySlug('blog', 'simple'); -const { Content } = await post.render(); +const post = await getEntry('blog', 'simple'); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/fixtures/render-html/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render-html/src/content.config.ts new file mode 100644 index 000000000000..663df266cccb --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-html/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render-html/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render-html/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render-html/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render-html/src/pages/[slug].astro b/packages/integrations/markdoc/test/fixtures/render-html/src/pages/[slug].astro index bea51d3b584b..3799c4a23712 100644 --- a/packages/integrations/markdoc/test/fixtures/render-html/src/pages/[slug].astro +++ b/packages/integrations/markdoc/test/fixtures/render-html/src/pages/[slug].astro @@ -1,15 +1,15 @@ --- -import { getCollection, getEntryBySlug } from "astro:content"; +import { getCollection, getEntry, render } from "astro:content"; const { slug } = Astro.params; -const post = await getEntryBySlug('blog', slug); -const { Content } = await post.render(); +const post = await getEntry('blog', slug); +const { Content } = await render(post); export async function getStaticPaths() { const blogEntries = await getCollection('blog'); return blogEntries.map(entry => ({ - params: { slug: entry.slug }, props: { entry }, + params: { slug: entry.id }, props: { entry }, })); } diff --git a/packages/integrations/markdoc/test/fixtures/render-null/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render-null/src/content.config.ts new file mode 100644 index 000000000000..663df266cccb --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-null/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render-null/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render-null/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render-null/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render-null/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render-null/src/pages/index.astro index ed8417c5b373..3c934a903019 100644 --- a/packages/integrations/markdoc/test/fixtures/render-null/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/render-null/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from "astro:content"; +import { getEntry, render } from "astro:content"; -const post = await getEntryBySlug('blog', 'render-null'); -const { Content } = await post.render(); +const post = await getEntry('blog', 'render-null'); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/fixtures/render-partials/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render-partials/src/content.config.ts new file mode 100644 index 000000000000..663df266cccb --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-partials/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render-partials/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render-partials/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render-partials/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render-partials/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render-partials/src/pages/index.astro index e9549f314926..7c7e6b72c7b3 100644 --- a/packages/integrations/markdoc/test/fixtures/render-partials/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/render-partials/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from 'astro:content'; +import { getEntry, render } from 'astro:content'; -const post = await getEntryBySlug('blog', 'with-partials'); -const { Content } = await post.render(); +const post = await getEntry('blog', 'with-partials'); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/fixtures/render-simple/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render-simple/src/content.config.ts new file mode 100644 index 000000000000..663df266cccb --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-simple/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render-simple/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render-simple/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render-simple/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro index 940eef154005..c628759e7dba 100644 --- a/packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/render-simple/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from "astro:content"; +import { getEntry, render } from "astro:content"; -const post = await getEntryBySlug('blog', 'simple'); -const { Content } = await post.render(); +const post = await getEntry('blog', 'simple'); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/fixtures/render-typographer/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render-typographer/src/content.config.ts new file mode 100644 index 000000000000..663df266cccb --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-typographer/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render-typographer/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render-typographer/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render-typographer/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render-typographer/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render-typographer/src/pages/index.astro index 88fc531fa2e1..910920e1779b 100644 --- a/packages/integrations/markdoc/test/fixtures/render-typographer/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/render-typographer/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from "astro:content"; +import { getEntry, render } from "astro:content"; -const post = await getEntryBySlug('blog', 'typographer'); -const { Content } = await post.render(); +const post = await getEntry('blog', 'typographer'); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/fixtures/render-with-components/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render-with-components/src/content.config.ts new file mode 100644 index 000000000000..663df266cccb --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-components/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render-with-components/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render-with-components/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render-with-components/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro index 52239acce73e..5c7747eef923 100644 --- a/packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/render-with-components/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from "astro:content"; +import { getEntry, render } from "astro:content"; -const post = await getEntryBySlug('blog', 'with-components'); -const { Content } = await post.render(); +const post = await getEntry('blog', 'with-components'); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/fixtures/render-with-config/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render-with-config/src/content.config.ts new file mode 100644 index 000000000000..663df266cccb --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-config/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render-with-config/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render-with-config/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render-with-config/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render-with-config/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render-with-config/src/pages/index.astro index 616d5ec0aa8d..f4727432e055 100644 --- a/packages/integrations/markdoc/test/fixtures/render-with-config/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/render-with-config/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from "astro:content"; +import { getEntry, render } from "astro:content"; -const post = await getEntryBySlug('blog', 'with-config'); -const { Content } = await post.render(); +const post = await getEntry('blog', 'with-config'); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/content.config.ts new file mode 100644 index 000000000000..663df266cccb --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/pages/index.astro index 52239acce73e..5c7747eef923 100644 --- a/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/render-with-extends-components/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from "astro:content"; +import { getEntry, render } from "astro:content"; -const post = await getEntryBySlug('blog', 'with-components'); -const { Content } = await post.render(); +const post = await getEntry('blog', 'with-components'); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/content.config.ts b/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/content.config.ts new file mode 100644 index 000000000000..663df266cccb --- /dev/null +++ b/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), +}); + +export const collections = { + blog, +}; diff --git a/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/content/config.ts deleted file mode 100644 index 629486e48aa5..000000000000 --- a/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/content/config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineCollection } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { - blog, -}; diff --git a/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/pages/index.astro index 0ae7ed4c9da3..e50cbf100980 100644 --- a/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/render-with-indented-components/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from "astro:content"; +import { getEntry, render } from "astro:content"; -const post = await getEntryBySlug('blog', 'with-indented-components'); -const { Content } = await post.render(); +const post = await getEntry('blog', 'with-indented-components'); +const { Content } = await render(post); --- diff --git a/packages/integrations/markdoc/test/fixtures/variables/src/content/config.ts b/packages/integrations/markdoc/test/fixtures/variables/src/content.config.ts similarity index 66% rename from packages/integrations/markdoc/test/fixtures/variables/src/content/config.ts rename to packages/integrations/markdoc/test/fixtures/variables/src/content.config.ts index ff473d4af5be..958e209bf450 100644 --- a/packages/integrations/markdoc/test/fixtures/variables/src/content/config.ts +++ b/packages/integrations/markdoc/test/fixtures/variables/src/content.config.ts @@ -1,6 +1,8 @@ import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; const blog = defineCollection({ + loader: glob({ pattern: '**/*.mdoc', base: './src/content/blog' }), schema: z.object({ title: z.string().transform(v => 'Processed by schema: ' + v), }), diff --git a/packages/integrations/markdoc/test/fixtures/variables/src/content/blog/entry.mdoc b/packages/integrations/markdoc/test/fixtures/variables/src/content/blog/entry.mdoc index 151d5a81d2fd..cc7d99c3e816 100644 --- a/packages/integrations/markdoc/test/fixtures/variables/src/content/blog/entry.mdoc +++ b/packages/integrations/markdoc/test/fixtures/variables/src/content/blog/entry.mdoc @@ -5,5 +5,4 @@ title: Test entry # {% $entry.data.title %} - id: {% $entry.id %} {% #id %} -- slug: {% $entry.slug %} {% #slug %} - collection: {% $entry.collection %} {% #collection %} diff --git a/packages/integrations/markdoc/test/fixtures/variables/src/pages/index.astro b/packages/integrations/markdoc/test/fixtures/variables/src/pages/index.astro index a2766faf051b..66d38083157d 100644 --- a/packages/integrations/markdoc/test/fixtures/variables/src/pages/index.astro +++ b/packages/integrations/markdoc/test/fixtures/variables/src/pages/index.astro @@ -1,8 +1,8 @@ --- -import { getEntryBySlug } from 'astro:content'; +import { getEntry, render } from 'astro:content'; -const entry = await getEntryBySlug('blog', 'entry'); -const { Content } = await entry.render(); +const entry = await getEntry('blog', 'entry'); +const { Content } = await render(entry); --- diff --git a/packages/integrations/markdoc/test/variables.test.js b/packages/integrations/markdoc/test/variables.test.js index 9873afcb77e2..2225f19c8d86 100644 --- a/packages/integrations/markdoc/test/variables.test.js +++ b/packages/integrations/markdoc/test/variables.test.js @@ -32,8 +32,7 @@ describe('Markdoc - Variables', () => { const html = await res.text(); const { document } = parseHTML(html); assert.equal(document.querySelector('h1')?.textContent, 'Processed by schema: Test entry'); - assert.equal(document.getElementById('id')?.textContent?.trim(), 'id: entry.mdoc'); - assert.equal(document.getElementById('slug')?.textContent?.trim(), 'slug: entry'); + assert.equal(document.getElementById('id')?.textContent?.trim(), 'id: entry'); assert.equal(document.getElementById('collection')?.textContent?.trim(), 'collection: blog'); }); }); @@ -47,8 +46,7 @@ describe('Markdoc - Variables', () => { const html = await baseFixture.readFile('/index.html'); const { document } = parseHTML(html); assert.equal(document.querySelector('h1')?.textContent, 'Processed by schema: Test entry'); - assert.equal(document.getElementById('id')?.textContent?.trim(), 'id: entry.mdoc'); - assert.equal(document.getElementById('slug')?.textContent?.trim(), 'slug: entry'); + assert.equal(document.getElementById('id')?.textContent?.trim(), 'id: entry'); assert.equal(document.getElementById('collection')?.textContent?.trim(), 'collection: blog'); }); }); diff --git a/packages/integrations/mdx/test/fixtures/css-head-mdx/src/content/config.ts b/packages/integrations/mdx/test/fixtures/css-head-mdx/src/content.config.ts similarity index 100% rename from packages/integrations/mdx/test/fixtures/css-head-mdx/src/content/config.ts rename to packages/integrations/mdx/test/fixtures/css-head-mdx/src/content.config.ts diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/content.config.ts b/packages/integrations/mdx/test/fixtures/mdx-images/src/content.config.ts new file mode 100644 index 000000000000..4621eee5fbaf --- /dev/null +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/content.config.ts @@ -0,0 +1,8 @@ +import { defineCollection, z } from 'astro:content'; +import { glob } from 'astro/loaders'; + +const blog = defineCollection({ + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), +}); + +export const collections = { blog }; diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts b/packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts deleted file mode 100644 index 14443e78d6f7..000000000000 --- a/packages/integrations/mdx/test/fixtures/mdx-images/src/content/config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { defineCollection, z } from 'astro:content'; - -const blog = defineCollection({}); - -export const collections = { blog }; diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/config.js b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content.config.js similarity index 67% rename from packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/config.js rename to packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content.config.js index 6250d13c8188..0f045f564881 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content/config.js +++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/content.config.js @@ -1,11 +1,12 @@ import { z, defineCollection } from "astro:content"; +import { glob } from "astro/loaders"; const filesSchema = () => { return z.object({}); }; const filesCollection = defineCollection({ - type: "content", + loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/files' }), schema: filesSchema(), }); diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/pages/broken.astro b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/pages/broken.astro index bee4c85b5331..fd096e8501f2 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/pages/broken.astro +++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/src/pages/broken.astro @@ -1,8 +1,8 @@ --- -import { getCollection } from "astro:content"; +import { getCollection, render } from "astro:content"; const files = await getCollection("files"); -const { Content } = await files[0].render(); +const { Content } = await render(files[0]); --- diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 888f7177c7f5..106ae354a1ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2710,12 +2710,6 @@ importers: specifier: workspace:* version: link:../../.. - packages/astro/test/fixtures/content-collections-with-config-mts: - dependencies: - astro: - specifier: workspace:* - version: link:../../.. - packages/astro/test/fixtures/content-intellisense: dependencies: '@astrojs/markdoc': @@ -3499,21 +3493,6 @@ importers: specifier: workspace:* version: link:../../.. - packages/astro/test/fixtures/legacy-content-collections: - dependencies: - '@astrojs/mdx': - specifier: workspace:* - version: link:../../../../integrations/mdx - astro: - specifier: workspace:* - version: link:../../.. - - packages/astro/test/fixtures/legacy-data-collections: - dependencies: - astro: - specifier: workspace:* - version: link:../../.. - packages/astro/test/fixtures/live-loaders: dependencies: '@astrojs/node': From 51a697d8a3d5da9e685e815707d5f1224762e97c Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 24 Sep 2025 12:31:29 +0100 Subject: [PATCH 07/36] chore: fix content collection tests (#14431) --- .../e2e/fixtures/actions-blog/db/seed.ts | 4 +-- .../e2e/fixtures/actions-react-19/db/seed.ts | 4 +-- .../src/pages/blog/[...slug].astro | 4 +-- .../test/content-collections-render.test.js | 31 ++++++------------- .../astro/test/content-collections.test.js | 5 +-- .../src/content/blog/{one.md => 1-one.md} | 2 +- ...dx => 2-launch-week-component-scripts.mdx} | 2 +- ...dx => 3-launch-week-components-export.mdx} | 2 +- .../{launch-week.mdx => 4-launch-week.mdx} | 2 +- .../pages/launch-week-component-scripts.astro | 2 +- .../pages/launch-week-components-export.astro | 2 +- .../content/src/pages/launch-week.astro | 2 +- .../src/pages/sort-blog-collection.astro | 2 +- .../core-image-deletion/src/content.config.ts | 2 +- 14 files changed, 25 insertions(+), 41 deletions(-) rename packages/astro/test/fixtures/content/src/content/blog/{one.md => 1-one.md} (69%) rename packages/astro/test/fixtures/content/src/content/blog/promo/{launch-week-component-scripts.mdx => 2-launch-week-component-scripts.mdx} (93%) rename packages/astro/test/fixtures/content/src/content/blog/promo/{launch-week-components-export.mdx => 3-launch-week-components-export.mdx} (93%) rename packages/astro/test/fixtures/content/src/content/blog/promo/{launch-week.mdx => 4-launch-week.mdx} (92%) diff --git a/packages/astro/e2e/fixtures/actions-blog/db/seed.ts b/packages/astro/e2e/fixtures/actions-blog/db/seed.ts index 11dc55f7fe00..4e54174b1ede 100644 --- a/packages/astro/e2e/fixtures/actions-blog/db/seed.ts +++ b/packages/astro/e2e/fixtures/actions-blog/db/seed.ts @@ -3,12 +3,12 @@ import { db, Likes, Comment } from "astro:db"; // https://astro.build/db/seed export default async function seed() { await db.insert(Likes).values({ - postId: "first-post.md", + postId: "first-post", likes: 10, }); await db.insert(Comment).values({ - postId: "first-post.md", + postId: "first-post", author: "Alice", body: "Great post!", }); diff --git a/packages/astro/e2e/fixtures/actions-react-19/db/seed.ts b/packages/astro/e2e/fixtures/actions-react-19/db/seed.ts index 11dc55f7fe00..4e54174b1ede 100644 --- a/packages/astro/e2e/fixtures/actions-react-19/db/seed.ts +++ b/packages/astro/e2e/fixtures/actions-react-19/db/seed.ts @@ -3,12 +3,12 @@ import { db, Likes, Comment } from "astro:db"; // https://astro.build/db/seed export default async function seed() { await db.insert(Likes).values({ - postId: "first-post.md", + postId: "first-post", likes: 10, }); await db.insert(Comment).values({ - postId: "first-post.md", + postId: "first-post", author: "Alice", body: "Great post!", }); diff --git a/packages/astro/e2e/fixtures/actions-react-19/src/pages/blog/[...slug].astro b/packages/astro/e2e/fixtures/actions-react-19/src/pages/blog/[...slug].astro index f89badfcb05a..59824d83efb2 100644 --- a/packages/astro/e2e/fixtures/actions-react-19/src/pages/blog/[...slug].astro +++ b/packages/astro/e2e/fixtures/actions-react-19/src/pages/blog/[...slug].astro @@ -1,5 +1,5 @@ --- -import { type CollectionEntry, getCollection, getEntry } from 'astro:content'; +import { type CollectionEntry, getCollection, getEntry, render } from 'astro:content'; import BlogPost from '../../layouts/BlogPost.astro'; import { db, eq, Likes } from 'astro:db'; import { Like, LikeWithActionState } from '../../components/Like'; @@ -17,7 +17,7 @@ export async function getStaticPaths() { type Props = CollectionEntry<'blog'>; const post = await getEntry('blog', Astro.params.slug)!; -const { Content } = await post.render(); +const { Content } = await render(post) const likesRes = await db.select().from(Likes).where(eq(Likes.postId, post.id)).get()!; --- diff --git a/packages/astro/test/content-collections-render.test.js b/packages/astro/test/content-collections-render.test.js index 259034218721..2338b04e0dc3 100644 --- a/packages/astro/test/content-collections-render.test.js +++ b/packages/astro/test/content-collections-render.test.js @@ -142,17 +142,23 @@ describe('Content Collections - render()', () => { it('getCollection should return new instances of the array to be mutated safely', async () => { const app = await fixture.loadTestAdapterApp(); - let request = new Request('http://example.com/sort-blog-collection'); + let request = new Request('http://example.com/'); let response = await app.render(request); let html = await response.text(); let $ = cheerio.load(html); - assert.equal($('li').first().text(), 'Launch week!'); + const firstText = $('li').first().text(); + + request = new Request('http://example.com/sort-blog-collection'); + response = await app.render(request); + html = await response.text(); + $ = cheerio.load(html); + assert.notEqual($('li').first().text(), firstText); request = new Request('http://example.com/'); response = await app.render(request); html = await response.text(); $ = cheerio.load(html); - assert.equal($('li').first().text(), 'Hello world'); + assert.equal($('li').first().text(), firstText); }); }); @@ -212,24 +218,5 @@ describe('Content Collections - render()', () => { assert.equal(h2.length, 1); assert.equal(h2.attr('data-components-export-applied'), 'true'); }); - - it.skip('Supports layout prop with recursive getCollection() call - DEPRECATED: layout props removed in Astro 5', async () => { - const response = await fixture.fetch('/with-layout-prop', { method: 'GET' }); - assert.equal(response.status, 200); - - const html = await response.text(); - const $ = cheerio.load(html); - - const body = $('body'); - assert.equal(body.attr('data-layout-prop'), 'true'); - - const h1 = $('h1'); - assert.equal(h1.length, 1); - assert.equal(h1.text(), 'With Layout Prop'); - - const h2 = $('h2'); - assert.equal(h2.length, 1); - assert.equal(h2.text(), 'Content with a layout prop'); - }); }); }); diff --git a/packages/astro/test/content-collections.test.js b/packages/astro/test/content-collections.test.js index d01f9f05ec6b..c7664d541840 100644 --- a/packages/astro/test/content-collections.test.js +++ b/packages/astro/test/content-collections.test.js @@ -233,10 +233,7 @@ describe('Content Collections', () => { } catch (e) { error = e.message; } - assert.equal( - error, - 'Found legacy content config file in "src/content/config.mjs". Please move this file to "src/content.config.mjs" and ensure each collection has a loader defined.', - ); + assert.ok(error.startsWith('Found legacy content config file in')); }); }); diff --git a/packages/astro/test/fixtures/content/src/content/blog/one.md b/packages/astro/test/fixtures/content/src/content/blog/1-one.md similarity index 69% rename from packages/astro/test/fixtures/content/src/content/blog/one.md rename to packages/astro/test/fixtures/content/src/content/blog/1-one.md index f4293f0a5ee5..6488b194cc70 100644 --- a/packages/astro/test/fixtures/content/src/content/blog/one.md +++ b/packages/astro/test/fixtures/content/src/content/blog/1-one.md @@ -1,5 +1,5 @@ --- -title: Hello world +title: 1 Hello world description: Just a demo --- This is a post diff --git a/packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-component-scripts.mdx b/packages/astro/test/fixtures/content/src/content/blog/promo/2-launch-week-component-scripts.mdx similarity index 93% rename from packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-component-scripts.mdx rename to packages/astro/test/fixtures/content/src/content/blog/promo/2-launch-week-component-scripts.mdx index 586c50da7e66..cdf3aecef617 100644 --- a/packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-component-scripts.mdx +++ b/packages/astro/test/fixtures/content/src/content/blog/promo/2-launch-week-component-scripts.mdx @@ -1,5 +1,5 @@ --- -title: 'Launch week!' +title: '2 Launch week!' description: 'Join us for the exciting launch of SPACE BLOG' publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' tags: ['announcement'] diff --git a/packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-components-export.mdx b/packages/astro/test/fixtures/content/src/content/blog/promo/3-launch-week-components-export.mdx similarity index 93% rename from packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-components-export.mdx rename to packages/astro/test/fixtures/content/src/content/blog/promo/3-launch-week-components-export.mdx index 40012b8ef46b..c803d5b8e466 100644 --- a/packages/astro/test/fixtures/content/src/content/blog/promo/launch-week-components-export.mdx +++ b/packages/astro/test/fixtures/content/src/content/blog/promo/3-launch-week-components-export.mdx @@ -1,5 +1,5 @@ --- -title: 'Launch week!' +title: '3 Launch week!' description: 'Join us for the exciting launch of SPACE BLOG' publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' tags: ['announcement'] diff --git a/packages/astro/test/fixtures/content/src/content/blog/promo/launch-week.mdx b/packages/astro/test/fixtures/content/src/content/blog/promo/4-launch-week.mdx similarity index 92% rename from packages/astro/test/fixtures/content/src/content/blog/promo/launch-week.mdx rename to packages/astro/test/fixtures/content/src/content/blog/promo/4-launch-week.mdx index 22ed07c43f0c..9b22e505e3f1 100644 --- a/packages/astro/test/fixtures/content/src/content/blog/promo/launch-week.mdx +++ b/packages/astro/test/fixtures/content/src/content/blog/promo/4-launch-week.mdx @@ -1,5 +1,5 @@ --- -title: 'Launch week!' +title: '4 Launch week!' description: 'Join us for the exciting launch of SPACE BLOG' publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)' tags: ['announcement'] diff --git a/packages/astro/test/fixtures/content/src/pages/launch-week-component-scripts.astro b/packages/astro/test/fixtures/content/src/pages/launch-week-component-scripts.astro index b8e36d1f52e6..928eae258ae7 100644 --- a/packages/astro/test/fixtures/content/src/pages/launch-week-component-scripts.astro +++ b/packages/astro/test/fixtures/content/src/pages/launch-week-component-scripts.astro @@ -1,7 +1,7 @@ --- import { getEntry, render } from 'astro:content'; -const entry = await getEntry('blog', 'promo/launch-week-component-scripts'); +const entry = await getEntry('blog', 'promo/2-launch-week-component-scripts'); const { Content } = await render(entry); --- diff --git a/packages/astro/test/fixtures/content/src/pages/launch-week-components-export.astro b/packages/astro/test/fixtures/content/src/pages/launch-week-components-export.astro index a77244f79f00..bd40b4d57857 100644 --- a/packages/astro/test/fixtures/content/src/pages/launch-week-components-export.astro +++ b/packages/astro/test/fixtures/content/src/pages/launch-week-components-export.astro @@ -1,7 +1,7 @@ --- import { getEntry, render } from 'astro:content'; -const entry = await getEntry('blog', 'promo/launch-week-components-export'); +const entry = await getEntry('blog', 'promo/3-launch-week-components-export'); const { Content } = await render(entry); --- diff --git a/packages/astro/test/fixtures/content/src/pages/launch-week.astro b/packages/astro/test/fixtures/content/src/pages/launch-week.astro index 71231592330a..ee4b361f3819 100644 --- a/packages/astro/test/fixtures/content/src/pages/launch-week.astro +++ b/packages/astro/test/fixtures/content/src/pages/launch-week.astro @@ -1,7 +1,7 @@ --- import { getEntry, render } from 'astro:content'; -const entry = await getEntry('blog', 'promo/launch-week'); +const entry = await getEntry('blog', 'promo/4-launch-week'); const { Content } = await render(entry); --- diff --git a/packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro b/packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro index 5426068197ad..9eb681ee259c 100644 --- a/packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro +++ b/packages/astro/test/fixtures/content/src/pages/sort-blog-collection.astro @@ -4,7 +4,7 @@ import { getCollection } from 'astro:content'; const blog = await getCollection('blog'); // Sort descending by title, make sure mutating `blog` doesn't mutate other pages that call `getCollection` too -blog.sort((a, b) => a.data.title < b.data.title ? 1 : -1) +blog.sort((a, b) => b.data.title.localeCompare(a.data.title)); --- diff --git a/packages/astro/test/fixtures/core-image-deletion/src/content.config.ts b/packages/astro/test/fixtures/core-image-deletion/src/content.config.ts index 0e376a0cc885..89756e2d112c 100644 --- a/packages/astro/test/fixtures/core-image-deletion/src/content.config.ts +++ b/packages/astro/test/fixtures/core-image-deletion/src/content.config.ts @@ -2,7 +2,7 @@ import { defineCollection, z } from 'astro:content'; import { glob } from 'astro/loaders'; const blog = defineCollection({ - loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), + loader: glob({ pattern: '**/*.*', base: './src/content/blog' }), schema: z.object({ title: z.string(), }) From 861b9cc770a05d9fcfcb2f1f442a3ba41e94b510 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Wed, 24 Sep 2025 14:13:33 +0200 Subject: [PATCH 08/36] feat!: remove emitESMImage() (#14426) Co-authored-by: Matt Kane --- .changeset/busy-olives-chew.md | 22 +++++ packages/astro/src/assets/utils/index.ts | 8 +- .../astro/src/assets/utils/node/emitAsset.ts | 85 ------------------- .../astro/src/assets/vite-plugin-assets.ts | 11 +-- packages/astro/src/content/content-layer.ts | 2 - packages/astro/src/content/runtime-assets.ts | 8 +- packages/astro/src/content/utils.ts | 10 +-- .../content/vite-plugin-content-imports.ts | 4 - .../markdoc/src/content-entry-type.ts | 10 +-- 9 files changed, 31 insertions(+), 129 deletions(-) create mode 100644 .changeset/busy-olives-chew.md diff --git a/.changeset/busy-olives-chew.md b/.changeset/busy-olives-chew.md new file mode 100644 index 000000000000..060a5d8f03f6 --- /dev/null +++ b/.changeset/busy-olives-chew.md @@ -0,0 +1,22 @@ +--- +'astro': major +--- + +Removes the deprecated `emitESMImage()` function + +In Astro 5.6.2, the `emitESMImage()` function was deprecated in favor of `emitImageMetadata()`, which removes two deprecated arguments that were not meant to be exposed for public use: `_watchMode` and `experimentalSvgEnabled`. + +Astro 6.0 removes `emitESMImage()` entirely. Update to `emitImageMetadata()` to keep your current behavior. + +#### What should I do? + +Replace all occurrences of the `emitESMImage()` with `emitImageMetadata()` and remove unused arguments: + +```diff +-import { emitESMImage } from 'astro/assets/utils'; ++import { emitImageMetadata } from 'astro/assets/utils'; + +const imageId = '/images/photo.jpg'; +-const result = await emitESMImage(imageId, false, false); ++const result = await emitImageMetadata(imageId); +``` diff --git a/packages/astro/src/assets/utils/index.ts b/packages/astro/src/assets/utils/index.ts index 0960d01ecf2f..6733481a4263 100644 --- a/packages/astro/src/assets/utils/index.ts +++ b/packages/astro/src/assets/utils/index.ts @@ -7,13 +7,7 @@ export { isESMImportedImage, isRemoteImage, resolveSrc } from './imageKind.js'; export { imageMetadata } from './metadata.js'; -export { - /** - * @deprecated - */ - emitESMImage, - emitImageMetadata, -} from './node/emitAsset.js'; +export { emitImageMetadata } from './node/emitAsset.js'; export { getOrigQueryParams } from './queryParams.js'; export { isRemoteAllowed, diff --git a/packages/astro/src/assets/utils/node/emitAsset.ts b/packages/astro/src/assets/utils/node/emitAsset.ts index 59e1150fa71a..eb60e54c0b48 100644 --- a/packages/astro/src/assets/utils/node/emitAsset.ts +++ b/packages/astro/src/assets/utils/node/emitAsset.ts @@ -59,91 +59,6 @@ async function handleSvgDeduplication( } } -/** - * Processes an image file and emits its metadata and optionally its contents. This function supports both build and development modes. - * - * @param {string | undefined} id - The identifier or path of the image file to process. If undefined, the function returns immediately. - * @param {boolean} _watchMode - **Deprecated**: Indicates if the method is operating in watch mode. This parameter will be removed or updated in the future. - * @param {boolean} _experimentalSvgEnabled - **Deprecated**: A flag to enable experimental handling of SVG files. Embeds SVG file data if set to true. - * @param {FileEmitter | undefined} [fileEmitter] - Function for emitting files during the build process. May throw in certain scenarios. - * @return {Promise} Resolves to metadata with optional image contents or `undefined` if processing fails. - */ -// We want to internally use this function until we fix the memory in the SVG features -export async function emitESMImage( - id: string | undefined, - /** @deprecated */ - _watchMode: boolean, - // FIX: in Astro 6, this function should not be passed in dev mode at all. - // Or rethink the API so that a function that throws isn't passed through. - /** @deprecated */ - _experimentalSvgEnabled: boolean, - fileEmitter?: FileEmitter, -): Promise { - if (!id) { - return undefined; - } - - const url = pathToFileURL(id); - let fileData: Buffer; - try { - fileData = await fs.readFile(url); - } catch { - return undefined; - } - - const fileMetadata = await imageMetadata(fileData, id); - - const emittedImage: Omit = { - src: '', - ...fileMetadata, - }; - - // Private for now, we generally don't want users to rely on filesystem paths, but we need it so that we can maybe remove the original asset from the build if it's unused. - Object.defineProperty(emittedImage, 'fsPath', { - enumerable: false, - writable: false, - value: id, - }); - - // Build - let isBuild = typeof fileEmitter === 'function'; - if (isBuild) { - const pathname = decodeURI(url.pathname); - const filename = path.basename(pathname, path.extname(pathname) + `.${fileMetadata.format}`); - - try { - let handle: string; - - if (fileMetadata.format === 'svg') { - // check if this content already exists - handle = await handleSvgDeduplication(fileData, filename, fileEmitter!); - } else { - // Non-SVG assets: emit normally - handle = fileEmitter!({ - name: filename, - source: fileData, - type: 'asset', - }); - } - - emittedImage.src = `__ASTRO_ASSET_IMAGE__${handle}__`; - } catch { - isBuild = false; - } - } - - if (!isBuild) { - // Pass the original file information through query params so we don't have to load the file twice - url.searchParams.append('origWidth', fileMetadata.width.toString()); - url.searchParams.append('origHeight', fileMetadata.height.toString()); - url.searchParams.append('origFormat', fileMetadata.format); - - emittedImage.src = `/@fs` + prependForwardSlash(fileURLToNormalizedPath(url)); - } - - return emittedImage as ImageMetadataWithContents; -} - /** * Processes an image file and emits its metadata and optionally its contents. This function supports both build and development modes. * diff --git a/packages/astro/src/assets/vite-plugin-assets.ts b/packages/astro/src/assets/vite-plugin-assets.ts index edb5f2e488df..411745150f8f 100644 --- a/packages/astro/src/assets/vite-plugin-assets.ts +++ b/packages/astro/src/assets/vite-plugin-assets.ts @@ -18,7 +18,7 @@ import { fontsPlugin } from './fonts/vite-plugin-fonts.js'; import type { ImageTransform } from './types.js'; import { getAssetsPrefix } from './utils/getAssetsPrefix.js'; import { isESMImportedImage } from './utils/imageKind.js'; -import { emitESMImage } from './utils/node/emitAsset.js'; +import { emitImageMetadata } from './utils/node/emitAsset.js'; import { getProxyCode } from './utils/proxy.js'; import { makeSvgComponent } from './utils/svg.js'; import { hashTransform, propsToFilename } from './utils/transformToPath.js'; @@ -221,13 +221,8 @@ export default function assets({ fs, settings, sync, logger }: Options): vite.Pl return; } - const emitFile = shouldEmitFile ? this.emitFile.bind(this) : undefined; - const imageMetadata = await emitESMImage( - id, - this.meta.watchMode, - id.endsWith('.svg'), - emitFile, - ); + const fileEmitter = shouldEmitFile ? this.emitFile.bind(this) : undefined; + const imageMetadata = await emitImageMetadata(id, fileEmitter); if (!imageMetadata) { throw new AstroError({ diff --git a/packages/astro/src/content/content-layer.ts b/packages/astro/src/content/content-layer.ts index 0f819847bd37..227c5028ba9a 100644 --- a/packages/astro/src/content/content-layer.ts +++ b/packages/astro/src/content/content-layer.ts @@ -282,8 +282,6 @@ class ContentLayer { }, collectionWithResolvedSchema, false, - // FUTURE: Remove in this in v6 - id.endsWith('.svg'), ); return parsedData; diff --git a/packages/astro/src/content/runtime-assets.ts b/packages/astro/src/content/runtime-assets.ts index 7bba50ee1154..b23642faa03a 100644 --- a/packages/astro/src/content/runtime-assets.ts +++ b/packages/astro/src/content/runtime-assets.ts @@ -1,22 +1,18 @@ import type { PluginContext } from 'rollup'; import { z } from 'zod'; import type { ImageMetadata, OmitBrand } from '../assets/types.js'; -import { emitESMImage } from '../assets/utils/node/emitAsset.js'; +import { emitImageMetadata } from '../assets/utils/node/emitAsset.js'; export function createImage( pluginContext: PluginContext, shouldEmitFile: boolean, entryFilePath: string, - experimentalSvgEnabled: boolean, ) { return () => { return z.string().transform(async (imagePath, ctx) => { const resolvedFilePath = (await pluginContext.resolve(imagePath, entryFilePath))?.id; - const metadata = (await emitESMImage( + const metadata = (await emitImageMetadata( resolvedFilePath, - pluginContext.meta.watchMode, - // FUTURE: Remove in this in v6 - experimentalSvgEnabled, shouldEmitFile ? pluginContext.emitFile : undefined, )) as OmitBrand; diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index 766d27eb93c4..bedd4c93c5a4 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -133,7 +133,6 @@ export async function getEntryDataAndImages< }, collectionConfig: CollectionConfig, shouldEmitFile: boolean, - experimentalSvgEnabled: boolean, pluginContext?: PluginContext, ): Promise<{ data: TOutputData; imageImports: Array }> { let data = entry.unvalidatedData as TOutputData; @@ -145,12 +144,7 @@ export async function getEntryDataAndImages< if (typeof schema === 'function') { if (pluginContext) { schema = schema({ - image: createImage( - pluginContext, - shouldEmitFile, - entry._internal.filePath, - experimentalSvgEnabled, - ), + image: createImage(pluginContext, shouldEmitFile, entry._internal.filePath), }); } else if (collectionConfig.type === CONTENT_LAYER_TYPE) { schema = schema({ @@ -211,14 +205,12 @@ export async function getEntryData( }, collectionConfig: CollectionConfig, shouldEmitFile: boolean, - experimentalSvgEnabled: boolean, pluginContext?: PluginContext, ) { const { data } = await getEntryDataAndImages( entry, collectionConfig, shouldEmitFile, - experimentalSvgEnabled, pluginContext, ); return data; diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts index 23a6147ce363..4950d2d9b2e7 100644 --- a/packages/astro/src/content/vite-plugin-content-imports.ts +++ b/packages/astro/src/content/vite-plugin-content-imports.ts @@ -245,8 +245,6 @@ async function getContentEntryModule( { id, collection, _internal, unvalidatedData }, collectionConfig, params.shouldEmitFile, - // FUTURE: Remove in this in v6 - id.endsWith('.svg'), pluginContext, ) : unvalidatedData; @@ -282,8 +280,6 @@ async function getDataEntryModule( { id, collection, _internal, unvalidatedData }, collectionConfig, params.shouldEmitFile, - // FUTURE: Remove in this in v6 - id.endsWith('.svg'), pluginContext, ) : unvalidatedData; diff --git a/packages/integrations/markdoc/src/content-entry-type.ts b/packages/integrations/markdoc/src/content-entry-type.ts index f68e237018ac..bcebff33af94 100644 --- a/packages/integrations/markdoc/src/content-entry-type.ts +++ b/packages/integrations/markdoc/src/content-entry-type.ts @@ -5,7 +5,7 @@ import { parseFrontmatter } from '@astrojs/markdown-remark'; import type { Config as MarkdocConfig, Node } from '@markdoc/markdoc'; import Markdoc from '@markdoc/markdoc'; import type { AstroConfig, ContentEntryType } from 'astro'; -import { emitESMImage } from 'astro/assets/utils'; +import { emitImageMetadata } from 'astro/assets/utils'; import type { Rollup, ErrorPayload as ViteErrorPayload } from 'vite'; import type { ComponentConfig } from './config.js'; import { htmlTokenTransform } from './html/transform/html-token-transform.js'; @@ -319,13 +319,7 @@ async function emitOptimizedImages( const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath); if (resolved?.id && fs.existsSync(new URL(prependForwardSlash(resolved.id), 'file://'))) { - const src = await emitESMImage( - resolved.id, - ctx.pluginContext.meta.watchMode, - // FUTURE: Remove in this in v6 - resolved.id.endsWith('.svg'), - ctx.pluginContext.emitFile, - ); + const src = await emitImageMetadata(resolved.id, ctx.pluginContext.emitFile); const fsPath = resolved.id; From 4f11510c9ed932f5cb6d1075b1172909dd5db23e Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Thu, 25 Sep 2025 10:48:36 +0200 Subject: [PATCH 09/36] feat(astro)!: update `i18n.redirectToDefaultLocale` default (#14406) Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/sad-lines-hear.md | 27 ++++++++++++++++++ .../astro/src/core/config/schemas/base.ts | 3 +- .../astro/src/core/config/schemas/refined.ts | 28 +++++++++---------- packages/astro/src/integrations/hooks.ts | 8 +----- packages/astro/src/types/public/config.ts | 6 ++-- .../i18n-double-prefix/astro.config.mjs | 2 +- .../i18n-double-prefix/src/pages/index.astro | 0 .../astro.config.mjs | 1 + 8 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 .changeset/sad-lines-hear.md create mode 100644 packages/astro/test/fixtures/i18n-double-prefix/src/pages/index.astro diff --git a/.changeset/sad-lines-hear.md b/.changeset/sad-lines-hear.md new file mode 100644 index 000000000000..91cd76e4edf2 --- /dev/null +++ b/.changeset/sad-lines-hear.md @@ -0,0 +1,27 @@ +--- +'astro': major +--- + +Changes the default routing configuration value of `i18n.routing.redirectToDefaultLocale` from `true` to `false`. + +In Astro v5.0, `i18n.routing.redirectToDefaultLocale` was `true` by default. When combined with the `i18n.routing.prefixDefaultLocale` default value of `false`, the resulting redirects could cause infinite loops. + +In Astro v6.0, `i18n.routing.redirectToDefaultLocale` now defaults to `false`. Additionally, it can now only be used if `i18n.routing.prefixDefaultLocale` is set to `true`. + +#### What should I do? + +Review your Astro `i18n` config as you may now need to explicitly set values for `redirectToDefaultLocale` and `prefixDefaultLocale` to recreate your project's previous behavior. + +```diff +// astro.config.mjs +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + i18n: { + routing: { + prefixDefaultLocale: true, ++ redirectToDefaultLocale: true + } + } +}) +``` diff --git a/packages/astro/src/core/config/schemas/base.ts b/packages/astro/src/core/config/schemas/base.ts index 2b0be0910f65..ddba2f3f2ca6 100644 --- a/packages/astro/src/core/config/schemas/base.ts +++ b/packages/astro/src/core/config/schemas/base.ts @@ -407,8 +407,7 @@ export const AstroConfigSchema = z.object({ .or( z.object({ prefixDefaultLocale: z.boolean().optional().default(false), - // TODO: Astro 6.0 change to false - redirectToDefaultLocale: z.boolean().optional().default(true), + redirectToDefaultLocale: z.boolean().optional().default(false), fallbackType: z.enum(['redirect', 'rewrite']).optional().default('redirect'), }), ) diff --git a/packages/astro/src/core/config/schemas/refined.ts b/packages/astro/src/core/config/schemas/refined.ts index 1caff3ad2794..d5940f224fcf 100644 --- a/packages/astro/src/core/config/schemas/refined.ts +++ b/packages/astro/src/core/config/schemas/refined.ts @@ -42,21 +42,19 @@ export const AstroConfigRefinedSchema = z.custom().superRefine((con } } - // TODO: Astro 6.0 - // Uncomment this validation check, and change the default value of redirectToDefaultLocale to false - // if ( - // config.i18n && - // typeof config.i18n.routing !== 'string' && - // config.i18n.routing.prefixDefaultLocale === false && - // config.i18n.routing.redirectToDefaultLocale === true - // ) { - // ctx.addIssue({ - // code: z.ZodIssueCode.custom, - // message: - // 'The option `i18n.routing.redirectToDefaultLocale` can be used only when `i18n.routing.prefixDefaultLocale` is set to `true`, otherwise redirects might cause infinite loops. Remove the option `i18n.routing.redirectToDefaultLocale`, or change its value to `false`.', - // path: ['i18n', 'routing', 'redirectToDefaultLocale'], - // }); - // } + if ( + config.i18n && + typeof config.i18n.routing !== 'string' && + config.i18n.routing.prefixDefaultLocale === false && + config.i18n.routing.redirectToDefaultLocale === true + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + 'The option `i18n.routing.redirectToDefaultLocale` can be used only when `i18n.routing.prefixDefaultLocale` is set to `true`, otherwise redirects might cause infinite loops. Remove the option `i18n.routing.redirectToDefaultLocale`, or change its value to `false`.', + path: ['i18n', 'routing', 'redirectToDefaultLocale'], + }); + } if (config.outDir.toString().startsWith(config.publicDir.toString())) { ctx.addIssue({ diff --git a/packages/astro/src/integrations/hooks.ts b/packages/astro/src/integrations/hooks.ts index 7e28a782d7d6..6d954cb843dc 100644 --- a/packages/astro/src/integrations/hooks.ts +++ b/packages/astro/src/integrations/hooks.ts @@ -353,13 +353,6 @@ export async function runHookConfigSetup({ updatedSettings.renderers.push(astroJSXRenderer); } - // TODO: Astro 6.0 - // Remove this hack to avoid breaking changes, and change the default value of redirectToDefaultLocale - if (updatedConfig.i18n && typeof updatedConfig.i18n.routing !== 'string') { - updatedConfig.i18n.routing.redirectToDefaultLocale ??= - updatedConfig.i18n.routing.prefixDefaultLocale || false; - } - updatedSettings.config = updatedConfig; return updatedSettings; } @@ -609,6 +602,7 @@ export async function runHookBuildGenerated({ type RunHookBuildDone = { settings: AstroSettings; pages: string[]; + // TODO: remove in Astro 6 routes: RouteData[]; logger: Logger; }; diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index 1d35329e8d6e..bd82172a645a 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -1770,14 +1770,14 @@ export interface ViteUserConfig extends OriginalViteUserConfig { * @name i18n.routing.redirectToDefaultLocale * @kind h4 * @type {boolean} - * @default `true` + * @default `false` * @version 4.2.0 * @description * * Configures whether or not the home URL (`/`) generated by `src/pages/index.astro` * will redirect to `/[defaultLocale]` when `prefixDefaultLocale: true` is set. * - * Set `redirectToDefaultLocale: false` to disable this automatic redirection at the root of your site: + * Set `redirectToDefaultLocale: true` to enable this automatic redirection at the root of your site: * ```js * // astro.config.mjs * export default defineConfig({ @@ -1786,7 +1786,7 @@ export interface ViteUserConfig extends OriginalViteUserConfig { * locales: ["en", "fr"], * routing: { * prefixDefaultLocale: true, - * redirectToDefaultLocale: false + * redirectToDefaultLocale: true * } * } * }) diff --git a/packages/astro/test/fixtures/i18n-double-prefix/astro.config.mjs b/packages/astro/test/fixtures/i18n-double-prefix/astro.config.mjs index e701e7f2d017..a64f1df5d452 100644 --- a/packages/astro/test/fixtures/i18n-double-prefix/astro.config.mjs +++ b/packages/astro/test/fixtures/i18n-double-prefix/astro.config.mjs @@ -11,7 +11,7 @@ export default defineConfig({ }, ], routing: { - prefixDefaultLocale: false, + prefixDefaultLocale: true, redirectToDefaultLocale: true, fallback: { es: 'en', diff --git a/packages/astro/test/fixtures/i18n-double-prefix/src/pages/index.astro b/packages/astro/test/fixtures/i18n-double-prefix/src/pages/index.astro new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/astro/test/fixtures/i18n-routing-prefix-always/astro.config.mjs b/packages/astro/test/fixtures/i18n-routing-prefix-always/astro.config.mjs index 6ab4d1305f35..890cf886222a 100644 --- a/packages/astro/test/fixtures/i18n-routing-prefix-always/astro.config.mjs +++ b/packages/astro/test/fixtures/i18n-routing-prefix-always/astro.config.mjs @@ -11,6 +11,7 @@ export default defineConfig({ ], routing: { prefixDefaultLocale: true, + redirectToDefaultLocale: true } }, base: "/new-site" From df6d2d7bbcaf6b6a327a37a6437d4adade6e2485 Mon Sep 17 00:00:00 2001 From: Florian Lefebvre Date: Thu, 25 Sep 2025 15:51:24 +0200 Subject: [PATCH 10/36] feat!: remove Astro.glob (#14421) Co-authored-by: Sarah Rainsberger <5098874+sarah11918@users.noreply.github.com> --- .changeset/kind-pears-behave.md | 24 +++++++ packages/astro/e2e/errors.test.js | 12 ---- .../src/components/AstroGlobOutsideAstro.js | 3 - .../src/pages/astro-glob-no-match.astro | 3 - .../src/pages/astro-glob-outside-astro.astro | 5 -- packages/astro/package.json | 2 - packages/astro/src/core/create-vite.ts | 2 - packages/astro/src/core/errors/README.md | 2 +- packages/astro/src/core/errors/dev/vite.ts | 25 +------ packages/astro/src/core/errors/errors-data.ts | 3 + packages/astro/src/core/render-context.ts | 1 - .../astro/src/runtime/server/astro-global.ts | 30 --------- packages/astro/src/types/public/context.ts | 22 ------- .../vite-plugin-astro-postprocess/README.md | 3 - .../vite-plugin-astro-postprocess/index.ts | 65 ------------------- packages/astro/test/astro-global.test.js | 6 +- packages/astro/test/astro-scripts.test.js | 4 +- .../astro-global/src/pages/glob.astro | 2 +- .../src/pages/omit-markdown-extensions.astro | 2 +- .../astro-global/src/pages/posts/[page].astro | 2 +- .../src/pages/posts/[slug]/[page].astro | 2 +- .../pages/posts/named-root-page/[page].astro | 2 +- .../posts/optional-root-page/[...page].astro | 2 +- .../astro-scripts/src/pages/glob.astro | 2 +- .../src/pages/episodes/[...page].astro | 2 +- .../src/pages/posts/[slug].astro | 2 +- .../src/components/Footer.astro | 2 +- .../src/components/Navbar.astro | 2 +- .../static-build/src/pages/index.astro | 2 +- packages/astro/test/glob-pages-css.test.js | 2 +- packages/astro/test/static-build.test.js | 2 +- .../test/units/runtime/astro-global.test.js | 29 --------- .../mdx-component/src/pages/glob.astro | 2 +- .../src/pages/[slug].astro | 2 +- .../mdx-infinite-loop/src/pages/index.astro | 2 +- .../fixtures/mdx-slots/src/pages/glob.astro | 2 +- pnpm-lock.yaml | 6 -- 37 files changed, 52 insertions(+), 231 deletions(-) create mode 100644 .changeset/kind-pears-behave.md delete mode 100644 packages/astro/e2e/fixtures/errors/src/components/AstroGlobOutsideAstro.js delete mode 100644 packages/astro/e2e/fixtures/errors/src/pages/astro-glob-no-match.astro delete mode 100644 packages/astro/e2e/fixtures/errors/src/pages/astro-glob-outside-astro.astro delete mode 100644 packages/astro/src/vite-plugin-astro-postprocess/README.md delete mode 100644 packages/astro/src/vite-plugin-astro-postprocess/index.ts delete mode 100644 packages/astro/test/units/runtime/astro-global.test.js diff --git a/.changeset/kind-pears-behave.md b/.changeset/kind-pears-behave.md new file mode 100644 index 000000000000..d34012a6991e --- /dev/null +++ b/.changeset/kind-pears-behave.md @@ -0,0 +1,24 @@ +--- +'astro': major +--- + +Removes `Astro.glob()` + +In Astro 5.0, `Astro.glob()` was deprecated in favor of using `getCollection()` to query your collections, and `import.meta.glob()` to query other source files in your project. + +Astro 6.0 removes `Astro.glob()` entirely. Update to `import.meta.glob()` to keep your current behavior. + +#### What should I do? + +Replace all use of `Astro.glob()` with `import.meta.glob()`. Note that `import.meta.glob()` no longer returns a `Promise`, so you may have to update your code accordingly. You should not require any updates to your glob patterns. + +```astro +--- +// src/pages/blog.astro +-const posts = await Astro.glob('./posts/*.md'); ++const posts = Object.values(import.meta.glob('./posts/*.md', { eager: true })); +--- +{posts.map((post) =>
  • {post.frontmatter.title}
  • )} +``` + +Where appropriate, consider using content collections to organize your content, which has its own newer, more performant querying functions. diff --git a/packages/astro/e2e/errors.test.js b/packages/astro/e2e/errors.test.js index f64a22b5cfc9..85edc09ae960 100644 --- a/packages/astro/e2e/errors.test.js +++ b/packages/astro/e2e/errors.test.js @@ -117,18 +117,6 @@ test.describe('Error display', () => { expect(await page.locator('vite-error-overlay').count()).toEqual(0); }); - test('astro glob no match error', async ({ page, astro }) => { - await page.goto(astro.resolveUrl('/astro-glob-no-match'), { waitUntil: 'networkidle' }); - const message = (await getErrorOverlayContent(page)).message; - expect(message).toMatch('did not return any matching files'); - }); - - test('astro glob used outside of an astro file', async ({ page, astro }) => { - await page.goto(astro.resolveUrl('/astro-glob-outside-astro'), { waitUntil: 'networkidle' }); - const message = (await getErrorOverlayContent(page)).message; - expect(message).toMatch('can only be used in'); - }); - test('can handle DomException errors', async ({ page, astro }) => { await page.goto(astro.resolveUrl('/dom-exception'), { waitUntil: 'networkidle' }); const message = (await getErrorOverlayContent(page)).message; diff --git a/packages/astro/e2e/fixtures/errors/src/components/AstroGlobOutsideAstro.js b/packages/astro/e2e/fixtures/errors/src/components/AstroGlobOutsideAstro.js deleted file mode 100644 index 5307474c035a..000000000000 --- a/packages/astro/e2e/fixtures/errors/src/components/AstroGlobOutsideAstro.js +++ /dev/null @@ -1,3 +0,0 @@ -export function globSomething(Astro) { - return Astro.glob('./*.lua') -} diff --git a/packages/astro/e2e/fixtures/errors/src/pages/astro-glob-no-match.astro b/packages/astro/e2e/fixtures/errors/src/pages/astro-glob-no-match.astro deleted file mode 100644 index a7739af5807f..000000000000 --- a/packages/astro/e2e/fixtures/errors/src/pages/astro-glob-no-match.astro +++ /dev/null @@ -1,3 +0,0 @@ ---- -Astro.glob('./*.lua') ---- \ No newline at end of file diff --git a/packages/astro/e2e/fixtures/errors/src/pages/astro-glob-outside-astro.astro b/packages/astro/e2e/fixtures/errors/src/pages/astro-glob-outside-astro.astro deleted file mode 100644 index 328de56ecf25..000000000000 --- a/packages/astro/e2e/fixtures/errors/src/pages/astro-glob-outside-astro.astro +++ /dev/null @@ -1,5 +0,0 @@ ---- -import { globSomething } from '../components/AstroGlobOutsideAstro' - -globSomething(Astro) ---- \ No newline at end of file diff --git a/packages/astro/package.json b/packages/astro/package.json index 24e26d5f9e11..021ad68d87d1 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -113,7 +113,6 @@ "@capsizecss/unpack": "^2.4.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.2.0", - "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", @@ -130,7 +129,6 @@ "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.25.0", - "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.3.0", "github-slugger": "^2.0.0", diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 6a8bbdacd0d5..eecbfa33070a 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -22,7 +22,6 @@ import astroDevToolbar from '../toolbar/vite-plugin-dev-toolbar.js'; import astroTransitions from '../transitions/vite-plugin-transitions.js'; import type { AstroSettings, RoutesList } from '../types/astro.js'; import astroVitePlugin from '../vite-plugin-astro/index.js'; -import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js'; import { vitePluginAstroServer } from '../vite-plugin-astro-server/index.js'; import configAliasVitePlugin from '../vite-plugin-config-alias/index.js'; import vitePluginFileURL from '../vite-plugin-fileurl/index.js'; @@ -158,7 +157,6 @@ export async function createVite( astroEnv({ settings, sync, envLoader }), markdownVitePlugin({ settings, logger }), htmlVitePlugin(), - astroPostprocessVitePlugin(), astroIntegrationsContainerPlugin({ settings, logger }), astroScriptsPageSSRPlugin({ settings }), astroHeadPlugin(), diff --git a/packages/astro/src/core/errors/README.md b/packages/astro/src/core/errors/README.md index 65e7743a0293..bf0753927da8 100644 --- a/packages/astro/src/core/errors/README.md +++ b/packages/astro/src/core/errors/README.md @@ -97,7 +97,7 @@ If the error cannot be triggered at all anymore, it can deprecated by adding a ` ```js /** * @docs - * @deprecated Removed in Astro v9.8.6 as it is no longer relevant due to... + * @deprecated This error was removed in Astro v6.0.0 along with the removal of... */ ``` diff --git a/packages/astro/src/core/errors/dev/vite.ts b/packages/astro/src/core/errors/dev/vite.ts index b624fdad5d62..f2212dbd03bd 100644 --- a/packages/astro/src/core/errors/dev/vite.ts +++ b/packages/astro/src/core/errors/dev/vite.ts @@ -6,7 +6,7 @@ import type { ErrorPayload } from 'vite'; import type { SSRLoadedRenderer } from '../../../types/public/internal.js'; import type { ModuleLoader } from '../../module-loader/index.js'; import { AstroError, type ErrorWithMetadata } from '../errors.js'; -import { FailedToLoadModuleSSR, InvalidGlob, MdxIntegrationMissingError } from '../errors-data.js'; +import { FailedToLoadModuleSSR, MdxIntegrationMissingError } from '../errors-data.js'; import { createSafeError } from '../utils.js'; import { getDocsForError, renderErrorMarkdown } from './utils.js'; @@ -76,29 +76,6 @@ export function enhanceViteSSRError({ stack: safeError.stack, }) as ErrorWithMetadata; } - - // Since Astro.glob is a wrapper around Vite's import.meta.glob, errors don't show accurate information, let's fix that - if (safeError.message.includes('Invalid glob')) { - const globPattern = /glob: "(.+)" \(/.exec(safeError.message)?.[1]; - - if (globPattern) { - safeError.message = InvalidGlob.message(globPattern); - safeError.name = 'InvalidGlob'; - safeError.title = InvalidGlob.title; - - const line = lns.findIndex((ln) => ln.includes(globPattern)); - - if (line !== -1) { - const column = lns[line]?.indexOf(globPattern); - - safeError.loc = { - file: path, - line: line + 1, - column, - }; - } - } - } } return safeError; diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index d78e6263aac3..9471a5501ec4 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -951,6 +951,7 @@ export const LocalImageUsedWrongly = { * - [Astro.glob](https://docs.astro.build/en/reference/api-reference/#astroglob) * @description * `Astro.glob()` can only be used in `.astro` files. You can use [`import.meta.glob()`](https://vite.dev/guide/features.html#glob-import) instead to achieve the same result. + * @deprecated This error was removed in Astro v6.0.0 along with the removal of `Astro.glob()`. */ export const AstroGlobUsedOutside = { name: 'AstroGlobUsedOutside', @@ -966,6 +967,7 @@ export const AstroGlobUsedOutside = { * - [Astro.glob](https://docs.astro.build/en/reference/api-reference/#astroglob) * @description * `Astro.glob()` did not return any matching files. There might be a typo in the glob pattern. + * @deprecated This error was removed in Astro v6.0.0 along with the removal of `Astro.glob()`. */ export const AstroGlobNoMatch = { name: 'AstroGlobNoMatch', @@ -1075,6 +1077,7 @@ export const FailedToLoadModuleSSR = { * - [Glob Patterns](https://docs.astro.build/en/guides/imports/#glob-patterns) * @description * Astro encountered an invalid glob pattern. This is often caused by the glob pattern not being a valid file path. + * @deprecated This error was removed in Astro v6.0.0 along with the removal of `Astro.glob()`. */ export const InvalidGlob = { name: 'InvalidGlob', diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index 3fc04db5803a..f766f3206c61 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -662,7 +662,6 @@ export class RenderContext { return { generator: astroStaticPartial.generator, - glob: astroStaticPartial.glob, routePattern: this.routeData.route, isPrerendered: this.routeData.prerender, cookies, diff --git a/packages/astro/src/runtime/server/astro-global.ts b/packages/astro/src/runtime/server/astro-global.ts index ea211004abad..31f352805a13 100644 --- a/packages/astro/src/runtime/server/astro-global.ts +++ b/packages/astro/src/runtime/server/astro-global.ts @@ -1,41 +1,11 @@ import { ASTRO_VERSION } from '../../core/constants.js'; -import { AstroError, AstroErrorData } from '../../core/errors/index.js'; import type { AstroGlobalPartial } from '../../types/public/context.js'; -/** Create the Astro.glob() runtime function. */ -function createAstroGlobFn() { - const globHandler = (importMetaGlobResult: Record) => { - // This is created inside of the runtime so we don't have access to the Astro logger. - console.warn(`Astro.glob is deprecated and will be removed in a future major version of Astro. -Use import.meta.glob instead: https://vitejs.dev/guide/features.html#glob-import`); - - if (typeof importMetaGlobResult === 'string') { - throw new AstroError({ - ...AstroErrorData.AstroGlobUsedOutside, - message: AstroErrorData.AstroGlobUsedOutside.message(JSON.stringify(importMetaGlobResult)), - }); - } - let allEntries = [...Object.values(importMetaGlobResult)]; - if (allEntries.length === 0) { - throw new AstroError({ - ...AstroErrorData.AstroGlobNoMatch, - message: AstroErrorData.AstroGlobNoMatch.message(JSON.stringify(importMetaGlobResult)), - }); - } - // Map over the `import()` promises, calling to load them. - return Promise.all(allEntries.map((fn) => fn())); - }; - // Cast the return type because the argument that the user sees (string) is different from the argument - // that the runtime sees post-compiler (Record). - return globHandler as unknown as AstroGlobalPartial['glob']; -} - // This is used to create the top-level Astro global; the one that you can use // inside of getStaticPaths. See the `astroGlobalArgs` option for parameter type. export function createAstro(site: string | undefined): AstroGlobalPartial { return { site: site ? new URL(site) : undefined, generator: `Astro v${ASTRO_VERSION}`, - glob: createAstroGlobFn(), }; } diff --git a/packages/astro/src/types/public/context.ts b/packages/astro/src/types/public/context.ts index 46e1ce811068..bc5cdc0bf6a5 100644 --- a/packages/astro/src/types/public/context.ts +++ b/packages/astro/src/types/public/context.ts @@ -4,14 +4,12 @@ import type { ActionClient, ActionReturnType, } from '../../actions/runtime/virtual/server.js'; -import type { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from '../../core/constants.js'; import type { AstroCookies } from '../../core/cookies/cookies.js'; import type { CspDirective, CspHash } from '../../core/csp/config.js'; import type { AstroSession } from '../../core/session.js'; import type { AstroComponentFactory } from '../../runtime/server/index.js'; import type { Params, RewritePayload } from './common.js'; import type { ValidRedirectStatus } from './config.js'; -import type { AstroInstance, MarkdownInstance, MDXInstance } from './content.js'; /** * Astro global available in all contexts in .astro files @@ -208,27 +206,7 @@ export interface AstroGlobal< }; } -/** Union type of supported markdown file extensions */ -type MarkdownFileExtension = (typeof SUPPORTED_MARKDOWN_FILE_EXTENSIONS)[number]; - export interface AstroGlobalPartial { - /** - * Fetch local files into your static site setup - * - * Example usage: - * ```typescript - * const posts = await Astro.glob('../pages/post/*.md'); - * ``` - * - * [Astro reference](https://docs.astro.build/en/reference/api-reference/#astroglob) - * @deprecated Astro.glob is deprecated and will be removed in the next major version of Astro. Use `import.meta.glob` instead: https://vitejs.dev/guide/features.html#glob-import - */ - glob(globStr: `${any}.astro`): Promise; - glob>( - globStr: `${any}${MarkdownFileExtension}`, - ): Promise[]>; - glob>(globStr: `${any}.mdx`): Promise[]>; - glob>(globStr: string): Promise; /** * Returns a [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) object built from the [site](https://docs.astro.build/en/reference/configuration-reference/#site) config option * diff --git a/packages/astro/src/vite-plugin-astro-postprocess/README.md b/packages/astro/src/vite-plugin-astro-postprocess/README.md deleted file mode 100644 index f4cc5fd6cced..000000000000 --- a/packages/astro/src/vite-plugin-astro-postprocess/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# vite-plugin-astro-postprocess - -Adds last-minute transforms to `.astro` files diff --git a/packages/astro/src/vite-plugin-astro-postprocess/index.ts b/packages/astro/src/vite-plugin-astro-postprocess/index.ts deleted file mode 100644 index 0e48d8a663ac..000000000000 --- a/packages/astro/src/vite-plugin-astro-postprocess/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { parse } from 'acorn'; -import type { Node as ESTreeNode } from 'estree-walker'; -import { walk } from 'estree-walker'; -import MagicString from 'magic-string'; -import type { Plugin } from 'vite'; -import { isMarkdownFile } from '../core/util.js'; - -// Check for `Astro.glob()`. Be very forgiving of whitespace. False positives are okay. -const ASTRO_GLOB_REGEX = /Astro2?\s*\.\s*glob\s*\(/; - -export default function astro(): Plugin { - return { - name: 'astro:postprocess', - async transform(code, id) { - // Currently only supported in ".astro" and ".md" (or any alternative markdown file extension like `.markdown`) files - if (!id.endsWith('.astro') && !isMarkdownFile(id)) { - return null; - } - - // Optimization: Detect usage with a quick string match. - // Only perform the transform if this function is found - if (!ASTRO_GLOB_REGEX.test(code)) { - return null; - } - - let s: MagicString | undefined; - const ast = parse(code, { - ecmaVersion: 'latest', - sourceType: 'module', - }); - - walk(ast as ESTreeNode, { - enter(node: any) { - // Transform `Astro.glob("./pages/*.astro")` to `Astro.glob(import.meta.glob("./pages/*.astro"), () => "./pages/*.astro")` - // Also handle for `Astro2.glob()` - if ( - node.type === 'CallExpression' && - node.callee.type === 'MemberExpression' && - node.callee.property.name === 'glob' && - (node.callee.object.name === 'Astro' || node.callee.object.name === 'Astro2') && - node.arguments.length - ) { - const firstArgStart = node.arguments[0].start; - const firstArgEnd = node.arguments[0].end; - const lastArgEnd = node.arguments[node.arguments.length - 1].end; - const firstArg = code.slice(firstArgStart, firstArgEnd); - s ??= new MagicString(code); - s.overwrite( - firstArgStart, - lastArgEnd, - `import.meta.glob(${firstArg}), () => ${firstArg}`, - ); - } - }, - }); - - if (s) { - return { - code: s.toString(), - map: s.generateMap({ hires: 'boundary' }), - }; - } - }, - }; -} diff --git a/packages/astro/test/astro-global.test.js b/packages/astro/test/astro-global.test.js index c996de4364b3..c8f8e2292537 100644 --- a/packages/astro/test/astro-global.test.js +++ b/packages/astro/test/astro-global.test.js @@ -38,7 +38,7 @@ describe('Astro Global', () => { assert.equal($('#nested-child-pathname').text(), '/blog/'); }); - it('Astro.glob() returned `url` metadata of each markdown file extensions DOES NOT include the extension', async () => { + it('import.meta.glob() returned `url` metadata of each markdown file extensions DOES NOT include the extension', async () => { const html = await fixture.fetch('/blog/omit-markdown-extensions/').then((res) => res.text()); const $ = cheerio.load(html); assert.equal( @@ -90,13 +90,13 @@ describe('Astro Global', () => { assert.equal($('#site').attr('href'), 'https://mysite.dev/subsite/'); }); - it('Astro.glob() correctly returns an array of all posts', async () => { + it('import.meta.glob() correctly returns an array of all posts', async () => { const html = await fixture.readFile('/posts/1/index.html'); const $ = cheerio.load(html); assert.equal($('.post-url').attr('href'), '/blog/post/post-2'); }); - it('Astro.glob() correctly returns meta info for MD and Astro files', async () => { + it('import.meta.glob() correctly returns meta info for MD and Astro files', async () => { const html = await fixture.readFile('/glob/index.html'); const $ = cheerio.load(html); assert.equal($('[data-file]').length, 8); diff --git a/packages/astro/test/astro-scripts.test.js b/packages/astro/test/astro-scripts.test.js index 528a147cbf17..ed7b849ae701 100644 --- a/packages/astro/test/astro-scripts.test.js +++ b/packages/astro/test/astro-scripts.test.js @@ -58,7 +58,7 @@ describe('Scripts', () => { assert.equal(entryURL.includes('_astro/'), true); }); - it('Scripts added via Astro.glob are hoisted', async () => { + it('Scripts added via import.meta.glob are hoisted', async () => { let glob = await fixture.readFile('/glob/index.html'); let $ = cheerio.load(glob); @@ -131,7 +131,7 @@ describe('Scripts', () => { await devServer.stop(); }); - it('Scripts added via Astro.glob are hoisted', async () => { + it('Scripts added via import.meta.glob are hoisted', async () => { let res = await fixture.fetch('/glob'); let html = await res.text(); let $ = cheerio.load(html); diff --git a/packages/astro/test/fixtures/astro-global/src/pages/glob.astro b/packages/astro/test/fixtures/astro-global/src/pages/glob.astro index c25d3733dca9..85a371881ac4 100644 --- a/packages/astro/test/fixtures/astro-global/src/pages/glob.astro +++ b/packages/astro/test/fixtures/astro-global/src/pages/glob.astro @@ -1,5 +1,5 @@ --- -const data = await Astro.glob('./post/**/*'); +const data = Object.values(import.meta.glob('./post/**/*', { eager: true })); --- diff --git a/packages/astro/test/fixtures/astro-global/src/pages/omit-markdown-extensions.astro b/packages/astro/test/fixtures/astro-global/src/pages/omit-markdown-extensions.astro index 68101d447d60..794683a2ac39 100644 --- a/packages/astro/test/fixtures/astro-global/src/pages/omit-markdown-extensions.astro +++ b/packages/astro/test/fixtures/astro-global/src/pages/omit-markdown-extensions.astro @@ -1,7 +1,7 @@ --- import Route from "../components/Route.astro"; -const markdownPosts = await Astro.glob('./post/**/*.{markdown,mdown,mkdn,mkd,mdwn,md}'); +const markdownPosts = Object.values(import.meta.glob('./post/**/*.{markdown,mdown,mkdn,mkd,mdwn,md}', { eager: true })); const markdownExtensions = /(\.(markdown|mdown|mkdn|mkd|mdwn|md))$/g const aUrlContainsExtension = markdownPosts.some((page:any)=> { return markdownExtensions.test(page.url) diff --git a/packages/astro/test/fixtures/astro-global/src/pages/posts/[page].astro b/packages/astro/test/fixtures/astro-global/src/pages/posts/[page].astro index 7bdaa3b364d7..deefd1a61e24 100644 --- a/packages/astro/test/fixtures/astro-global/src/pages/posts/[page].astro +++ b/packages/astro/test/fixtures/astro-global/src/pages/posts/[page].astro @@ -2,7 +2,7 @@ import Route from "../../components/Route.astro"; export async function getStaticPaths({paginate}) { - const data = await Astro.glob('../post/*.md'); + const data = Object.values(import.meta.glob('../post/*.md', { eager: true })); return paginate(data, {pageSize: 1}); } const { page } = Astro.props; diff --git a/packages/astro/test/fixtures/astro-pagination/src/pages/posts/[slug]/[page].astro b/packages/astro/test/fixtures/astro-pagination/src/pages/posts/[slug]/[page].astro index 33df98cf598c..ea824e7053be 100644 --- a/packages/astro/test/fixtures/astro-pagination/src/pages/posts/[slug]/[page].astro +++ b/packages/astro/test/fixtures/astro-pagination/src/pages/posts/[slug]/[page].astro @@ -1,6 +1,6 @@ --- export async function getStaticPaths({paginate}) { - const allPosts = await Astro.glob('../../post/*.md'); + const allPosts = Object.values(import.meta.glob('../../post/*.md', { eager: true })); return ['red', 'blue'].flatMap((filter) => { const filteredPosts = allPosts.filter((post) => post.frontmatter.tag === filter); return paginate(filteredPosts, { diff --git a/packages/astro/test/fixtures/astro-pagination/src/pages/posts/named-root-page/[page].astro b/packages/astro/test/fixtures/astro-pagination/src/pages/posts/named-root-page/[page].astro index c908f1c431f5..ff0a5f202eaf 100644 --- a/packages/astro/test/fixtures/astro-pagination/src/pages/posts/named-root-page/[page].astro +++ b/packages/astro/test/fixtures/astro-pagination/src/pages/posts/named-root-page/[page].astro @@ -1,6 +1,6 @@ --- export async function getStaticPaths({paginate}) { - const data = await Astro.glob('../../post/*.md'); + const data = Object.values(import.meta.glob('../../post/*.md', { eager: true })); return paginate(data, {pageSize: 1}); } const canonicalURL = new URL(Astro.url.pathname, Astro.site); diff --git a/packages/astro/test/fixtures/astro-pagination/src/pages/posts/optional-root-page/[...page].astro b/packages/astro/test/fixtures/astro-pagination/src/pages/posts/optional-root-page/[...page].astro index c908f1c431f5..ff0a5f202eaf 100644 --- a/packages/astro/test/fixtures/astro-pagination/src/pages/posts/optional-root-page/[...page].astro +++ b/packages/astro/test/fixtures/astro-pagination/src/pages/posts/optional-root-page/[...page].astro @@ -1,6 +1,6 @@ --- export async function getStaticPaths({paginate}) { - const data = await Astro.glob('../../post/*.md'); + const data = Object.values(import.meta.glob('../../post/*.md', { eager: true })); return paginate(data, {pageSize: 1}); } const canonicalURL = new URL(Astro.url.pathname, Astro.site); diff --git a/packages/astro/test/fixtures/astro-scripts/src/pages/glob.astro b/packages/astro/test/fixtures/astro-scripts/src/pages/glob.astro index cb5124da6085..c18131b85875 100644 --- a/packages/astro/test/fixtures/astro-scripts/src/pages/glob.astro +++ b/packages/astro/test/fixtures/astro-scripts/src/pages/glob.astro @@ -1,6 +1,6 @@ --- import '../styles/global.css'; -const components = await Astro.glob('/src/components/Glob/*'); +const components = Object.values(import.meta.glob('/src/components/Glob/*', { eager: true })); const MyComponent = components[0].default; --- diff --git a/packages/astro/test/fixtures/astro-sitemap-rss/src/pages/episodes/[...page].astro b/packages/astro/test/fixtures/astro-sitemap-rss/src/pages/episodes/[...page].astro index 3732c4ba31b0..93366b73e378 100644 --- a/packages/astro/test/fixtures/astro-sitemap-rss/src/pages/episodes/[...page].astro +++ b/packages/astro/test/fixtures/astro-sitemap-rss/src/pages/episodes/[...page].astro @@ -1,6 +1,6 @@ --- export async function getStaticPaths({paginate, rss}) { - const episodes = (await Astro.glob('../episode/*.md')).sort((a, b) => new Date(b.frontmatter.pubDate) - new Date(a.frontmatter.pubDate)); + const episodes = Object.values(import.meta.glob('../episode/*.md', { eager: true })).sort((a, b) => new Date(b.frontmatter.pubDate) - new Date(a.frontmatter.pubDate)); rss({ title: 'MF Doomcast', description: 'The podcast about the things you find on a picnic, or at a picnic table', diff --git a/packages/astro/test/fixtures/debug-component/src/pages/posts/[slug].astro b/packages/astro/test/fixtures/debug-component/src/pages/posts/[slug].astro index ed85be913b6b..fc0a6f351ba1 100644 --- a/packages/astro/test/fixtures/debug-component/src/pages/posts/[slug].astro +++ b/packages/astro/test/fixtures/debug-component/src/pages/posts/[slug].astro @@ -3,7 +3,7 @@ import Debug from 'astro/debug'; // all the content that should be generated export async function getStaticPaths() { - const data = await Astro.glob('../../data/posts/*.md') + const data = Object.values(import.meta.glob('../../data/posts/*.md', { eager: true })); const allArticles = data.map((article) => { return { diff --git a/packages/astro/test/fixtures/glob-pages-css/src/components/Footer.astro b/packages/astro/test/fixtures/glob-pages-css/src/components/Footer.astro index 32d8844f24be..2cbc2fbd2f42 100644 --- a/packages/astro/test/fixtures/glob-pages-css/src/components/Footer.astro +++ b/packages/astro/test/fixtures/glob-pages-css/src/components/Footer.astro @@ -1,6 +1,6 @@ --- // The side-effect of this happening is needed for the test. -await Astro.glob("../pages/**/*.astro"); +Object.values(import.meta.glob("../pages/**/*.astro", { eager: true })); --- + + + +``` diff --git a/packages/astro/package.json b/packages/astro/package.json index fa499f8b496a..c2949bfee431 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -106,7 +106,7 @@ "test:integration": "astro-scripts test \"test/*.test.js\"" }, "dependencies": { - "@astrojs/compiler": "0.0.0-next-result-create-astro-20250926081949", + "@astrojs/compiler": "0.0.0-script-order-20251002140654", "@astrojs/internal-helpers": "workspace:*", "@astrojs/markdown-remark": "workspace:*", "@astrojs/telemetry": "workspace:*", diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index e3ade372851d..336eb210e182 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -62,7 +62,6 @@ export async function compile({ cssPartialCompileResults, cssTransformErrors, }), - experimentalScriptOrder: astroConfig.experimental.preserveScriptOrder ?? false, async resolvePath(specifier) { return resolvePath(specifier, filename); }, diff --git a/packages/astro/src/core/config/schemas/base.ts b/packages/astro/src/core/config/schemas/base.ts index ddba2f3f2ca6..b708001f55c7 100644 --- a/packages/astro/src/core/config/schemas/base.ts +++ b/packages/astro/src/core/config/schemas/base.ts @@ -97,7 +97,6 @@ export const ASTRO_CONFIG_DEFAULTS = { clientPrerender: false, contentIntellisense: false, headingIdCompat: false, - preserveScriptOrder: false, liveContentCollections: false, csp: false, staticImportMetaEnv: false, @@ -468,10 +467,6 @@ export const AstroConfigSchema = z.object({ .boolean() .optional() .default(ASTRO_CONFIG_DEFAULTS.experimental.headingIdCompat), - preserveScriptOrder: z - .boolean() - .optional() - .default(ASTRO_CONFIG_DEFAULTS.experimental.preserveScriptOrder), fonts: z.array(z.union([localFontFamilySchema, remoteFontFamilySchema])).optional(), liveContentCollections: z .boolean() diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts index bd82172a645a..8c63821a2a43 100644 --- a/packages/astro/src/types/public/config.ts +++ b/packages/astro/src/types/public/config.ts @@ -2358,48 +2358,6 @@ export interface ViteUserConfig extends OriginalViteUserConfig { directives?: CspDirective[]; }; - /** - * @name experimental.preserveScriptOrder - * @type {boolean} - * @default `false` - * @version 5.5 - * @description - * - * When enabled, ` - -``` +Updates `