From a51c533224687120f0b279f740bdd80b62bc3769 Mon Sep 17 00:00:00 2001 From: Waqas Ahmed Date: Thu, 27 Aug 2026 20:46:47 +0500 Subject: [PATCH 01/19] fix(check): include .astro files in checked project references (#17715) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: include .astro files in checked project references (#17478) * fix: implement .astro project-reference fix in astro's own source (#17478) The previous approach patched @volar/kit via pnpm's patchedDependencies, which only rewrites node_modules inside this monorepo and never reaches published npm tarballs. astro check/@astrojs/language-server both build with tsc -b (no bundling) and depend on @volar/kit as a runtime dependency, so the patch never shipped to real npm consumers - only the VS Code extension (which bundles with esbuild) benefited. Reimplement the fix directly in AstroCheck's checker setup instead. createTypeScriptChecker's setup callback runs once per project (root and each reference) and exposes that project's configFileName and mutable languageServiceHost, which is enough to re-parse each referenced tsconfig with the language plugins' extraFileExtensions and merge the result into that project's file list - without touching @volar/kit at all. getRootFileNames() reads from a separate internal host, so it gets its own merge on top. Verified end to end: removed the @volar/kit patch, rebuilt @astrojs/language-server against the real unpatched dependency, and confirmed the project-references test suite (including a fileResult assertion swapped for a direct getRootFileNames() check per review) is fully green. Also rebuilt astro, @astrojs/markdown-satteri, and the svelte/vue integrations from a clean state to confirm the two previously-failing assertions were an artifact of an unbuilt local environment, not a real pre-existing failure - full suite is 10/10 with everything built. * fix(check): rename shadowed variable to fix lint error * refactor(check): simplify project-reference .astro file handling per review Per @matthewp: the previous version's dynamic caching/invalidation was unreliable anyway — it only invalidated by comparing TypeScript's own file list, which never includes .astro files, so adding/removing only an .astro file never triggered a re-parse. Replaces it with a static approach that matches what the root project already does: each referenced project's tsconfig is parsed once with extraFileExtensions during checker setup, and the result is merged once into that project's languageServiceHost.getScriptFileNames() and into linter.getRootFileNames() — no cache, no resolver callbacks, no file-list change tracking. Also removed the editor-language-server claim from the PR description and changeset — this only fixes AstroCheck; the editor's language server uses the separate nodeServer.ts/createTypeScriptProject path. Verified: pnpm --filter @astrojs/language-server build (tsc -b) is clean, and biome check on check.ts is clean. Could not run the check.test.ts suite itself locally — it imports packages/astro/test/test-utils.ts, which needs a full astro package build, and that build fails here on an unrelated missing workspace dependency (@astrojs/markdown-satteri) not present in this checkout. Happy to have CI or a maintainer confirm the existing check.test.ts assertions (file count, error count, and the new getRootFileNames() .astro-inclusion check) still pass — none of them test dynamic add/remove behavior, so nothing in that suite should be sensitive to removing the caching layer. * chore: reword changeset for end-user impact, not implementation --------- Co-authored-by: Matthew Phillips Co-authored-by: wakqasahmed --- .../fix-check-astro-project-references.md | 5 ++ .../language-server/src/check.ts | 64 ++++++++++++++++++- .../language-server/test/check/check.test.ts | 12 +++- .../fixture-references/src/hasError.astro | 3 + 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 .changeset/fix-check-astro-project-references.md create mode 100644 packages/language-tools/language-server/test/check/fixture-references/src/hasError.astro diff --git a/.changeset/fix-check-astro-project-references.md b/.changeset/fix-check-astro-project-references.md new file mode 100644 index 000000000000..e6371e9aa164 --- /dev/null +++ b/.changeset/fix-check-astro-project-references.md @@ -0,0 +1,5 @@ +--- +'@astrojs/language-server': patch +--- + +Fixes `astro check` silently skipping `.astro` files that are only reachable through a TypeScript project reference (a tsconfig referenced via `references` in another tsconfig). These files are now checked and reported like any other `.astro` file. diff --git a/packages/language-tools/language-server/src/check.ts b/packages/language-tools/language-server/src/check.ts index ab265b89b367..585184301782 100644 --- a/packages/language-tools/language-server/src/check.ts +++ b/packages/language-tools/language-server/src/check.ts @@ -1,6 +1,6 @@ import { existsSync } from 'node:fs'; import { homedir } from 'node:os'; -import { resolve } from 'node:path'; +import { dirname, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import * as kit from '@volar/kit'; import { Diagnostic, DiagnosticSeverity } from '@volar/language-server'; @@ -152,13 +152,17 @@ export class AstroCheck { if (tsconfigPath) { const includeProjectReference = true; + const extraFileExtensions = languagePlugins.flatMap( + (plugin) => plugin.typescript?.extraFileExtensions ?? [], + ); + const allExtraFileNames: string[] = []; this.linter = kit.createTypeScriptChecker( languagePlugins, services, tsconfigPath, includeProjectReference, ({ project }) => { - const { languageServiceHost } = project.typescript!; + const { configFileName, languageServiceHost } = project.typescript!; const astroInstall = getAstroInstall([this.workspacePath]); addAstroTypes( @@ -166,8 +170,33 @@ export class AstroCheck { this.ts, languageServiceHost, ); + + const extraFileNames = this.getExtraFileNamesFromReferences( + configFileName, + extraFileExtensions, + ); + if (extraFileNames.length > 0) { + allExtraFileNames.push(...extraFileNames); + + const originalGetScriptFileNames = + languageServiceHost.getScriptFileNames.bind(languageServiceHost); + languageServiceHost.getScriptFileNames = () => [ + ...new Set([...originalGetScriptFileNames(), ...extraFileNames]), + ]; + } }, ); + + // `getRootFileNames()` (used by `lint()` to enumerate the whole project when no + // explicit file list is given) reads project references' file lists from an + // internal host that the per-project `languageServiceHost` patch above can't reach, + // so it needs its own, separate merge here. + if (allExtraFileNames.length > 0) { + const originalGetRootFileNames = this.linter.getRootFileNames.bind(this.linter); + this.linter.getRootFileNames = () => [ + ...new Set([...originalGetRootFileNames(), ...allExtraFileNames]), + ]; + } } else { this.linter = kit.createTypeScriptInferredChecker( languagePlugins, @@ -215,6 +244,37 @@ export class AstroCheck { } } + /** + * `@volar/kit`'s `createTypeScriptChecker` re-parses the root tsconfig with the language + * plugins' `extraFileExtensions` (so `.astro` files are included), but for project + * references it reuses TypeScript's own resolved `commandLine`, which never includes + * extra extensions. That silently drops `.astro`/`.vue`/`.svelte` files that are only + * reachable through a referenced tsconfig. `setup` is invoked once per project (the root + * and each reference), so this re-parses that project's own tsconfig the same way the + * root one already is, and returns the extra file names found, for the caller to merge + * into the language service host's root file list. + */ + private getExtraFileNamesFromReferences( + configFileName: string | undefined, + extraFileExtensions: import('typescript').FileExtensionInfo[], + ): string[] { + if (!configFileName || extraFileExtensions.length === 0) { + return []; + } + + const commandLine = this.ts.parseJsonSourceFileConfigFileContent( + this.ts.readJsonConfigFile(configFileName, this.ts.sys.readFile), + this.ts.sys, + dirname(configFileName), + undefined, + configFileName, + undefined, + extraFileExtensions, + ); + + return commandLine.fileNames; + } + private getTsconfig() { if (this.tsconfigPath) { const tsconfig = resolve(this.workspacePath, this.tsconfigPath.replace(/^~/, homedir())); diff --git a/packages/language-tools/language-server/test/check/check.test.ts b/packages/language-tools/language-server/test/check/check.test.ts index 1c4b432a507e..1de8d55bdebf 100644 --- a/packages/language-tools/language-server/test/check/check.test.ts +++ b/packages/language-tools/language-server/test/check/check.test.ts @@ -74,11 +74,19 @@ describe('AstroCheck with project references', async () => { }); it('Finds files from referenced projects', async () => { - assert.ok(result.fileChecked > 0, 'Expected at least one file to be checked'); + assert.strictEqual(result.fileChecked, 2); }); it('Reports errors from referenced projects', async () => { - assert.strictEqual(result.errors, 1); + assert.strictEqual(result.errors, 2); + }); + + it('Includes .astro files from referenced projects', async () => { + const rootFileNames = checker.linter.getRootFileNames(); + assert.ok( + rootFileNames.some((fileName) => fileName.endsWith('hasError.astro')), + 'Expected the referenced project file list to include the .astro file', + ); }); }); diff --git a/packages/language-tools/language-server/test/check/fixture-references/src/hasError.astro b/packages/language-tools/language-server/test/check/fixture-references/src/hasError.astro new file mode 100644 index 000000000000..ee2c60b5401a --- /dev/null +++ b/packages/language-tools/language-server/test/check/fixture-references/src/hasError.astro @@ -0,0 +1,3 @@ +--- +console.log(doesntExist); +--- From 52d3f56999ecdf92510b2c16ed2a3a4785b9c73a Mon Sep 17 00:00:00 2001 From: "astro-factory[bot]" <316791938+astro-factory[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 13:28:21 -0400 Subject: [PATCH 02/19] fix(@astrojs/sitemap): preserve root path `/` in sitemap URLs when trailingSlash is 'never' (#17851) Co-authored-by: factory[bot] --- .changeset/sour-poems-wave.md | 5 ++ packages/integrations/sitemap/package.json | 1 - packages/integrations/sitemap/src/index.ts | 56 +++++++++---------- .../sitemap/src/write-sitemap-chunk.ts | 46 +++++---------- .../integrations/sitemap/src/write-sitemap.ts | 46 +++++---------- .../sitemap/test/trailing-slash.test.ts | 6 +- pnpm-lock.yaml | 17 +++--- 7 files changed, 72 insertions(+), 105 deletions(-) create mode 100644 .changeset/sour-poems-wave.md diff --git a/.changeset/sour-poems-wave.md b/.changeset/sour-poems-wave.md new file mode 100644 index 000000000000..3915e545446d --- /dev/null +++ b/.changeset/sour-poems-wave.md @@ -0,0 +1,5 @@ +--- +'@astrojs/sitemap': patch +--- + +Fixes the sitemap outputting a URL with an empty path for the homepage (e.g. `https://example.com` instead of `https://example.com/`) when `trailingSlash` is set to `"never"` or `build.format` is set to `"file"` diff --git a/packages/integrations/sitemap/package.json b/packages/integrations/sitemap/package.json index 30a57f68e209..68dde8a8c4af 100644 --- a/packages/integrations/sitemap/package.json +++ b/packages/integrations/sitemap/package.json @@ -33,7 +33,6 @@ }, "dependencies": { "sitemap": "^9.0.0", - "stream-replace-string": "^2.0.0", "zod": "^4.3.6" }, "devDependencies": { diff --git a/packages/integrations/sitemap/src/index.ts b/packages/integrations/sitemap/src/index.ts index 22618eb5d0c3..ec1f39626952 100644 --- a/packages/integrations/sitemap/src/index.ts +++ b/packages/integrations/sitemap/src/index.ts @@ -231,22 +231,19 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => { (urlDataItem) => !groupedUrlCollection.includes(urlDataItem.url), ); // Process each chunk here - await writeSitemapChunk( - { - filenameBase, - hostname: finalSiteUrl.href, - sitemapHostname: finalSiteUrl.href, - sourceData: chunksItem, - destinationDir: destDir, - publicBasePath: config.base, - customSitemaps, - limit: entryLimit, - xslURL, - lastmod, - namespaces: opts.namespaces, - }, - config, - ); + await writeSitemapChunk({ + filenameBase, + hostname: finalSiteUrl.href, + sitemapHostname: finalSiteUrl.href, + sourceData: chunksItem, + destinationDir: destDir, + publicBasePath: config.base, + customSitemaps, + limit: entryLimit, + xslURL, + lastmod, + namespaces: opts.namespaces, + }); logger.info(`\`${outFile}\` created at \`${path.relative(process.cwd(), destDir)}\``); return; } catch (err) { @@ -254,21 +251,18 @@ const createPlugin = (options?: SitemapOptions): AstroIntegration => { return; } } - await writeSitemap( - { - filenameBase: filenameBase, - hostname: finalSiteUrl.href, - destinationDir: destDir, - publicBasePath: config.base, - sourceData: urlData, - limit: entryLimit, - customSitemaps, - xslURL: xslURL, - lastmod, - namespaces: opts.namespaces, - }, - config, - ); + await writeSitemap({ + filenameBase: filenameBase, + hostname: finalSiteUrl.href, + destinationDir: destDir, + publicBasePath: config.base, + sourceData: urlData, + limit: entryLimit, + customSitemaps, + xslURL: xslURL, + lastmod, + namespaces: opts.namespaces, + }); logger.info(`\`${outFile}\` created at \`${path.relative(process.cwd(), destDir)}\``); } catch (err) { if (err instanceof ZodError) { diff --git a/packages/integrations/sitemap/src/write-sitemap-chunk.ts b/packages/integrations/sitemap/src/write-sitemap-chunk.ts index 4baafdf379d0..f26eb5be608b 100644 --- a/packages/integrations/sitemap/src/write-sitemap-chunk.ts +++ b/packages/integrations/sitemap/src/write-sitemap-chunk.ts @@ -1,11 +1,9 @@ -import { createWriteStream, type WriteStream } from 'node:fs'; +import { createWriteStream } from 'node:fs'; import { mkdir } from 'node:fs/promises'; import { normalize, resolve } from 'node:path'; import { pipeline, Readable } from 'node:stream'; import { promisify } from 'node:util'; -import type { AstroConfig } from 'astro'; import { SitemapAndIndexStream, SitemapIndexStream, SitemapStream } from 'sitemap'; -import replace from 'stream-replace-string'; import type { SitemapItem } from './index.js'; import { getLatestLastmod } from './utils/lastmod.js'; @@ -29,22 +27,19 @@ type WriteSitemapChunkConfig = { }; // adapted from sitemap.js/sitemap-simple -export async function writeSitemapChunk( - { - filenameBase, - hostname, - sitemapHostname = hostname, - sourceData, - destinationDir, - limit = 50000, - customSitemaps = [], - publicBasePath = './', - xslURL: xslUrl, - lastmod, - namespaces = { news: true, xhtml: true, image: true, video: true }, - }: WriteSitemapChunkConfig, - astroConfig: AstroConfig, -) { +export async function writeSitemapChunk({ + filenameBase, + hostname, + sitemapHostname = hostname, + sourceData, + destinationDir, + limit = 50000, + customSitemaps = [], + publicBasePath = './', + xslURL: xslUrl, + lastmod, + namespaces = { news: true, xhtml: true, image: true, video: true }, +}: WriteSitemapChunkConfig) { await mkdir(destinationDir, { recursive: true }); // Normalize publicBasePath @@ -78,18 +73,7 @@ export async function writeSitemapChunk( const writePath = resolve(destinationDir, path); const publicPath = normalize(normalizedPublicBasePath + path); - let stream: WriteStream; - if (astroConfig.trailingSlash === 'never' || astroConfig.build.format === 'file') { - // workaround for trailing slash issue in sitemap.js - const host = hostname.endsWith('/') ? hostname.slice(0, -1) : hostname; - const searchStr = `${host}/`; - const replaceStr = `${host}`; - stream = sitemapStream - .pipe(replace(searchStr, replaceStr)) - .pipe(createWriteStream(writePath)); - } else { - stream = sitemapStream.pipe(createWriteStream(writePath)); - } + const stream = sitemapStream.pipe(createWriteStream(writePath)); const url = new URL(publicPath, sitemapHostname).toString(); // Stamp this index entry with the freshest lastmod among the diff --git a/packages/integrations/sitemap/src/write-sitemap.ts b/packages/integrations/sitemap/src/write-sitemap.ts index cd190e913365..789a50dee709 100644 --- a/packages/integrations/sitemap/src/write-sitemap.ts +++ b/packages/integrations/sitemap/src/write-sitemap.ts @@ -1,11 +1,9 @@ -import { createWriteStream, type WriteStream } from 'node:fs'; +import { createWriteStream } from 'node:fs'; import { mkdir } from 'node:fs/promises'; import { normalize, resolve } from 'node:path'; import { pipeline, Readable } from 'node:stream'; import { promisify } from 'node:util'; -import type { AstroConfig } from 'astro'; import { SitemapAndIndexStream, SitemapIndexStream, SitemapStream } from 'sitemap'; -import replace from 'stream-replace-string'; import type { SitemapItem } from './index.js'; import { getLatestLastmod } from './utils/lastmod.js'; @@ -29,22 +27,19 @@ type WriteSitemapConfig = { }; // adapted from sitemap.js/sitemap-simple -export async function writeSitemap( - { - filenameBase, - hostname, - sitemapHostname = hostname, - sourceData, - destinationDir, - limit = 50000, - customSitemaps = [], - publicBasePath = './', - xslURL: xslUrl, - lastmod, - namespaces = { news: true, xhtml: true, image: true, video: true }, - }: WriteSitemapConfig, - astroConfig: AstroConfig, -) { +export async function writeSitemap({ + filenameBase, + hostname, + sitemapHostname = hostname, + sourceData, + destinationDir, + limit = 50000, + customSitemaps = [], + publicBasePath = './', + xslURL: xslUrl, + lastmod, + namespaces = { news: true, xhtml: true, image: true, video: true }, +}: WriteSitemapConfig) { await mkdir(destinationDir, { recursive: true }); const sitemapAndIndexStream = new SitemapAndIndexStream({ @@ -69,18 +64,7 @@ export async function writeSitemap( } const publicPath = normalize(publicBasePath + path); - let stream: WriteStream; - if (astroConfig.trailingSlash === 'never' || astroConfig.build.format === 'file') { - // workaround for trailing slash issue in sitemap.js: https://github.com/ekalinin/sitemap.js/issues/403 - const host = hostname.endsWith('/') ? hostname.slice(0, -1) : hostname; - const searchStr = `${host}/`; - const replaceStr = `${host}`; - stream = sitemapStream - .pipe(replace(searchStr, replaceStr)) - .pipe(createWriteStream(writePath)); - } else { - stream = sitemapStream.pipe(createWriteStream(writePath)); - } + const stream = sitemapStream.pipe(createWriteStream(writePath)); const url = new URL(publicPath, sitemapHostname).toString(); // Stamp this index entry with the freshest lastmod among the URLs diff --git a/packages/integrations/sitemap/test/trailing-slash.test.ts b/packages/integrations/sitemap/test/trailing-slash.test.ts index 8ccbbe0d251c..92bedc2cac66 100644 --- a/packages/integrations/sitemap/test/trailing-slash.test.ts +++ b/packages/integrations/sitemap/test/trailing-slash.test.ts @@ -44,7 +44,7 @@ describe('Trailing slash', () => { const data = await readXML(fixture.readFile('/sitemap-0.xml')); const urls = data.urlset.url; - assert.equal(urls[0].loc[0], 'http://example.com'); + assert.equal(urls[0].loc[0], 'http://example.com/'); assert.equal(urls[1].loc[0], 'http://example.com/one'); assert.equal(urls[2].loc[0], 'http://example.com/two'); }); @@ -64,7 +64,7 @@ describe('Trailing slash', () => { const data = await readXML(fixture.readFile('/sitemap-0.xml')); const urls = data.urlset.url; - assert.equal(urls[0].loc[0], 'http://example.com'); + assert.equal(urls[0].loc[0], 'http://example.com/'); assert.equal(urls[1].loc[0], 'http://example.com/one'); assert.equal(urls[2].loc[0], 'http://example.com/two'); }); @@ -81,7 +81,7 @@ describe('Trailing slash', () => { it('URLs do not end with trailing slash', async () => { const data = await readXML(fixture.readFile('/sitemap-0.xml')); const urls = data.urlset.url; - assert.equal(urls[0].loc[0], 'http://example.com/base'); + assert.equal(urls[0].loc[0], 'http://example.com/base/'); assert.equal(urls[1].loc[0], 'http://example.com/base/one'); assert.equal(urls[2].loc[0], 'http://example.com/base/two'); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 375a515d1071..171524c6e861 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6129,9 +6129,6 @@ importers: sitemap: specifier: ^9.0.0 version: 9.0.0 - stream-replace-string: - specifier: ^2.0.0 - version: 2.0.0 zod: specifier: ^4.3.6 version: 4.3.6 @@ -7181,6 +7178,15 @@ importers: specifier: ^4.22.0 version: 4.22.3 + triage/gh-17848: + dependencies: + '@astrojs/sitemap': + specifier: workspace:* + version: link:../../packages/integrations/sitemap + astro: + specifier: workspace:* + version: link:../../packages/astro + packages: '@anthropic-ai/sdk@0.91.1': @@ -15473,9 +15479,6 @@ packages: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} - stream-replace-string@2.0.0: - resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} - streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} @@ -26033,8 +26036,6 @@ snapshots: stdin-discarder@0.2.2: {} - stream-replace-string@2.0.0: {} - streamx@2.23.0: dependencies: events-universal: 1.0.1 From 157c500c38faa7ecf1251adbaeefcd109470d75c Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 27 Aug 2026 13:30:04 -0400 Subject: [PATCH 03/19] fix(content): invalidate the data store directly on write (#17755) The dev server relied on the file watcher observing the atomic rename that commits a data store write to invalidate the content virtual modules. On Windows the watcher can miss that event, leaving dev serving stale content until a restart (#17335). The store now notifies listeners after real disk writes and the dev server invalidates directly, keeping the watcher path as a fallback for other processes with echo suppression to avoid double reloads. --- .changeset/light-pandas-repeat.md | 5 + .../astro/src/content/data-store-writer.ts | 40 +++++-- packages/astro/src/content/index.ts | 5 +- .../astro/src/content/mutable-data-store.ts | 35 +++++- .../vite-plugin-content-virtual-mod.ts | 62 ++++++++++- packages/astro/src/core/dev/dev.ts | 7 +- .../content-layer/content-virtual-mod.test.ts | 77 ++++++++++++- .../store-write-notifications.test.ts | 101 ++++++++++++++++++ 8 files changed, 315 insertions(+), 17 deletions(-) create mode 100644 .changeset/light-pandas-repeat.md create mode 100644 packages/astro/test/units/content-layer/store-write-notifications.test.ts diff --git a/.changeset/light-pandas-repeat.md b/.changeset/light-pandas-repeat.md new file mode 100644 index 000000000000..12ff8d673550 --- /dev/null +++ b/.changeset/light-pandas-repeat.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a bug where editing a content collection entry during `astro dev` on Windows kept serving stale content until the dev server was restarted. The data store now notifies the dev server directly after each write instead of relying only on the file watcher, which can miss the atomic rename that commits the write on some platforms. diff --git a/packages/astro/src/content/data-store-writer.ts b/packages/astro/src/content/data-store-writer.ts index 66ef92d8a07a..9c659a2d8844 100644 --- a/packages/astro/src/content/data-store-writer.ts +++ b/packages/astro/src/content/data-store-writer.ts @@ -14,8 +14,17 @@ export type DataStoreManifest = Record; * (build/dev) and are never imported at runtime. */ export interface DataStoreWriter { - /** Serialize and persist the given collections. */ - write(collections: Map>): Promise; + /** + * Serialize and persist the given collections. + * Resolves to `true` if the data on disk changed, or `false` if the write + * was skipped because the persisted data was already identical. + */ + write(collections: Map>): Promise; + /** + * The file whose write commits a store update: the store file itself, or + * the manifest for chunked stores. + */ + readonly target: PathLike; } /** @@ -102,17 +111,20 @@ export function chunkString(str: string, maxBytes: number): string[] { * partial reads. If the file already contains identical data, the write is * skipped. Callers are responsible for serializing concurrent writes to the * same file. + * + * Returns `true` if the file was written, or `false` if the write was skipped. */ -export async function writeFileAtomic(file: PathLike, data: string): Promise { +export async function writeFileAtomic(file: PathLike, data: string): Promise { const tempFile = file instanceof URL ? new URL(`${file.href}.tmp`) : `${file}.tmp`; const oldData = await fs.readFile(file, 'utf-8').catch(() => ''); if (oldData === data) { // If the data hasn't changed, we can skip the write. - return; + return false; } // Write to a temporary file first and then move it to prevent partial reads. await fs.writeFile(tempFile, data); await fs.rename(tempFile, file); + return true; } /** @@ -125,8 +137,12 @@ export class FileWriter implements DataStoreWriter { this.#file = file; } - async write(collections: Map>): Promise { - await writeFileAtomic(this.#file, serializeDataStore(collections)); + get target(): PathLike { + return this.#file; + } + + async write(collections: Map>): Promise { + return await writeFileAtomic(this.#file, serializeDataStore(collections)); } } @@ -155,7 +171,11 @@ export class ChunkedWriter implements DataStoreWriter { this.#chunkSize = chunkSize; } - async write(collections: Map>): Promise { + get target(): PathLike { + return this.#manifestFile; + } + + async write(collections: Map>): Promise { if (!this.#hasher) { this.#hasher = await xxhash(); } @@ -168,12 +188,14 @@ export class ChunkedWriter implements DataStoreWriter { } // The manifest is the commit point: every part it references already - // exists on disk, so a reader never sees a dangling reference. - await writeFileAtomic(this.#manifestFile, JSON.stringify(manifest)); + // exists on disk, so a reader never sees a dangling reference. Parts are + // content-addressed, so an unchanged manifest means unchanged data. + const didWrite = await writeFileAtomic(this.#manifestFile, JSON.stringify(manifest)); this.#writtenFiles.add(DATA_STORE_MANIFEST_FILE); // Prune files left behind by previous snapshots. emptyDir(this.#dir, this.#writtenFiles); + return didWrite; } async #writeCollection(entries: Iterable<[string, unknown]>) { diff --git a/packages/astro/src/content/index.ts b/packages/astro/src/content/index.ts index a00dee10796a..e91f87691bd1 100644 --- a/packages/astro/src/content/index.ts +++ b/packages/astro/src/content/index.ts @@ -3,4 +3,7 @@ export { createContentTypesGenerator } from './types-generator.js'; export { getContentPaths } from './utils.js'; export { astroContentAssetPropagationPlugin } from './vite-plugin-content-assets.js'; export { astroContentImportPlugin } from './vite-plugin-content-imports.js'; -export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js'; +export { + astroContentVirtualModPlugin, + attachDataStoreInvalidation, +} from './vite-plugin-content-virtual-mod.js'; diff --git a/packages/astro/src/content/mutable-data-store.ts b/packages/astro/src/content/mutable-data-store.ts index f843ed122958..6219baca6601 100644 --- a/packages/astro/src/content/mutable-data-store.ts +++ b/packages/astro/src/content/mutable-data-store.ts @@ -46,6 +46,35 @@ export class MutableDataStore extends ImmutableDataStore { #writeInProgress = false; #writeQueued = false; + #fileWrittenListeners = new Set<(path: string) => void>(); + + /** + * Registers a listener called with the file path whenever this store writes a + * file to disk (the data store itself, or the asset/module import files). + * Writes that are skipped because the data on disk is already identical do + * not notify. The dev server uses this to invalidate the content virtual + * modules deterministically, instead of relying on the file watcher to + * observe the write — on some platforms (notably Windows) the watcher can + * miss the atomic rename that commits it. + * Returns a function that removes the listener. + */ + onFileWritten(listener: (path: string) => void): () => void { + this.#fileWrittenListeners.add(listener); + return () => { + this.#fileWrittenListeners.delete(listener); + }; + } + + #notifyFileWritten(path: PathLike) { + if (this.#fileWrittenListeners.size === 0) { + return; + } + const normalized = path instanceof URL ? fileURLToPath(path) : path.toString(); + for (const listener of this.#fileWrittenListeners) { + listener(normalized); + } + } + set(collectionName: string, key: string, value: unknown) { const collection = this._collections.get(collectionName) ?? new Map(); collection.set(String(key), value); @@ -344,6 +373,7 @@ export default new Map([\n${lines.join(',\n')}]); // Write it to a temporary file first and then move it to prevent partial reads. await fs.writeFile(tempFile, data); await fs.rename(tempFile, filePath); + this.#notifyFileWritten(filePath); } finally { // We're done writing. Unflag the file and check if there are any pending writes for this file. this.#writing.delete(fileKey); @@ -509,7 +539,10 @@ export default new Map([\n${lines.join(',\n')}]); // Mark as clean before writing to disk so that it catches any changes that happen during the write this.#dirty = false; this.#writeInProgress = true; - await this.#writer.write(this._collections); + const didWrite = await this.#writer.write(this._collections); + if (didWrite) { + this.#notifyFileWritten(this.#writer.target); + } } catch (err) { throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err }); } finally { 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 c2aba6d3478a..988c40e8c4d9 100644 --- a/packages/astro/src/content/vite-plugin-content-virtual-mod.ts +++ b/packages/astro/src/content/vite-plugin-content-virtual-mod.ts @@ -28,6 +28,7 @@ import { RESOLVED_VIRTUAL_MODULE_ID, VIRTUAL_MODULE_ID, } from './consts.js'; +import type { MutableDataStore } from './mutable-data-store.js'; import { getDataStoreChunkSize, getDataStoreDir, getDataStoreFile } from './paths.js'; import { getContentPaths, isDeferredModule } from './utils.js'; @@ -92,6 +93,57 @@ function invalidateDataStore(viteServer: ViteDevServer, { notifyClient = true } } } +// Timestamps of direct (write-driven) invalidations, keyed by file path. The +// file watcher usually observes the same write shortly afterwards; watcher +// events inside this window are echoes of an invalidation that has already +// happened and are skipped so clients don't get two full reloads for one change. +const directInvalidations = new Map(); +const DIRECT_INVALIDATION_ECHO_MS = 1000; + +function markDirectInvalidation(path: string) { + directInvalidations.set(path, Date.now()); +} + +function isDirectInvalidationEcho(path: string) { + const time = directInvalidations.get(path); + return time !== undefined && Date.now() - time < DIRECT_INVALIDATION_ECHO_MS; +} + +/** The file whose write commits a data store update during dev. */ +function getDevDataStoreFile(settings: AstroSettings): URL { + if (getDataStoreChunkSize(settings) !== undefined) { + return new URL(DATA_STORE_MANIFEST_FILE, getDataStoreDir(settings, true)); + } + return getDataStoreFile(settings, true); +} + +/** + * Invalidates the content virtual modules directly whenever the given store + * writes to disk. The watcher listeners in `configureServer` cover writes from + * other processes, but the watcher can miss the atomic rename that commits a + * write on some platforms (notably Windows, see #17335), leaving dev serving + * stale content until a restart. Subscribing to the store's own write + * notifications makes invalidation of this process's writes deterministic. + */ +export function attachDataStoreInvalidation( + store: MutableDataStore, + server: ViteDevServer, + settings: AstroSettings, +) { + const dataStorePath = fileURLToPath(getDevDataStoreFile(settings)); + const assetImportsPath = fileURLToPath(new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir)); + store.onFileWritten((path) => { + if (path === dataStorePath) { + markDirectInvalidation(dataStorePath); + invalidateDataStore(server); + invalidateAssetImports(server, assetImportsPath); + } else if (path === assetImportsPath) { + markDirectInvalidation(assetImportsPath); + invalidateAssetImports(server, assetImportsPath); + } + }); +} + export function astroContentVirtualModPlugin({ settings, fs, @@ -335,7 +387,7 @@ export function astroContentVirtualModPlugin({ const assetImportsPath = fileURLToPath(new URL(ASSET_IMPORTS_FILE, settings.dotAstroDir)); server.watcher.on('add', (addedPath) => { - if (addedPath === dataStorePath) { + if (addedPath === dataStorePath && !isDirectInvalidationEcho(dataStorePath)) { invalidateDataStore(server); invalidateAssetImports(server, assetImportsPath); } @@ -343,9 +395,15 @@ export function astroContentVirtualModPlugin({ server.watcher.on('change', (changedPath) => { if (changedPath === dataStorePath) { + if (isDirectInvalidationEcho(dataStorePath)) { + return; + } invalidateDataStore(server); invalidateAssetImports(server, assetImportsPath); - } else if (changedPath === assetImportsPath) { + } else if ( + changedPath === assetImportsPath && + !isDirectInvalidationEcho(assetImportsPath) + ) { invalidateAssetImports(server, assetImportsPath); } }); diff --git a/packages/astro/src/core/dev/dev.ts b/packages/astro/src/core/dev/dev.ts index c7da44a9705e..7398c19cc8bd 100644 --- a/packages/astro/src/core/dev/dev.ts +++ b/packages/astro/src/core/dev/dev.ts @@ -7,7 +7,7 @@ import { gt, major, minor, patch } from 'semver'; import type * as vite from 'vite'; import { getDataStoreChunkSize, getDataStoreDir, getDataStoreFile } from '../../content/paths.js'; import { globalContentLayer } from '../../content/instance.js'; -import { attachContentServerListeners } from '../../content/index.js'; +import { attachContentServerListeners, attachDataStoreInvalidation } from '../../content/index.js'; import { MutableDataStore } from '../../content/mutable-data-store.js'; import { globalContentConfigObserver } from '../../content/utils.js'; import { telemetry } from '../../events/index.js'; @@ -105,6 +105,11 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise> = []; + const watcherListeners = new Map void>>(); return { sentMessages, environments: { @@ -43,11 +50,26 @@ function createMockViteDevServer() { }, watcher: { add: () => {}, - on: () => {}, + on: (event: string, listener: (path: string) => void) => { + if (!watcherListeners.has(event)) { + watcherListeners.set(event, []); + } + watcherListeners.get(event)!.push(listener); + }, + emit: (event: string, path: string) => { + for (const listener of watcherListeners.get(event) ?? []) { + listener(path); + } + }, }, }; } +function countClientReloads(server: ReturnType) { + return server.sentMessages.filter((msg) => msg.channel === 'client' && msg.type === 'full-reload') + .length; +} + describe('astroContentVirtualModPlugin', () => { it('loads chunk files through validated virtual modules', async () => { const root = createTempDir('content-virtual-mod-chunks-test-'); @@ -166,3 +188,52 @@ describe('astroContentVirtualModPlugin', () => { ); }); }); + +describe('attachDataStoreInvalidation', () => { + it('invalidates on store writes without a watcher event, and skips the watcher echo (#17335)', async (t) => { + const root = createTempDir('content-data-store-invalidation-test-'); + const settings = createMinimalSettings(root, { config: { legacy: {} } }); + const dataStoreFile = getDataStoreFile(settings, true); + const dataStorePath = fileURLToPath(dataStoreFile); + await nodeFs.promises.mkdir(settings.dotAstroDir, { recursive: true }); + + // Mock only Date so the direct-invalidation echo window can be advanced + // without waiting; the store's debounce timers stay real. + t.mock.timers.enable({ apis: ['Date'], now: 10_000 }); + t.after(() => mock.timers.reset()); + + const mockServer = createMockViteDevServer(); + const plugin = astroContentVirtualModPlugin({ settings, fs: nodeFs }); + // @ts-expect-error - mock args are sufficient for this test + plugin.config?.({}, { command: 'serve' }); + // @ts-expect-error - mock server has enough structure for this test + plugin.configureServer?.(mockServer); + + const store = await MutableDataStore.fromFile(dataStoreFile); + // @ts-expect-error - mock server has enough structure for this test + attachDataStoreInvalidation(store, mockServer, settings); + + // A content change is written to the data store. No watcher event is + // emitted, simulating platforms where the watcher misses the atomic + // rename (the Windows failure mode in #17335). + store.set('dogs', 'beagle', { id: 'beagle', data: { breed: 'Beagle' } }); + await store.waitUntilSaveComplete(); + + assert.equal(countClientReloads(mockServer), 1, 'the store write should trigger a reload'); + const contentChanged = mockServer.sentMessages.filter( + (msg) => msg.channel === 'ssr' && msg.type === 'astro:content-changed', + ); + assert.equal(contentChanged.length, 1, 'the SSR runner should be told content changed'); + + // The watcher observes the same write shortly afterwards: that echo must + // not reload clients a second time. + mockServer.watcher.emit('change', dataStorePath); + assert.equal(countClientReloads(mockServer), 1, 'the watcher echo should be skipped'); + + // A watcher event well outside the echo window (e.g. another process + // wrote the store) still invalidates: the fallback path is preserved. + t.mock.timers.tick(5_000); + mockServer.watcher.emit('change', dataStorePath); + assert.equal(countClientReloads(mockServer), 2, 'a later external change should reload'); + }); +}); diff --git a/packages/astro/test/units/content-layer/store-write-notifications.test.ts b/packages/astro/test/units/content-layer/store-write-notifications.test.ts new file mode 100644 index 000000000000..4829ef943792 --- /dev/null +++ b/packages/astro/test/units/content-layer/store-write-notifications.test.ts @@ -0,0 +1,101 @@ +import { strict as assert } from 'node:assert'; +import { describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { DATA_STORE_MANIFEST_FILE } from '../../../dist/content/consts.js'; +import { MutableDataStore } from '../../../dist/content/mutable-data-store.js'; +import { createTempDir } from './test-helpers.ts'; + +const CHUNK_SIZE = 1024 * 1024; + +// The dev server subscribes to these notifications to invalidate the content +// virtual modules deterministically after each write, because the file watcher +// can miss the atomic rename that commits a write on some platforms (notably +// Windows, see #17335). +describe('MutableDataStore - write notifications', () => { + it('notifies with the store file path after a write', async () => { + const tempDir = createTempDir(); + const dataStoreFile = new URL('./data-store.json', tempDir); + const store = await MutableDataStore.fromFile(dataStoreFile); + + const written: string[] = []; + store.onFileWritten((path) => written.push(path)); + + store.set('dogs', 'beagle', { id: 'beagle', data: { breed: 'Beagle' } }); + await store.waitUntilSaveComplete(); + + assert.deepEqual(written, [fileURLToPath(dataStoreFile)]); + }); + + it('does not notify when the data on disk is already identical', async () => { + const tempDir = createTempDir(); + const dataStoreFile = new URL('./data-store.json', tempDir); + const store = await MutableDataStore.fromFile(dataStoreFile); + + store.set('dogs', 'beagle', { id: 'beagle', data: { breed: 'Beagle' } }); + await store.waitUntilSaveComplete(); + + const written: string[] = []; + store.onFileWritten((path) => written.push(path)); + + // Force another save cycle without changing the serialized data. The + // writer skips the identical write, so no notification is emitted and + // the dev server does not reload the page for a no-op sync. + store.set('dogs', 'beagle', { id: 'beagle', data: { breed: 'Beagle' } }); + await store.waitUntilSaveComplete(); + + assert.deepEqual(written, []); + }); + + it('notifies with the manifest path for chunked stores', async () => { + const tempDir = createTempDir(); + const dataStoreDir = new URL('./data-store/', tempDir); + const store = await MutableDataStore.fromDir(dataStoreDir, CHUNK_SIZE); + + const written: string[] = []; + store.onFileWritten((path) => written.push(path)); + + store.set('dogs', 'beagle', { id: 'beagle', data: { breed: 'Beagle' } }); + await store.waitUntilSaveComplete(); + + assert.deepEqual(written, [ + fileURLToPath(new URL(`./${DATA_STORE_MANIFEST_FILE}`, dataStoreDir)), + ]); + }); + + it('notifies for asset import file writes', async () => { + const tempDir = createTempDir(); + const assetsFile = new URL('./content-assets.mjs', tempDir); + const store = new MutableDataStore(); + + const written: string[] = []; + store.onFileWritten((path) => written.push(path)); + + store.scopedStore('categories').set({ + id: 'example', + data: {}, + filePath: 'src/content/categories/example.json', + assetImports: ['./images/seed.webp'], + }); + await store.writeAssetImports(assetsFile); + + assert.deepEqual(written, [fileURLToPath(assetsFile)]); + }); + + it('stops notifying after the listener is removed', async () => { + const tempDir = createTempDir(); + const dataStoreFile = new URL('./data-store.json', tempDir); + const store = await MutableDataStore.fromFile(dataStoreFile); + + const written: string[] = []; + const unsubscribe = store.onFileWritten((path) => written.push(path)); + + store.set('dogs', 'beagle', { id: 'beagle', data: { breed: 'Beagle' } }); + await store.waitUntilSaveComplete(); + assert.equal(written.length, 1); + + unsubscribe(); + store.set('dogs', 'poodle', { id: 'poodle', data: { breed: 'Poodle' } }); + await store.waitUntilSaveComplete(); + assert.equal(written.length, 1); + }); +}); From 1301c374435897654bf52d80d91d0947b72cf1a1 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Fri, 28 Aug 2026 08:04:12 -0400 Subject: [PATCH 04/19] fix(cloudflare): prebundle JSON logger when enabled (#17850) --- .changeset/nine-streets-retire.md | 5 +++++ packages/integrations/cloudflare/src/index.ts | 3 +++ .../cloudflare/test/typegen-phase.test.ts | 22 ++++++++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .changeset/nine-streets-retire.md diff --git a/.changeset/nine-streets-retire.md b/.changeset/nine-streets-retire.md new file mode 100644 index 000000000000..8b6f2159130d --- /dev/null +++ b/.changeset/nine-streets-retire.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': patch +--- + +Fixes React SSR failures on the first Cloudflare dev request when JSON logging is enabled diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 8dffd6fb2682..63d808b450a5 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -359,6 +359,9 @@ export default function createIntegration({ ...(prebundleContentRuntime ? (['astro/content/runtime'] as const) : []), 'astro/compiler-runtime', 'astro/jsx-runtime', + ...(config.logger?.entrypoint === 'astro/logger/json' + ? ['astro/logger/json'] + : []), 'astro/app/entrypoint/dev', 'astro/middleware', 'astro/virtual-modules/middleware.js', diff --git a/packages/integrations/cloudflare/test/typegen-phase.test.ts b/packages/integrations/cloudflare/test/typegen-phase.test.ts index ba961f2406d3..04d827ba0288 100644 --- a/packages/integrations/cloudflare/test/typegen-phase.test.ts +++ b/packages/integrations/cloudflare/test/typegen-phase.test.ts @@ -17,7 +17,10 @@ interface OptimizeDepsPatch { optimizeDeps?: { noDiscovery?: boolean; include?: string[]; exclude?: string[] }; } -async function runConfigSetup(command: 'dev' | 'build' | 'sync') { +async function runConfigSetup( + command: 'dev' | 'build' | 'sync', + loggerConfig?: { entrypoint: string }, +) { const integration = cloudflare(); let updatedConfig: { vite: { plugins: unknown[] } } | undefined; @@ -31,6 +34,7 @@ async function runConfigSetup(command: 'dev' | 'build' | 'sync') { experimental: {}, vite: {}, image: {}, + logger: loggerConfig, }, updateConfig(config: { vite: { plugins: unknown[] } }) { updatedConfig = config; @@ -116,5 +120,21 @@ describe('type generation phase (build and sync)', () => { assert.ok(include.includes('astro/actions/runtime/entrypoints/server.js')); assert.ok(include.includes('astro/actions/runtime/entrypoints/route.js')); }); + + it('only prebundles the JSON logger when it is enabled', async () => { + const defaultConfig = await runConfigSetup('dev'); + const defaultInclude = + defaultConfig.configEnvironment('ssr', {})?.optimizeDeps?.include ?? []; + assert.ok(!defaultInclude.includes('astro/logger/json')); + + const consoleConfig = await runConfigSetup('dev', { entrypoint: 'astro/logger/console' }); + const consoleInclude = + consoleConfig.configEnvironment('ssr', {})?.optimizeDeps?.include ?? []; + assert.ok(!consoleInclude.includes('astro/logger/json')); + + const jsonConfig = await runConfigSetup('dev', { entrypoint: 'astro/logger/json' }); + const jsonInclude = jsonConfig.configEnvironment('ssr', {})?.optimizeDeps?.include ?? []; + assert.ok(jsonInclude.includes('astro/logger/json')); + }); }); }); From 07b919f23e3041c4cc9c4f33004a19a32a5294b3 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 28 Aug 2026 16:19:24 +0100 Subject: [PATCH 05/19] fix: add prism package (#17854) Co-authored-by: Roman --- .changeset/great-bags-flash.md | 5 +++++ packages/integrations/cloudflare/src/index.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/great-bags-flash.md diff --git a/.changeset/great-bags-flash.md b/.changeset/great-bags-flash.md new file mode 100644 index 000000000000..f080628bb11d --- /dev/null +++ b/.changeset/great-bags-flash.md @@ -0,0 +1,5 @@ +--- +'@astrojs/cloudflare': patch +--- + +Added `@astrojs/prism` to the list of dependencies to optimise. The dev server is now faster for sites that use Prism as code highlighter. diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 63d808b450a5..50f58d51f48c 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -258,6 +258,7 @@ export default function createIntegration({ // Note: this "Failed to resolve dependency" log will not appear as long as the `@astrojs/prism` package is installed, // even if it is not actually used. const prismFiles = [ + '@astrojs/prism', '@astrojs/prism > prismjs', '@astrojs/prism > prismjs/components.js', '@astrojs/prism > prismjs/dependencies.js', From 24d253d8b45a81cc2509793b3eeda5ada3d0a34a Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Fri, 28 Aug 2026 17:29:09 +0200 Subject: [PATCH 06/19] ci: pin Deno to 2.9.5 for Netlify integration tests (#17855) --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b3b5b180612..40b03c189460 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -193,6 +193,14 @@ jobs: node-version: ${{ matrix.NODE_VERSION }} cache: "pnpm" + # Deno 2.9.6 dropped `--allow-scripts` from `deno eval`, which @netlify/edge-functions-dev passes to boot its dev server. + # TODO: Remove once fixed upstream. + - name: Pin Deno + if: ${{ matrix.TEST_SUITE.name == 'integrations' }} + uses: denoland/setup-deno@22d081ff2d3a40755e97629de92e3bcbfa7cf2ed # v2.0.5 + with: + deno-version: v2.9.5 + - name: Install dependencies run: pnpm install From 413a6e7a9b966124913893182b83cbd30a9fd3ab Mon Sep 17 00:00:00 2001 From: "astro-factory[bot]" <316791938+astro-factory[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 14:25:05 -0400 Subject: [PATCH 07/19] fix(build): correctly identify winning route in prerender conflict warnings (#17833) Change `builtPaths` from `Set` to `Map` so that conflict warnings reference the route that actually first rendered the duplicate pathname, instead of using `matchRoute()` which could return an unrelated route that merely matches the URL pattern. Co-authored-by: factory[bot] Co-authored-by: Matthew Phillips Co-authored-by: Matthew Phillips --- .changeset/common-mails-pump.md | 5 ++ packages/astro/src/core/build/generate.ts | 52 +++++++++--------- .../astro.config.mjs | 5 ++ .../package.json | 7 +++ .../src/pages/[post].astro | 9 ++++ .../src/pages/[slug].astro | 11 ++++ .../astro/test/prerender-conflict.test.ts | 54 +++++++++++++++++++ pnpm-lock.yaml | 15 +++--- 8 files changed, 121 insertions(+), 37 deletions(-) create mode 100644 .changeset/common-mails-pump.md create mode 100644 packages/astro/test/fixtures/prerender-conflict-same-route/astro.config.mjs create mode 100644 packages/astro/test/fixtures/prerender-conflict-same-route/package.json create mode 100644 packages/astro/test/fixtures/prerender-conflict-same-route/src/pages/[post].astro create mode 100644 packages/astro/test/fixtures/prerender-conflict-same-route/src/pages/[slug].astro diff --git a/.changeset/common-mails-pump.md b/.changeset/common-mails-pump.md new file mode 100644 index 000000000000..804b3e8aee2e --- /dev/null +++ b/.changeset/common-mails-pump.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes prerender conflict warnings to correctly identify the route that first rendered a duplicate pathname, instead of misattributing the conflict to an unrelated route that merely matches the URL pattern diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index c3644ca89416..f27208a593c9 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -35,7 +35,6 @@ import { getRedirectLocationOrThrow } from '../redirects/index.js'; import { createRequest } from '../request.js'; import { redirectTemplate } from '../routing/3xx.js'; import { routeIsRedirect } from '../routing/helpers.js'; -import { matchRoute } from '../routing/match.js'; import { getOutputFilename } from '../output-filename.js'; import { getOutFile, getOutFolder } from './common.js'; import { createDefaultPrerenderer, type DefaultPrerenderer } from './default-prerenderer.js'; @@ -146,7 +145,9 @@ export async function generatePages( // Filter paths for conflicts (same path from multiple routes) const { config } = options.settings; - const builtPaths = new Set(); + // Maps each normalized pathname to the route that first claimed it, + // so conflict warnings identify the actual winning route. + const builtPaths = new Map(); const filteredPaths: typeof pathsWithRoutes = []; const fallbackPaths: typeof pathsWithRoutes = []; for (const pathWithRoute of pathsWithRoutes) { @@ -163,36 +164,31 @@ export async function generatePages( // Path hasn't been built yet, include it if (!builtPaths.has(normalized)) { - builtPaths.add(normalized); + builtPaths.set(normalized, route); } else { - // Path was already built. Check if this route has higher priority. - const matchedRoute = matchRoute(decodeURI(pathname), options.routesList); - if (!matchedRoute) { - continue; - } - - if (matchedRoute !== route) { - // Current route is lower-priority. Warn or error based on config. - if (config.prerenderConflictBehavior === 'error') { - throw new AstroError({ - ...AstroErrorData.PrerenderRouteConflict, - message: AstroErrorData.PrerenderRouteConflict.message( - matchedRoute.route, - route.route, - normalized, - ), - hint: AstroErrorData.PrerenderRouteConflict.hint(matchedRoute.route, route.route), - }); - } else if (config.prerenderConflictBehavior === 'warn') { - const msg = AstroErrorData.PrerenderRouteConflict.message( - matchedRoute.route, + // Path was already built by another route (or a duplicate from the same route). + const winningRoute = builtPaths.get(normalized)!; + + // Warn or error based on config. + if (config.prerenderConflictBehavior === 'error') { + throw new AstroError({ + ...AstroErrorData.PrerenderRouteConflict, + message: AstroErrorData.PrerenderRouteConflict.message( + winningRoute.route, route.route, normalized, - ); - logger.warn('build', msg); - } - continue; + ), + hint: AstroErrorData.PrerenderRouteConflict.hint(winningRoute.route, route.route), + }); + } else if (config.prerenderConflictBehavior === 'warn') { + const msg = AstroErrorData.PrerenderRouteConflict.message( + winningRoute.route, + route.route, + normalized, + ); + logger.warn('build', msg); } + continue; } const paths = route.type === 'fallback' ? fallbackPaths : filteredPaths; diff --git a/packages/astro/test/fixtures/prerender-conflict-same-route/astro.config.mjs b/packages/astro/test/fixtures/prerender-conflict-same-route/astro.config.mjs new file mode 100644 index 000000000000..2c054430146d --- /dev/null +++ b/packages/astro/test/fixtures/prerender-conflict-same-route/astro.config.mjs @@ -0,0 +1,5 @@ +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + prerenderConflictBehavior: 'warn', +}); diff --git a/packages/astro/test/fixtures/prerender-conflict-same-route/package.json b/packages/astro/test/fixtures/prerender-conflict-same-route/package.json new file mode 100644 index 000000000000..16ce3b8e32b7 --- /dev/null +++ b/packages/astro/test/fixtures/prerender-conflict-same-route/package.json @@ -0,0 +1,7 @@ +{ + "name": "@test/prerender-conflict-same-route", + "private": true, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/prerender-conflict-same-route/src/pages/[post].astro b/packages/astro/test/fixtures/prerender-conflict-same-route/src/pages/[post].astro new file mode 100644 index 000000000000..d27ee999ce83 --- /dev/null +++ b/packages/astro/test/fixtures/prerender-conflict-same-route/src/pages/[post].astro @@ -0,0 +1,9 @@ +--- +export async function getStaticPaths() { + return [ + { params: { post: 'post1' } }, + ]; +} +const { post } = Astro.params; +--- +Post: {post} diff --git a/packages/astro/test/fixtures/prerender-conflict-same-route/src/pages/[slug].astro b/packages/astro/test/fixtures/prerender-conflict-same-route/src/pages/[slug].astro new file mode 100644 index 000000000000..f36323f97d81 --- /dev/null +++ b/packages/astro/test/fixtures/prerender-conflict-same-route/src/pages/[slug].astro @@ -0,0 +1,11 @@ +--- +export async function getStaticPaths() { + return [ + { params: { slug: 'page1' } }, + { params: { slug: 'page2' } }, + { params: { slug: 'page2' } }, + ]; +} +const { slug } = Astro.params; +--- +Slug: {slug} diff --git a/packages/astro/test/prerender-conflict.test.ts b/packages/astro/test/prerender-conflict.test.ts index 600972131cd8..2dc27936840a 100644 --- a/packages/astro/test/prerender-conflict.test.ts +++ b/packages/astro/test/prerender-conflict.test.ts @@ -10,6 +10,60 @@ import { type Fixture, loadFixture } from './test-utils.ts'; */ describe('Prerender conflicts', () => { + describe('same route duplicate pathname', () => { + let fixture: Fixture; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/prerender-conflict-same-route/', + outDir: './dist/prerender-conflict-same-route/', + }); + }); + + it('warns with the correct winning route when a route emits the same pathname twice', async () => { + const logs: AstroLoggerMessage[] = []; + const logger = new AstroLogger({ + level: 'warn', + destination: { + write(chunk) { + logs.push(chunk); + return true; + }, + }, + }); + await fixture.build({ + // @ts-expect-error: `_logger` is an internal API + _logger: logger, + }); + + const relevantLogs = logs + .filter((log) => log.level === 'warn' && log.label === 'build') + .map((log) => log.message); + + assert.deepEqual( + relevantLogs, + [ + 'Could not render `/page2` from route `/[slug]` as it conflicts with higher priority route `/[slug]`.', + ], + 'Should identify the same route as the winning route for duplicate pathnames, not a different route that merely matches the pattern.', + ); + }); + + it('fails with the correct winning route when prerenderConflictBehavior is set to error', async () => { + let err: unknown; + try { + await fixture.build({ prerenderConflictBehavior: 'error' }); + } catch (e) { + err = e; + } + assert.ok(err, 'Build should fail when prerenderConflictBehavior is set to error'); + assert.equal( + String(err), + 'PrerenderRouteConflict: Could not render `/page2` from route `/[slug]` as it conflicts with higher priority route `/[slug]`.', + ); + }); + }); + describe('dynamic vs dynamic', () => { let fixture: Fixture; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 171524c6e861..79867e882d09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3923,6 +3923,12 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/prerender-conflict-same-route: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/prerender-conflict-static-dynamic: dependencies: astro: @@ -7178,15 +7184,6 @@ importers: specifier: ^4.22.0 version: 4.22.3 - triage/gh-17848: - dependencies: - '@astrojs/sitemap': - specifier: workspace:* - version: link:../../packages/integrations/sitemap - astro: - specifier: workspace:* - version: link:../../packages/astro - packages: '@anthropic-ai/sdk@0.91.1': From 7c04f2edadbf05f311a0f7273a43d46e827c633e Mon Sep 17 00:00:00 2001 From: ocavue Date: Sat, 29 Aug 2026 22:47:30 +1200 Subject: [PATCH 08/19] ci: enable PR duplicate packages check (#17829) --- .github/workflows/diff-dependencies.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/diff-dependencies.yml b/.github/workflows/diff-dependencies.yml index 63b4bc0d62e8..f6b3b98d9b82 100644 --- a/.github/workflows/diff-dependencies.yml +++ b/.github/workflows/diff-dependencies.yml @@ -21,6 +21,3 @@ jobs: - name: Create Diff uses: e18e/action-dependency-diff@9a7f09f5f2993256322e0db17ee4c8d9339dab77 # v1.7.1 - with: - # We’re using this package primarily to track size changes, not as worried about duplicates - duplicate-threshold: 100 From f8e94585ab6c38e2702ee1e2e540858f72058a40 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Sat, 29 Aug 2026 19:20:45 +0200 Subject: [PATCH 09/19] feat: move MDX into the processors themselves (#17262) Co-authored-by: Armand Philippot --- .../astro-markdown-remark-peer-range.md | 5 + .changeset/internal-helpers-mdx-export.md | 5 + .changeset/markdown-processors-own-mdx.md | 8 + .../mdx-extend-markdown-config-processor.md | 5 + .../mdx-legacy-plugin-options-warning.md | 5 + .../mdx-processor-version-requirements.md | 15 ++ .changeset/unified-recma-plugins.md | 5 + knip.js | 7 +- packages/astro/package.json | 2 +- packages/astro/src/core/util.ts | 46 ---- packages/astro/src/core/viteUtils.ts | 59 +---- packages/integrations/mdx/package.json | 29 +-- .../integrations/mdx/src/image-constants.ts | 10 - packages/integrations/mdx/src/index.ts | 208 ++++++++++-------- .../integrations/mdx/src/processor-guards.ts | 10 +- packages/integrations/mdx/src/utils.ts | 50 +---- .../mdx/src/vite-plugin-mdx-postprocess.ts | 2 +- .../integrations/mdx/src/vite-plugin-mdx.ts | 85 +++---- .../integrations/mdx/test/mdx-plugins.test.ts | 171 ++++++++++++++ packages/integrations/mdx/test/test-utils.ts | 7 - .../integrations/mdx/test/units/utils.test.ts | 94 +------- packages/internal-helpers/package.json | 1 + packages/internal-helpers/src/markdown.ts | 18 +- packages/internal-helpers/src/mdx.ts | 130 +++++++++++ packages/markdown/remark/package.json | 11 + .../src/mdx/create-processor-browser.ts | 18 ++ .../remark/src/mdx/create-processor.ts} | 100 +++++++-- .../src/mdx}/rehype-analyze-astro-metadata.ts | 11 +- .../mdx}/rehype-apply-frontmatter-export.ts | 0 .../src/mdx}/rehype-images-to-component.ts | 4 +- .../src/mdx/rehype-inject-headings-export.ts} | 0 .../remark/src/mdx}/rehype-meta-string.ts | 0 .../remark/src/mdx}/rehype-optimize-static.ts | 0 packages/markdown/remark/src/mdx/utils.ts | 54 +++++ packages/markdown/remark/src/processor.ts | 9 + .../remark/src/rehype-collect-headings.ts | 4 +- .../remark/test}/mdx-compilation.test.ts | 27 ++- .../test/mdx-rehype-optimize-static.test.ts} | 2 +- .../remark/test/mdx-rehype-plugins.test.ts} | 4 +- .../markdown/remark/test/mdx-utils.test.ts | 93 ++++++++ packages/markdown/satteri/package.json | 2 + .../satteri/src/mdx}/charset.ts | 8 +- .../satteri/src/mdx/create-processor.ts} | 88 ++++---- .../satteri/src/mdx}/hast-astro-metadata.ts | 2 +- .../src/mdx}/hast-images-to-component.ts | 2 +- .../satteri/src/mdx}/jsx-utils.ts | 0 packages/markdown/satteri/src/processor.ts | 4 + .../satteri/test/mdx-renderer-options.test.ts | 33 +++ pnpm-lock.yaml | 83 +++---- 49 files changed, 954 insertions(+), 582 deletions(-) create mode 100644 .changeset/astro-markdown-remark-peer-range.md create mode 100644 .changeset/internal-helpers-mdx-export.md create mode 100644 .changeset/markdown-processors-own-mdx.md create mode 100644 .changeset/mdx-extend-markdown-config-processor.md create mode 100644 .changeset/mdx-legacy-plugin-options-warning.md create mode 100644 .changeset/mdx-processor-version-requirements.md create mode 100644 .changeset/unified-recma-plugins.md delete mode 100644 packages/integrations/mdx/src/image-constants.ts create mode 100644 packages/internal-helpers/src/mdx.ts create mode 100644 packages/markdown/remark/src/mdx/create-processor-browser.ts rename packages/{integrations/mdx/src/plugins.ts => markdown/remark/src/mdx/create-processor.ts} (52%) rename packages/{integrations/mdx/src => markdown/remark/src/mdx}/rehype-analyze-astro-metadata.ts (96%) rename packages/{integrations/mdx/src => markdown/remark/src/mdx}/rehype-apply-frontmatter-export.ts (100%) rename packages/{integrations/mdx/src => markdown/remark/src/mdx}/rehype-images-to-component.ts (97%) rename packages/{integrations/mdx/src/rehype-collect-headings.ts => markdown/remark/src/mdx/rehype-inject-headings-export.ts} (100%) rename packages/{integrations/mdx/src => markdown/remark/src/mdx}/rehype-meta-string.ts (100%) rename packages/{integrations/mdx/src => markdown/remark/src/mdx}/rehype-optimize-static.ts (100%) create mode 100644 packages/markdown/remark/src/mdx/utils.ts rename packages/{integrations/mdx/test/units => markdown/remark/test}/mdx-compilation.test.ts (92%) rename packages/{integrations/mdx/test/units/rehype-optimize-static.test.ts => markdown/remark/test/mdx-rehype-optimize-static.test.ts} (96%) rename packages/{integrations/mdx/test/units/rehype-plugins.test.ts => markdown/remark/test/mdx-rehype-plugins.test.ts} (96%) create mode 100644 packages/markdown/remark/test/mdx-utils.test.ts rename packages/{integrations/mdx/src/satteri => markdown/satteri/src/mdx}/charset.ts (89%) rename packages/{integrations/mdx/src/satteri/index.ts => markdown/satteri/src/mdx/create-processor.ts} (74%) rename packages/{integrations/mdx/src/satteri => markdown/satteri/src/mdx}/hast-astro-metadata.ts (98%) rename packages/{integrations/mdx/src/satteri => markdown/satteri/src/mdx}/hast-images-to-component.ts (97%) rename packages/{integrations/mdx/src/satteri => markdown/satteri/src/mdx}/jsx-utils.ts (100%) create mode 100644 packages/markdown/satteri/test/mdx-renderer-options.test.ts diff --git a/.changeset/astro-markdown-remark-peer-range.md b/.changeset/astro-markdown-remark-peer-range.md new file mode 100644 index 000000000000..14d4516d93b1 --- /dev/null +++ b/.changeset/astro-markdown-remark-peer-range.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes `@astrojs/markdown-remark` being pinned to an exact version. diff --git a/.changeset/internal-helpers-mdx-export.md b/.changeset/internal-helpers-mdx-export.md new file mode 100644 index 000000000000..25a1215be80a --- /dev/null +++ b/.changeset/internal-helpers-mdx-export.md @@ -0,0 +1,5 @@ +--- +'@astrojs/internal-helpers': minor +--- + +Adds an `@astrojs/internal-helpers/mdx` entrypoint with the shared helpers the Markdown processor packages use to render `.mdx` files. diff --git a/.changeset/markdown-processors-own-mdx.md b/.changeset/markdown-processors-own-mdx.md new file mode 100644 index 000000000000..b839f53c5146 --- /dev/null +++ b/.changeset/markdown-processors-own-mdx.md @@ -0,0 +1,8 @@ +--- +'@astrojs/markdown-remark': minor +'@astrojs/markdown-satteri': minor +--- + +Adds MDX rendering to the `unified()` and `satteri()` processors. + +Both processors now compile `.mdx` files themselves. You still need to install `@astrojs/mdx` to add MDX support to your project. diff --git a/.changeset/mdx-extend-markdown-config-processor.md b/.changeset/mdx-extend-markdown-config-processor.md new file mode 100644 index 000000000000..47c1723b95e7 --- /dev/null +++ b/.changeset/mdx-extend-markdown-config-processor.md @@ -0,0 +1,5 @@ +--- +'@astrojs/mdx': patch +--- + +Fixes `.mdx` files still using `markdown.processor` when `extendMarkdownConfig` is `false`. They now use a clean default processor instead; pass `mdx({ processor })` to choose one explicitly. diff --git a/.changeset/mdx-legacy-plugin-options-warning.md b/.changeset/mdx-legacy-plugin-options-warning.md new file mode 100644 index 000000000000..b142fa7e4f5e --- /dev/null +++ b/.changeset/mdx-legacy-plugin-options-warning.md @@ -0,0 +1,5 @@ +--- +'@astrojs/mdx': patch +--- + +Adds a warning when the deprecated `remarkPlugins`, `rehypePlugins`, `recmaPlugins` and `remarkRehype` options are ignored because your Markdown processor does not run them. They still apply when your processor is `unified()`, and were previously dropped silently otherwise. diff --git a/.changeset/mdx-processor-version-requirements.md b/.changeset/mdx-processor-version-requirements.md new file mode 100644 index 000000000000..541708155ef6 --- /dev/null +++ b/.changeset/mdx-processor-version-requirements.md @@ -0,0 +1,15 @@ +--- +'@astrojs/mdx': major +--- + +Moves MDX file processing to the Markdown processors. + +'@astrojs/mdx' is still required to add MDX support to your project. However, it now delegates the MDX files processing to Markdown processors. + +#### What should I do? + +If you haven't explicitly installed a Markdown processor, you don't need to do anything. + +Otherwise, ensure that your configured Markdown processor uses the following version: +- `@astrojs/markdown-satteri` 0.4.0 or later if you use `satteri()` +- `@astrojs/markdown-remark` 7.3.0 or later if you use `unified()` diff --git a/.changeset/unified-recma-plugins.md b/.changeset/unified-recma-plugins.md new file mode 100644 index 000000000000..18fe94c681e3 --- /dev/null +++ b/.changeset/unified-recma-plugins.md @@ -0,0 +1,5 @@ +--- +'@astrojs/markdown-remark': minor +--- + +Adds a `recmaPlugins` option to `unified()` for adding recma (estree/JSX) plugins to the MDX compiler. diff --git a/knip.js b/knip.js index 6b4faedb83bc..2594ed7111dd 100644 --- a/knip.js +++ b/knip.js @@ -126,9 +126,8 @@ export default { 'packages/integrations/mdx': { entry: [srcEntry, dtsEntry, testEntry], project, - // Optional peer dep: type-only imports for narrowing the `satteri()` processor. - // Knip flags it because the peer is referenced from source; the runtime stays gated by name-check. - ignoreDependencies: ['@astrojs/markdown-satteri'], + // Optional peer dep: dynamically imported for the deprecated remark/rehype options. + ignoreDependencies: ['@astrojs/markdown-remark'], }, 'packages/markdown/remark': { entry: [srcEntry, dtsEntry, testEntry], @@ -137,6 +136,8 @@ export default { 'packages/markdown/satteri': { entry: [srcEntry, dtsEntry, testEntry], project, + // Only referenced by a `declare module 'hast'` augmentation, which knip doesn't count. + ignoreDependencies: ['@types/hast'], }, 'packages/upgrade': { entry: ['src/index.ts!', testEntry], diff --git a/packages/astro/package.json b/packages/astro/package.json index 63c37e5a3895..b1ba111b3aa6 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -182,7 +182,7 @@ "sharp": "^0.35.4" }, "peerDependencies": { - "@astrojs/markdown-remark": "workspace:*" + "@astrojs/markdown-remark": "^7.3.0" }, "peerDependenciesMeta": { "@astrojs/markdown-remark": { diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 2941ea771f97..bd3cfe54b3ae 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -1,4 +1,3 @@ -import fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import type { AstroSettings } from '../types/astro.js'; import type { AstroConfig } from '../types/public/config.js'; @@ -132,51 +131,6 @@ export function isEndpoint(file: URL, settings: AstroSettings): boolean { return !endsWithPageExt(file, settings) && !file.toString().includes('?astro'); } -export function resolveJsToTs(filePath: string) { - if (filePath.endsWith('.jsx') && !fs.existsSync(filePath)) { - const tryPath = filePath.slice(0, -4) + '.tsx'; - if (fs.existsSync(tryPath)) { - return tryPath; - } - } - return filePath; -} - -// Match Vite's default `resolve.extensions` order so that when multiple -// candidate files exist, we pick the same module Vite will load. -// https://vite.dev/config/shared-options.html#resolve-extensions -const VITE_DEFAULT_RESOLVE_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']; - -/** - * Resolve a path that doesn't name a file on disk (e.g. produced by an - * extensionless import like `import { Counter } from './Counter'`) to the file - * Vite would load, by probing Vite's default extension order and directory - * `index` files. Returns the path unchanged when it already exists as a file - * or when no candidate is found. - */ -export function resolveExtensionlessPath(filePath: string): string { - const stat = fs.statSync(filePath, { throwIfNoEntry: false }); - if (stat?.isFile()) { - return filePath; - } - for (const ext of VITE_DEFAULT_RESOLVE_EXTENSIONS) { - const tryPath = filePath + ext; - if (fs.existsSync(tryPath)) { - return tryPath; - } - } - // Directory import: resolve to its `index` module, like Vite does. - if (stat?.isDirectory()) { - for (const ext of VITE_DEFAULT_RESOLVE_EXTENSIONS) { - const tryPath = `${filePath}/index${ext}`; - if (fs.existsSync(tryPath)) { - return tryPath; - } - } - } - return filePath; -} - /** * Set a default NODE_ENV so Vite doesn't set an incorrect default when loading the Astro config */ diff --git a/packages/astro/src/core/viteUtils.ts b/packages/astro/src/core/viteUtils.ts index 899b2b392aac..09b04badb121 100644 --- a/packages/astro/src/core/viteUtils.ts +++ b/packages/astro/src/core/viteUtils.ts @@ -1,15 +1,10 @@ -import { createRequire } from 'node:module'; import path from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { fileURLToPath } from 'node:url'; import { prependForwardSlash, slash } from '../core/path.js'; import type { ModuleLoader } from './module-loader/index.js'; -import { - resolveExtensionlessPath, - resolveJsToTs, - unwrapId, - VALID_ID_PREFIX, - viteID, -} from './util.js'; +import { unwrapId, VALID_ID_PREFIX, viteID } from './util.js'; + +export { resolvePath } from '@astrojs/internal-helpers/mdx'; const isWindows = typeof process !== 'undefined' && process.platform === 'win32'; @@ -20,52 +15,6 @@ export function normalizePath(id: string) { return path.posix.normalize(isWindows ? slash(id) : id); } -/** - * Resolve island component specifiers to stable paths for hydration metadata. - * - * Examples: - * - `./components/Button.jsx` from `/app/src/pages/index.astro` - * -> `/app/src/pages/components/Button.tsx` (when `.tsx` exists) - * - `../components/Counter` from `/app/src/pages/index.astro` - * -> `/app/src/components/Counter.tsx` (extensionless imports probe Vite's - * default extension order, then directory `index` files) - * - `#components/react/Counter.tsx` - * -> `/app/src/components/react/Counter.tsx` via package `imports` - */ -export function resolvePath(specifier: string, importer: string) { - if (specifier.startsWith('.')) { - const absoluteSpecifier = path.resolve(path.dirname(importer), specifier); - return resolveExtensionlessPath(resolveJsToTs(normalizePath(absoluteSpecifier))); - } else if (specifier.startsWith('#')) { - // Support Node subpath imports (package.json#imports), so this resolves - // before we hand off to non-runnable dev pipelines. - // - // Without this, unresolved values like `/@id/#components/...` can leak - // into client hydration URLs. - try { - // Primary path: CJS-style resolver rooted at the importer. - const resolved = createRequire(pathToFileURL(importer)).resolve(specifier); - return resolveJsToTs(normalizePath(resolved)); - } catch { - try { - // Fallback: ESM resolver in case environments differ. - const importerURL = pathToFileURL(importer).toString(); - const resolved = import.meta.resolve(specifier, importerURL); - const resolvedUrl = new URL(resolved); - if (resolvedUrl.protocol === 'file:') { - return resolveJsToTs(normalizePath(fileURLToPath(resolvedUrl))); - } - } catch { - // fall through - } - } - // Keep original behavior for unresolved specifiers (e.g. package ids). - return specifier; - } else { - return specifier; - } -} - export function rootRelativePath( root: URL, idOrUrl: URL | string, diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json index 63fa681d5d52..3e84c82c76e2 100644 --- a/packages/integrations/mdx/package.json +++ b/packages/integrations/mdx/package.json @@ -35,50 +35,37 @@ }, "dependencies": { "@astrojs/internal-helpers": "workspace:*", - "@astrojs/markdown-remark": "workspace:*", - "@mdx-js/mdx": "^3.1.1", - "acorn": "^8.16.0", - "es-module-lexer": "^2.0.0", - "estree-util-visit": "^2.0.0", - "hast-util-to-html": "^9.0.5", - "piccolore": "^0.1.3", - "rehype-raw": "^7.0.0", - "remark-gfm": "^4.0.1", - "remark-smartypants": "^3.0.2", - "source-map": "^0.7.6", - "unist-util-visit": "^5.1.0", - "vfile": "^6.0.3" + "@astrojs/markdown-satteri": "workspace:*", + "es-module-lexer": "^2.0.0" }, "peerDependencies": { - "@astrojs/markdown-satteri": "^0.3.1", - "astro": "^7.0.0" + "@astrojs/markdown-remark": "^7.3.0", + "@astrojs/markdown-satteri": "^0.4.0", + "astro": "^7.2.6" }, "peerDependenciesMeta": { - "@astrojs/markdown-satteri": { + "@astrojs/markdown-remark": { "optional": true } }, "devDependencies": { - "@astrojs/markdown-satteri": "workspace:*", + "@astrojs/markdown-remark": "workspace:*", "@shikijs/rehype": "^4.0.2", "@shikijs/twoslash": "^4.0.2", - "@types/estree": "^1.0.8", "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "astro": "workspace:*", "astro-scripts": "workspace:*", "cheerio": "1.2.0", "linkedom": "^0.18.12", - "mdast-util-mdx": "^3.0.0", - "mdast-util-mdx-jsx": "^3.2.0", "rehype-mathjax": "^7.1.0", "rehype-pretty-code": "^0.14.3", "remark-math": "^6.0.0", - "remark-rehype": "^11.1.2", "remark-toc": "^9.0.0", "satteri": "^0.10.3", "shiki": "^4.0.2", "unified": "^11.0.5", + "unist-util-visit": "^5.1.0", "vite": "^8.0.13" }, "engines": { diff --git a/packages/integrations/mdx/src/image-constants.ts b/packages/integrations/mdx/src/image-constants.ts deleted file mode 100644 index 643015ac683d..000000000000 --- a/packages/integrations/mdx/src/image-constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Tag name we rewrite markdown-derived `` elements to. Lowercase + hyphenated -// so MDX routes the tag through the `_components` map. -export const ASTRO_IMAGE_ELEMENT = 'astro-image'; -// Module-level identifier bound to Astro's `Image` component (from `astro:assets`). -// Imported by every compiled MDX file that contains a rewritten image; used as the -// fallback when no `components.img` is provided. -export const ASTRO_IMAGE_IMPORT = '__AstroImage__'; -// Boolean export set on MDX modules that contain rewritten images. Read by -// `vite-plugin-mdx-postprocess` to decide whether to wire up the image component. -export const USES_ASTRO_IMAGE_FLAG = '__usesAstroImage'; diff --git a/packages/integrations/mdx/src/index.ts b/packages/integrations/mdx/src/index.ts index a054545d204c..6243e1c09f61 100644 --- a/packages/integrations/mdx/src/index.ts +++ b/packages/integrations/mdx/src/index.ts @@ -3,7 +3,12 @@ import { fileURLToPath } from 'node:url'; import { type AstroMarkdownOptions, markdownConfigDefaults, + type PluggableList, + type RehypePlugins, + type RemarkPlugins, + type RemarkRehype as RemarkRehypeOptions, } from '@astrojs/internal-helpers/markdown'; +import { isSatteriProcessor, satteri } from '@astrojs/markdown-satteri'; import type { AstroIntegration, AstroIntegrationLogger, @@ -12,15 +17,17 @@ import type { HookParameters, } from 'astro'; import type { MarkdownProcessor } from 'astro/markdown'; -import type { Options as RemarkRehypeOptions } from 'remark-rehype'; -import type { PluggableList } from 'unified'; import { getContainerRenderer as getContainerRendererImpl } from './container-renderer.js'; -import { isSatteriProcessor, isUnifiedProcessor } from './processor-guards.js'; -import type { OptimizeOptions } from './rehype-optimize-static.js'; -import { ignoreStringPlugins, safeParseFrontmatter } from './utils.js'; +import { isUnifiedProcessor } from './processor-guards.js'; +import { safeParseFrontmatter } from './utils.js'; import { type VitePluginMdxOptions, vitePluginMdx } from './vite-plugin-mdx.js'; import { vitePluginMdxPostprocess } from './vite-plugin-mdx-postprocess.js'; +/** MDX static-optimization options. Mirror of the pipeline's `OptimizeOptions`. */ +export interface OptimizeOptions { + ignoreElementNames?: string[]; +} + // `gfm`/`smartypants` are deprecated and stay unset unless the user opts in; the // MDX pipelines treat an absent value as the default (on), like the `.md` processors. type SharedMarkdownOptions = Required< @@ -30,12 +37,18 @@ type SharedMarkdownOptions = Required< export type MdxOptions = SharedMarkdownOptions & { extendMarkdownConfig: boolean; + /** + * @deprecated Pass `recmaPlugins` to `unified({ recmaPlugins })` from `@astrojs/markdown-remark` and set it as `markdown.processor` instead. Will be removed in a future major. + */ recmaPlugins: PluggableList; optimize: boolean | OptimizeOptions; /** - * Override the markdown processor for `.mdx` files. Defaults to `config.markdown.processor`. + * Override the markdown processor for `.mdx` files. Defaults to `config.markdown.processor`, + * or to a clean `satteri()` processor when `extendMarkdownConfig` is `false`. * Use this to run `.mdx` files through a different processor (or the same processor with - * different options) than your `.md` files. + * different options) than your `.md` files. It is never replaced: the deprecated + * `remarkPlugins`, `rehypePlugins`, `recmaPlugins` and `remarkRehype` options apply only + * when the processor is `unified`, and are ignored with a warning otherwise. */ processor?: MarkdownProcessor; // Markdown allows strings as remark and rehype plugins. @@ -59,11 +72,7 @@ export type MdxOptions = SharedMarkdownOptions & { * @internal */ export type ResolvedMdxOptions = SharedMarkdownOptions & { - recmaPlugins: PluggableList; optimize: boolean | OptimizeOptions; - remarkPlugins: PluggableList; - rehypePlugins: PluggableList; - remarkRehype: RemarkRehypeOptions; }; type SetupHookParams = HookParameters<'astro:config:setup'> & { @@ -126,7 +135,7 @@ export default function mdx(partialMdxOptions: Partial = {}): AstroI }, }); }, - 'astro:config:done': ({ config, logger }) => { + 'astro:config:done': async ({ config, logger }) => { warnDeprecatedMdxPluginOptions(partialMdxOptions, logger); // We resolve the final MDX options here so that other integrations have a chance to modify @@ -138,63 +147,53 @@ export default function mdx(partialMdxOptions: Partial = {}): AstroI const resolvedMdxOptions = applyDefaultOptions({ options: partialMdxOptions, - defaults: markdownConfigToMdxOptions(markdownConfig, logger), + defaults: { ...markdownConfig, optimize: false }, }); - const processor = partialMdxOptions.processor ?? config.markdown.processor; + // `extendMarkdownConfig: false` means `.mdx` must not inherit `markdown.processor`. + const configuredProcessor = + partialMdxOptions.processor ?? + (extendMarkdownConfig ? config.markdown.processor : undefined); + let processor = configuredProcessor ?? satteri(); - if (extendMarkdownConfig && isUnifiedProcessor(processor)) { - // MDX inherits from the processor only when the user did NOT pass that option - // to `mdx({...})`. Following the historical contract: MDX's value REPLACES the - // markdown processor's value (no per-key merge). - if (partialMdxOptions.remarkPlugins === undefined) { - resolvedMdxOptions.remarkPlugins = ignoreStringPlugins( - processor.options.remarkPlugins, - logger, - ); - } - if (partialMdxOptions.rehypePlugins === undefined) { - resolvedMdxOptions.rehypePlugins = ignoreStringPlugins( - processor.options.rehypePlugins, + if (hasLegacyMdxPluginOptions(partialMdxOptions)) { + const base = isUnifiedProcessor(processor) ? processor.options : undefined; + // Never replace a configured processor; a fallback default is ours to choose. + const mayUseUnified = base !== undefined || configuredProcessor === undefined; + const unified = mayUseUnified ? await importUnified() : undefined; + if (unified) { + processor = unified({ + // MDX plugin lists are function-only; widen to the processor's plugin type. + remarkPlugins: + (partialMdxOptions.remarkPlugins as RemarkPlugins | undefined) ?? + base?.remarkPlugins, + rehypePlugins: + (partialMdxOptions.rehypePlugins as RehypePlugins | undefined) ?? + base?.rehypePlugins, + remarkRehype: partialMdxOptions.remarkRehype ?? base?.remarkRehype, + recmaPlugins: partialMdxOptions.recmaPlugins ?? base?.recmaPlugins, + gfm: base?.gfm, + smartypants: base?.smartypants, + }); + } else { + warnLegacyMdxPluginOptionsIgnored(partialMdxOptions, processor, { + userConfigured: configuredProcessor !== undefined, logger, - ); - } - if (partialMdxOptions.remarkRehype === undefined) { - resolvedMdxOptions.remarkRehype = { ...processor.options.remarkRehype }; - } - // `gfm`/`smartypants` from `unified({...})` apply to `.mdx` too, unless - // `mdx({...})` set its own. - if (partialMdxOptions.gfm === undefined && processor.options.gfm !== undefined) { - resolvedMdxOptions.gfm = processor.options.gfm; - } - if ( - partialMdxOptions.smartypants === undefined && - processor.options.smartypants !== undefined - ) { - resolvedMdxOptions.smartypants = processor.options.smartypants; + }); } } - if (extendMarkdownConfig && isSatteriProcessor(processor)) { - // `gfm`/`smartPunctuation` from `satteri({ features: {...} })` apply to `.mdx` - // too, unless `mdx({...})` set its own. Mirrors the unified branch above. - const features = processor.options.features; - // `gfm` can be `boolean | GfmOptions`; only the boolean form is shape-compatible - // with `mdxOptions.gfm`. Object configs stay on the processor and are applied at - // the satteri/mdx boundary, like `smartPunctuation` below. - if (partialMdxOptions.gfm === undefined && typeof features.gfm === 'boolean') { - resolvedMdxOptions.gfm = features.gfm; - } - // `smartPunctuation` can be `boolean | SmartPunctuationOptions`; only the boolean - // form is shape-compatible with `mdxOptions.smartypants`. Object configs stay on - // the processor and are applied at the satteri/mdx boundary. - if ( - partialMdxOptions.smartypants === undefined && - typeof features.smartPunctuation === 'boolean' - ) { - resolvedMdxOptions.smartypants = features.smartPunctuation; - } + + // Without this, the deprecated `markdown.*` would outrank the processor, unlike `.md`. + const processorFeatures = readProcessorFeatures(processor); + if (partialMdxOptions.gfm === undefined && processorFeatures.gfm !== undefined) { + resolvedMdxOptions.gfm = processorFeatures.gfm; + } + if ( + partialMdxOptions.smartypants === undefined && + processorFeatures.smartypants !== undefined + ) { + resolvedMdxOptions.smartypants = processorFeatures.smartypants; } - // Other third-party processors handle their own pipeline via `createMdxRenderer`. // Mutate `mdxOptions` so that `vitePluginMdx` can reference the actual options Object.assign(vitePluginMdxOptions, { @@ -214,6 +213,64 @@ const defaultMdxOptions = { extendMarkdownConfig: true, } satisfies Partial; +const LEGACY_PLUGIN_OPTIONS = [ + 'remarkPlugins', + 'rehypePlugins', + 'remarkRehype', + 'recmaPlugins', +] as const; + +// `mdx({ remarkPlugins: [] })` is a documented opt-out, so empty counts as set. +function hasLegacyMdxPluginOptions(options: Partial): boolean { + return LEGACY_PLUGIN_OPTIONS.some((key) => options[key] !== undefined); +} + +async function importUnified(): Promise< + typeof import('@astrojs/markdown-remark').unified | undefined +> { + try { + return (await import('@astrojs/markdown-remark')).unified; + } catch { + return undefined; + } +} + +function warnLegacyMdxPluginOptionsIgnored( + options: Partial, + processor: MarkdownProcessor, + { userConfigured, logger }: { userConfigured: boolean; logger: AstroIntegrationLogger }, +): void { + const ignored = LEGACY_PLUGIN_OPTIONS.filter((key) => options[key] !== undefined); + const names = ignored.map((key) => `\`${key}\``).join(', '); + const isPlural = ignored.length > 1; + const whose = userConfigured + ? `your \`${processor.name}\` processor` + : `the default \`${processor.name}\` processor used for \`.mdx\``; + logger.warn( + `${names} on \`mdx({...})\` ${isPlural ? 'are' : 'is'} ignored because ${whose} does not run remark/rehype plugins. Set \`markdown.processor: unified({...})\` from \`@astrojs/markdown-remark\` to apply them.`, + ); +} + +/** + * The processor's own equivalent of `gfm`/`smartypants`, when shape-compatible with the shared + * markdown options. Sätteri's object-form features have none, so they stay on the processor. + */ +function readProcessorFeatures( + processor: MarkdownProcessor, +): Pick { + if (isUnifiedProcessor(processor)) { + return { gfm: processor.options.gfm, smartypants: processor.options.smartypants }; + } + if (isSatteriProcessor(processor)) { + const { gfm, smartPunctuation } = processor.options.features; + return { + gfm: typeof gfm === 'boolean' ? gfm : undefined, + smartypants: typeof smartPunctuation === 'boolean' ? smartPunctuation : undefined, + }; + } + return {}; +} + let didWarnAboutDeprecatedMdxPluginOptions = false; function warnDeprecatedMdxPluginOptions( @@ -221,9 +278,7 @@ function warnDeprecatedMdxPluginOptions( logger: AstroIntegrationLogger, ): void { if (didWarnAboutDeprecatedMdxPluginOptions) return; - const deprecated = (['remarkPlugins', 'rehypePlugins', 'remarkRehype'] as const).filter( - (key) => options[key] !== undefined, - ); + const deprecated = LEGACY_PLUGIN_OPTIONS.filter((key) => options[key] !== undefined); if (deprecated.length === 0) return; didWarnAboutDeprecatedMdxPluginOptions = true; @@ -237,25 +292,6 @@ function warnDeprecatedMdxPluginOptions( ); } -function markdownConfigToMdxOptions( - markdownConfig: SharedMarkdownOptions, - _logger: AstroIntegrationLogger, -): ResolvedMdxOptions { - return { - ...markdownConfig, - // Deprecated `markdown.{gfm,smartypants}` may be unset (optional in the schema); - // fall back to the processor defaults so the MDX pipeline still enables them by default. - gfm: markdownConfig.gfm ?? markdownConfigDefaults.gfm, - smartypants: markdownConfig.smartypants ?? markdownConfigDefaults.smartypants, - recmaPlugins: [], - optimize: false, - // Plugins come from the processor — merged in astro:config:done. - remarkPlugins: [], - rehypePlugins: [], - remarkRehype: {}, - }; -} - function applyDefaultOptions({ options, defaults, @@ -268,10 +304,6 @@ function applyDefaultOptions({ shikiConfig: options.shikiConfig ?? defaults.shikiConfig, gfm: options.gfm ?? defaults.gfm, smartypants: options.smartypants ?? defaults.smartypants, - recmaPlugins: options.recmaPlugins ?? defaults.recmaPlugins, optimize: options.optimize ?? defaults.optimize, - remarkPlugins: options.remarkPlugins ?? defaults.remarkPlugins, - rehypePlugins: options.rehypePlugins ?? defaults.rehypePlugins, - remarkRehype: options.remarkRehype ?? defaults.remarkRehype, }; } diff --git a/packages/integrations/mdx/src/processor-guards.ts b/packages/integrations/mdx/src/processor-guards.ts index 5797873e8562..37b9a981088b 100644 --- a/packages/integrations/mdx/src/processor-guards.ts +++ b/packages/integrations/mdx/src/processor-guards.ts @@ -1,14 +1,10 @@ import type { UnifiedResolvedOptions } from '@astrojs/markdown-remark'; -import type { SatteriResolvedOptions } from '@astrojs/markdown-satteri'; import type { MarkdownProcessor } from 'astro/markdown'; -// Name-checks for the built-in processors. Type-only imports keep -// `@astrojs/markdown-satteri` (an optional peer) out of MDX's runtime graph. +// Name-check for the built-in `unified` processor. The type-only import keeps +// `@astrojs/markdown-remark` (a dev-only dependency now that its pipeline is invoked via the +// processor) out of MDX's runtime graph. export const isUnifiedProcessor = (p: { name: string; }): p is MarkdownProcessor => p.name === 'unified'; - -export const isSatteriProcessor = (p: { - name: string; -}): p is MarkdownProcessor => p.name === 'satteri'; diff --git a/packages/integrations/mdx/src/utils.ts b/packages/integrations/mdx/src/utils.ts index 2bde89e43a57..011d13060ae9 100644 --- a/packages/integrations/mdx/src/utils.ts +++ b/packages/integrations/mdx/src/utils.ts @@ -1,10 +1,5 @@ import { parseFrontmatter } from '@astrojs/internal-helpers/frontmatter'; -import type { Options as AcornOpts } from 'acorn'; -import { parse } from 'acorn'; -import type { AstroConfig, AstroIntegrationLogger, SSRError } from 'astro'; -import type { MdxjsEsm } from 'mdast-util-mdx'; -import colors from 'piccolore'; -import type { PluggableList } from 'unified'; +import type { AstroConfig, SSRError } from 'astro'; export function appendForwardSlash(path: string) { return path.endsWith('/') ? path : path + '/'; @@ -63,46 +58,3 @@ export function safeParseFrontmatter(code: string, id: string) { } } } - -export function jsToTreeNode( - jsString: string, - acornOpts: AcornOpts = { - ecmaVersion: 'latest', - sourceType: 'module', - }, -): MdxjsEsm { - return { - type: 'mdxjsEsm', - value: '', - data: { - // @ts-expect-error `parse` return types is incompatible but it should work in runtime - estree: { - ...parse(jsString, acornOpts), - type: 'Program', - sourceType: 'module', - }, - }, - }; -} - -export function ignoreStringPlugins(plugins: any[], logger: AstroIntegrationLogger): PluggableList { - let validPlugins: PluggableList = []; - let hasInvalidPlugin = false; - for (const plugin of plugins) { - if (typeof plugin === 'string') { - logger.warn(`${colors.bold(plugin)} not applied.`); - hasInvalidPlugin = true; - } else if (Array.isArray(plugin) && typeof plugin[0] === 'string') { - logger.warn(`${colors.bold(plugin[0])} not applied.`); - hasInvalidPlugin = true; - } else { - validPlugins.push(plugin); - } - } - if (hasInvalidPlugin) { - logger.warn( - `To inherit Markdown plugins in MDX, please use explicit imports in your config instead of "strings." See Markdown docs: https://docs.astro.build/en/guides/markdown-content/#markdown-plugins`, - ); - } - return validPlugins; -} diff --git a/packages/integrations/mdx/src/vite-plugin-mdx-postprocess.ts b/packages/integrations/mdx/src/vite-plugin-mdx-postprocess.ts index c401f25e5abf..375b4da3dba1 100644 --- a/packages/integrations/mdx/src/vite-plugin-mdx-postprocess.ts +++ b/packages/integrations/mdx/src/vite-plugin-mdx-postprocess.ts @@ -5,7 +5,7 @@ import { ASTRO_IMAGE_ELEMENT, ASTRO_IMAGE_IMPORT, USES_ASTRO_IMAGE_FLAG, -} from './rehype-images-to-component.js'; +} from '@astrojs/internal-helpers/mdx'; import { type FileInfo, getFileInfo } from './utils.js'; const underscoreFragmentImportRegex = /[\s,{]_Fragment[\s,}]/; diff --git a/packages/integrations/mdx/src/vite-plugin-mdx.ts b/packages/integrations/mdx/src/vite-plugin-mdx.ts index 58a6a2924c35..e8a97d28ba7a 100644 --- a/packages/integrations/mdx/src/vite-plugin-mdx.ts +++ b/packages/integrations/mdx/src/vite-plugin-mdx.ts @@ -1,9 +1,7 @@ import type { SSRError } from 'astro'; import type { MarkdownProcessor, MdxRenderer } from 'astro/markdown'; -import { VFile } from 'vfile'; import type { Plugin } from 'vite'; import type { ResolvedMdxOptions } from './index.js'; -import { isSatteriProcessor, isUnifiedProcessor } from './processor-guards.js'; import { safeParseFrontmatter } from './utils.js'; export interface VitePluginMdxOptions { @@ -93,70 +91,41 @@ export function vitePluginMdx(opts: VitePluginMdxOptions): Plugin { }; } +// The package each built-in processor comes from, so the error can name what to update. +const BUILT_IN_PROCESSOR_PACKAGES: Record = { + satteri: '@astrojs/markdown-satteri', + unified: '@astrojs/markdown-remark', +}; + +function mdxUnsupportedMessage(name: string): string { + const pkg = BUILT_IN_PROCESSOR_PACKAGES[name]; + if (pkg) { + return `\`${pkg}\` is too old to render \`.mdx\` files. Update it to the latest version — a \`^\` range on an older version will not pick it up:\n npm install ${pkg}@latest`; + } + return `The markdown processor "${name}" does not provide MDX support. Implement \`createMdxRenderer\` on the processor to enable MDX rendering.`; +} + async function resolveMdxRenderer( opts: VitePluginMdxOptions, sourcemap: boolean, ): Promise { const { processor } = opts; - // Third-party processors opt into MDX support by implementing createMdxRenderer themselves. - if (processor.createMdxRenderer) { - return processor.createMdxRenderer( - { - syntaxHighlight: opts.mdxOptions.syntaxHighlight, - shikiConfig: opts.mdxOptions.shikiConfig, - gfm: opts.mdxOptions.gfm, - smartypants: opts.mdxOptions.smartypants, - }, - { optimize: opts.mdxOptions.optimize, recmaPlugins: opts.mdxOptions.recmaPlugins }, - ); + if (!processor.createMdxRenderer) { + throw new Error(mdxUnsupportedMessage(processor.name)); } - if (isSatteriProcessor(processor)) { - const { createMdxProcessor: createSatteriMdxProcessor } = await import('./satteri/index.js'); - const satteriProcessor = createSatteriMdxProcessor(opts.mdxOptions, processor.options, { + return processor.createMdxRenderer( + { + syntaxHighlight: opts.mdxOptions.syntaxHighlight, + shikiConfig: opts.mdxOptions.shikiConfig, + gfm: opts.mdxOptions.gfm, + smartypants: opts.mdxOptions.smartypants, + }, + { + optimize: opts.mdxOptions.optimize, srcDir: opts.srcDir, - }); - return { - async process(content, filePath, frontmatter) { - const result = await satteriProcessor.process(content, filePath, frontmatter); - return { code: result.code, map: null, astroMetadata: result.astroMetadata }; - }, - }; - } - - if (isUnifiedProcessor(processor)) { - const { createMdxProcessor } = await import('./plugins.js'); - const { getAstroMetadata } = await import('./rehype-analyze-astro-metadata.js'); - const unifiedProcessor = createMdxProcessor(opts.mdxOptions, { sourcemap }); - return { - async process(content, filePath, frontmatter) { - const vfile = new VFile({ - value: content, - path: filePath, - data: { - astro: { frontmatter }, - applyFrontmatterExport: { srcDir: opts.srcDir }, - }, - }); - const compiled = await unifiedProcessor.process(vfile); - const astroMetadata = getAstroMetadata(vfile); - if (!astroMetadata) { - throw new Error( - 'Internal MDX error: Astro metadata is not set by rehype-analyze-astro-metadata', - ); - } - return { - code: String(compiled.value), - map: compiled.map ? JSON.stringify(compiled.map) : null, - astroMetadata, - }; - }, - }; - } - - throw new Error( - `The markdown processor "${processor.name}" does not provide MDX support. ` + - `Implement \`createMdxRenderer\` on the processor to enable MDX rendering.`, + sourcemap, + }, ); } diff --git a/packages/integrations/mdx/test/mdx-plugins.test.ts b/packages/integrations/mdx/test/mdx-plugins.test.ts index 3fedac4b09b3..dff1a070f227 100644 --- a/packages/integrations/mdx/test/mdx-plugins.test.ts +++ b/packages/integrations/mdx/test/mdx-plugins.test.ts @@ -1,9 +1,11 @@ import * as assert from 'node:assert/strict'; import { before, describe, it } from 'node:test'; import { unified } from '@astrojs/markdown-remark'; +import { satteri } from '@astrojs/markdown-satteri'; import mdx from '@astrojs/mdx'; import { parseHTML } from 'linkedom'; import remarkToc from 'remark-toc'; +import { defineHastPlugin } from 'satteri'; import { loadFixture, type AstroInlineConfig, @@ -60,6 +62,130 @@ describe('MDX plugins - Astro config integration', () => { assert.notEqual(selectRehypeExample(document), null); }); + describe('markdown.processor inheritance', () => { + it('inherits `markdown.processor` by default', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-processor-inherited/', + markdown: { processor: satteriWithMarker() }, + integrations: [mdx()], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.notEqual(selectSatteriMarker(document), null); + }); + + it('does not inherit `markdown.processor` when `extendMarkdownConfig` is false', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-processor-not-inherited/', + markdown: { processor: satteriWithMarker() }, + integrations: [mdx({ extendMarkdownConfig: false })], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.equal(selectSatteriMarker(document), null); + }); + + it('keeps an explicit `mdx({ processor })` over the deprecated plugin options', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-processor-explicit/', + integrations: [ + mdx({ processor: satteriWithMarker(), remarkPlugins: [remarkExamplePlugin] }), + ], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.notEqual(selectSatteriMarker(document), null); + assert.equal(selectRemarkExample(document), null); + }); + }); + + describe('deprecated plugin options never replace the processor', () => { + it('ignores them and keeps a Sätteri `markdown.processor` rendering `.mdx`', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-legacy-satteri/', + markdown: { processor: satteriWithMarker() }, + integrations: [mdx({ remarkPlugins: [remarkExamplePlugin] })], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.notEqual(selectSatteriMarker(document), null, 'Sätteri processor was replaced.'); + assert.equal(selectRemarkExample(document), null); + }); + + it('ignores `recmaPlugins` without replacing a Sätteri processor', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-legacy-recma/', + markdown: { processor: satteriWithMarker() }, + integrations: [mdx({ recmaPlugins: [recmaExamplePlugin] })], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.notEqual(selectSatteriMarker(document), null, 'Sätteri processor was replaced.'); + assert.notEqual( + selectRecmaExample(document)?.getAttribute('data-recma-plugin-works'), + 'true', + 'recma plugin ran, so the processor was replaced.', + ); + }); + + it('folds them into an already-`unified` processor, replacing per key', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-legacy-unified/', + markdown: { processor: unified({ remarkPlugins: [remarkToc] }) }, + integrations: [mdx({ remarkPlugins: [remarkExamplePlugin] })], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.notEqual(selectRemarkExample(document), null, 'MDX remark plugins not applied.'); + assert.equal(selectTocLink(document), null, 'Should replace the processor plugins.'); + }); + + it('treats an empty list as an opt-out from the processor plugins', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-legacy-empty/', + markdown: { processor: unified({ remarkPlugins: [remarkToc] }) }, + integrations: [mdx({ remarkPlugins: [] })], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.equal(selectTocLink(document), null, '`remarkPlugins: []` did not opt out.'); + }); + + it('inherits the processor plugins when the option is absent', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-legacy-absent/', + markdown: { processor: unified({ remarkPlugins: [remarkToc] }) }, + integrations: [mdx()], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.notEqual(selectTocLink(document), null, 'Processor plugins were not inherited.'); + }); + }); + + describe('gfm precedence', () => { + it('lets `mdx({ gfm })` override the processor own feature', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-gfm-mdx-wins/', + markdown: { processor: satteri({ features: { gfm: false } }) }, + integrations: [mdx({ gfm: true })], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.notEqual(selectGfmLink(document), null); + }); + + it('honours the processor own feature when `extendMarkdownConfig` is false', async () => { + const fixture = await buildFixture({ + outDir: './dist/mdx-plugins-gfm-processor-wins/', + integrations: [mdx({ extendMarkdownConfig: false, processor: unified({ gfm: false }) })], + }); + const { document } = parseHTML(await fixture.readFile(FILE)); + + assert.equal(selectGfmLink(document), null); + }); + }); + for (const extendMarkdownConfig of [true, false]) { describe(`extendMarkdownConfig = ${extendMarkdownConfig}`, () => { let fixture: Fixture; @@ -147,6 +273,28 @@ async function buildFixture(config: AstroInlineConfig = {}): Promise { return fixture; } +// A fresh processor per fixture: integrations extend the pipeline by mutating `processor.options`. +function satteriWithMarker() { + return satteri({ + hastPlugins: [ + defineHastPlugin({ + name: 'append-marker', + element: { + filter: ['h1'], + visit(node, ctx) { + ctx.appendChild(node, { + type: 'element', + tagName: 'span', + properties: { id: 'satteri-plugin-works' }, + children: [], + }); + }, + }, + }), + ], + }); +} + const remarkExamplePlugin: RemarkPlugin = () => { return (tree) => { tree.children.push({ @@ -156,6 +304,21 @@ const remarkExamplePlugin: RemarkPlugin = () => { }; }; +// Flips the fixture's `export let recmaPluginWorking = false`, which it binds to a `data-` attribute. +const recmaExamplePlugin = () => { + return (tree: any) => { + for (const node of tree.body) { + const declaration = node.type === 'ExportNamedDeclaration' ? node.declaration : node; + if (declaration?.type !== 'VariableDeclaration') continue; + for (const declarator of declaration.declarations) { + if (declarator.id?.name === 'recmaPluginWorking') { + declarator.init = { type: 'Literal', value: true, raw: 'true' }; + } + } + } + }; +}; + const rehypeExamplePlugin: RehypePlugin = () => { return (tree) => { tree.children.push({ @@ -179,6 +342,14 @@ function selectSmartypantsQuote(document: Document) { return document.querySelector('blockquote'); } +function selectRecmaExample(document: Document) { + return document.querySelector('div[data-recma-plugin-works]'); +} + +function selectSatteriMarker(document: Document) { + return document.querySelector('h1 span#satteri-plugin-works'); +} + function selectRemarkExample(document: Document) { return document.querySelector('div[data-remark-plugin-works]'); } diff --git a/packages/integrations/mdx/test/test-utils.ts b/packages/integrations/mdx/test/test-utils.ts index 1a0a6491637d..53e059a50c27 100644 --- a/packages/integrations/mdx/test/test-utils.ts +++ b/packages/integrations/mdx/test/test-utils.ts @@ -1,9 +1,7 @@ -import type * as estree from 'estree'; import type * as hast from 'hast'; import type * as mdast from 'mdast'; import type * as unified from 'unified'; -export { SpyLogger } from 'astro/_internal/test/units/test-utils'; export { loadFixture, type AstroInlineConfig, @@ -20,8 +18,3 @@ export type RehypePlugin = unified.Plugi PluginParameters, hast.Root >; - -export type RecmaPlugin = unified.Plugin< - PluginParameters, - estree.Program ->; diff --git a/packages/integrations/mdx/test/units/utils.test.ts b/packages/integrations/mdx/test/units/utils.test.ts index 9bf181496b17..ffa774092e20 100644 --- a/packages/integrations/mdx/test/units/utils.test.ts +++ b/packages/integrations/mdx/test/units/utils.test.ts @@ -1,13 +1,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import type { AstroConfig } from 'astro'; -import { - appendForwardSlash, - getFileInfo, - ignoreStringPlugins, - jsToTreeNode, -} from '../../dist/utils.js'; -import { SpyLogger } from '../test-utils.ts'; +import { appendForwardSlash, getFileInfo } from '../../dist/utils.js'; describe('utils', () => { describe('appendForwardSlash', () => { @@ -93,90 +87,4 @@ describe('utils', () => { assert.equal(result.fileUrl, '/other/path/file.mdx'); }); }); - - describe('jsToTreeNode', () => { - it('parses a simple export statement', () => { - const node = jsToTreeNode('export const x = 1;'); - const estree = node.data!.estree!; - assert.equal(node.type, 'mdxjsEsm'); - assert.equal(estree.type, 'Program'); - assert.equal(estree.sourceType, 'module'); - assert.ok(estree.body.length > 0); - }); - - it('parses an import statement', () => { - const node = jsToTreeNode("import foo from 'bar';"); - assert.equal(node.type, 'mdxjsEsm'); - assert.equal(node.data!.estree!.body[0].type, 'ImportDeclaration'); - }); - - it('parses a function export', () => { - const node = jsToTreeNode('export function getHeadings() { return []; }'); - assert.equal(node.type, 'mdxjsEsm'); - const decl = node.data!.estree!.body[0]; - assert.equal(decl.type, 'ExportNamedDeclaration'); - }); - - it('throws on invalid JS', () => { - assert.throws(() => jsToTreeNode('this is not valid javascript {{{'), { - name: 'SyntaxError', - }); - }); - }); - - describe('ignoreStringPlugins', () => { - it('returns function plugins unchanged', () => { - const plugin1 = () => {}; - const plugin2 = () => {}; - const spyLogger = new SpyLogger(); - const logger = spyLogger.forkIntegrationLogger('test-spy'); - const result = ignoreStringPlugins([plugin1, plugin2], logger); - assert.equal(result.length, 2); - assert.equal(result[0], plugin1); - assert.equal(result[1], plugin2); - assert.equal(spyLogger.logs.filter((m) => m.level === 'warn').length, 0); - }); - - it('filters out string-based plugins', () => { - const fnPlugin = () => {}; - const spyLogger = new SpyLogger(); - const logger = spyLogger.forkIntegrationLogger('test-spy'); - const result = ignoreStringPlugins(['remark-toc', fnPlugin], logger); - assert.equal(result.length, 1); - assert.equal(result[0], fnPlugin); - }); - - it('filters out array-based string plugins [string, options]', () => { - const fnPlugin = () => {}; - const spyLogger = new SpyLogger(); - const logger = spyLogger.forkIntegrationLogger('test-spy'); - const result = ignoreStringPlugins([['remark-toc', {}], fnPlugin], logger); - assert.equal(result.length, 1); - assert.equal(result[0], fnPlugin); - }); - - it('logs warnings for string plugins', () => { - const spyLogger = new SpyLogger(); - const logger = spyLogger.forkIntegrationLogger('test-spy'); - ignoreStringPlugins(['remark-toc', ['rehype-highlight', {}]], logger); - // One warning per string plugin + one summary warning - assert.equal(spyLogger.logs.filter((m) => m.level === 'warn').length, 3); - }); - - it('returns empty array for all string plugins', () => { - const spyLogger = new SpyLogger(); - const logger = spyLogger.forkIntegrationLogger('test-spy'); - const result = ignoreStringPlugins(['remark-toc'], logger); - assert.equal(result.length, 0); - }); - - it('handles array-based function plugins [function, options]', () => { - const fnPlugin = () => {}; - const spyLogger = new SpyLogger(); - const logger = spyLogger.forkIntegrationLogger('test-spy'); - const result = ignoreStringPlugins([[fnPlugin, { option: true }]], logger); - assert.equal(result.length, 1); - assert.equal(spyLogger.logs.filter((m) => m.level === 'warn').length, 0); - }); - }); }); diff --git a/packages/internal-helpers/package.json b/packages/internal-helpers/package.json index 5297becb99fe..b2442bed0303 100644 --- a/packages/internal-helpers/package.json +++ b/packages/internal-helpers/package.json @@ -20,6 +20,7 @@ "./request": "./dist/request.js", "./object": "./dist/object.js", "./markdown": "./dist/markdown.js", + "./mdx": "./dist/mdx.js", "./frontmatter": "./dist/frontmatter.js", "./shiki": "./dist/shiki.js" }, diff --git a/packages/internal-helpers/src/markdown.ts b/packages/internal-helpers/src/markdown.ts index d0e4ae980ebb..23072a7c33fc 100644 --- a/packages/internal-helpers/src/markdown.ts +++ b/packages/internal-helpers/src/markdown.ts @@ -9,7 +9,7 @@ import type { ThemeRegistration, ThemeRegistrationRaw, } from 'shiki'; -import type { PluggableList, Plugin } from 'unified'; +import type { Plugin } from 'unified'; import type { RemotePattern } from './remote.js'; // Processor-agnostic markdown contract types, shared between `astro` and the @@ -51,6 +51,7 @@ export type RehypePlugin = Plugin< >; export type RehypePlugins = (string | [string, any] | RehypePlugin | [RehypePlugin, any])[]; export type RemarkRehype = Record; +export type { PluggableList } from 'unified'; export interface MarkdownHeading { depth: number; @@ -111,7 +112,7 @@ export interface MarkdownRenderResult { * Integrations extend the pipeline by mutating `processor.options.*` directly. */ export interface MarkdownProcessor { - /** Identifier for this processor. Used by integrations to look up built-in MDX support. */ + /** Identifier for this processor, e.g. `'unified'`. Surfaced in errors and warnings. */ readonly name: string; /** Processor-specific options. Always present; pass `{}` for processors that take no options. */ options: TOptions; @@ -119,8 +120,8 @@ export interface MarkdownProcessor { createRenderer(shared: AstroMarkdownOptions): Promise; /** * Create the runtime renderer for `.mdx` files. Optional — when absent, `@astrojs/mdx` - * falls back to its built-in handling for the known `unified` / `satteri` processor names. - * Third-party processors should provide this to enable MDX support. + * throws, since the processor cannot render `.mdx`. The built-in `unified` / `satteri` + * processors implement this; third-party processors should too to enable MDX support. */ createMdxRenderer?(shared: AstroMarkdownOptions, mdx: MdxRendererOptions): Promise; } @@ -128,7 +129,14 @@ export interface MarkdownProcessor { /** Cross-cutting MDX options passed to `createMdxRenderer` regardless of processor. */ export interface MdxRendererOptions { optimize: boolean | { ignoreElementNames?: string[] }; - recmaPlugins: PluggableList; + /** + * Astro's `srcDir`. Pipelines use it to default layout-less MDX pages to UTF-8. + * Optional because `@astrojs/mdx` 7.0.x calls `createMdxRenderer` without it, so a + * pipeline must still render when it is absent. + */ + srcDir?: URL; + /** Whether Vite has sourcemaps enabled; a pipeline may emit a source map when true. */ + sourcemap?: boolean; } /** Runtime renderer for `.mdx` files returned by `createMdxRenderer`. */ diff --git a/packages/internal-helpers/src/mdx.ts b/packages/internal-helpers/src/mdx.ts new file mode 100644 index 000000000000..97af2b4e84d4 --- /dev/null +++ b/packages/internal-helpers/src/mdx.ts @@ -0,0 +1,130 @@ +import { existsSync, statSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { slash } from './path.js'; +import type { AstroMetadata } from './markdown.js'; + +// Helpers shared between `@astrojs/mdx`, the markdown processor packages and `astro`'s own island +// resolution. They live here so the processor packages can own MDX without depending on `astro`. + +// Tag name we rewrite markdown-derived `` elements to. Lowercase + hyphenated +// so MDX routes the tag through the `_components` map. +export const ASTRO_IMAGE_ELEMENT = 'astro-image'; +// Module-level identifier bound to Astro's `Image` component (from `astro:assets`). +// Imported by every compiled MDX file that contains a rewritten image; used as the +// fallback when no `components.img` is provided. +export const ASTRO_IMAGE_IMPORT = '__AstroImage__'; +// Boolean export set on MDX modules that contain rewritten images. Read by +// `vite-plugin-mdx-postprocess` to decide whether to wire up the image component. +export const USES_ASTRO_IMAGE_FLAG = '__usesAstroImage'; + +export function createDefaultAstroMetadata(): AstroMetadata { + return { + hydratedComponents: [], + clientOnlyComponents: [], + serverComponents: [], + scripts: [], + propagation: 'none', + containsHead: false, + pageOptions: {}, + }; +} + +const isWindows = typeof process !== 'undefined' && process.platform === 'win32'; + +/** Re-implementation of Vite's normalizePath that can be used without Vite. */ +function normalizePath(id: string) { + return path.posix.normalize(isWindows ? slash(id) : id); +} + +function resolveJsToTs(filePath: string) { + if (filePath.endsWith('.jsx') && !existsSync(filePath)) { + const tryPath = filePath.slice(0, -4) + '.tsx'; + if (existsSync(tryPath)) { + return tryPath; + } + } + return filePath; +} + +// Match Vite's default `resolve.extensions` order so that when multiple +// candidate files exist, we pick the same module Vite will load. +// https://vite.dev/config/shared-options.html#resolve-extensions +const VITE_DEFAULT_RESOLVE_EXTENSIONS = ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']; + +/** + * Resolve a path that doesn't name a file on disk (e.g. produced by an + * extensionless import like `import { Counter } from './Counter'`) to the file + * Vite would load, by probing Vite's default extension order and directory + * `index` files. Returns the path unchanged when it already exists as a file + * or when no candidate is found. + */ +function resolveExtensionlessPath(filePath: string): string { + const stat = statSync(filePath, { throwIfNoEntry: false }); + if (stat?.isFile()) { + return filePath; + } + for (const ext of VITE_DEFAULT_RESOLVE_EXTENSIONS) { + const tryPath = filePath + ext; + if (existsSync(tryPath)) { + return tryPath; + } + } + // Directory import: resolve to its `index` module, like Vite does. + if (stat?.isDirectory()) { + for (const ext of VITE_DEFAULT_RESOLVE_EXTENSIONS) { + const tryPath = `${filePath}/index${ext}`; + if (existsSync(tryPath)) { + return tryPath; + } + } + } + return filePath; +} + +/** + * Resolve island component specifiers to stable paths for hydration metadata. + * + * Examples: + * - `./components/Button.jsx` from `/app/src/pages/index.astro` + * -> `/app/src/pages/components/Button.tsx` (when `.tsx` exists) + * - `../components/Counter` from `/app/src/pages/index.astro` + * -> `/app/src/components/Counter.tsx` (extensionless imports probe Vite's + * default extension order, then directory `index` files) + * - `#components/react/Counter.tsx` + * -> `/app/src/components/react/Counter.tsx` via package `imports` + */ +export function resolvePath(specifier: string, importer: string) { + if (specifier.startsWith('.')) { + const absoluteSpecifier = path.resolve(path.dirname(importer), specifier); + return resolveExtensionlessPath(resolveJsToTs(normalizePath(absoluteSpecifier))); + } else if (specifier.startsWith('#')) { + // Support Node subpath imports (package.json#imports), so this resolves + // before we hand off to non-runnable dev pipelines. + // + // Without this, unresolved values like `/@id/#components/...` can leak + // into client hydration URLs. + try { + // Primary path: CJS-style resolver rooted at the importer. + const resolved = createRequire(pathToFileURL(importer)).resolve(specifier); + return resolveJsToTs(normalizePath(resolved)); + } catch { + try { + // Fallback: ESM resolver in case environments differ. + const importerURL = pathToFileURL(importer).toString(); + const resolved = import.meta.resolve(specifier, importerURL); + const resolvedUrl = new URL(resolved); + if (resolvedUrl.protocol === 'file:') { + return resolveJsToTs(normalizePath(fileURLToPath(resolvedUrl))); + } + } catch { + // fall through + } + } + // Keep original behavior for unresolved specifiers (e.g. package ids). + return specifier; + } else { + return specifier; + } +} diff --git a/packages/markdown/remark/package.json b/packages/markdown/remark/package.json index a3da1d2d47c1..8f2fdd9dabec 100644 --- a/packages/markdown/remark/package.json +++ b/packages/markdown/remark/package.json @@ -20,6 +20,10 @@ "#import-plugin": { "browser": "./dist/import-plugin-browser.js", "default": "./dist/import-plugin-default.js" + }, + "#mdx-processor": { + "browser": "./dist/mdx/create-processor-browser.js", + "default": "./dist/mdx/create-processor.js" } }, "files": [ @@ -35,8 +39,12 @@ "dependencies": { "@astrojs/internal-helpers": "workspace:*", "@astrojs/prism": "workspace:*", + "@mdx-js/mdx": "^3.1.1", + "acorn": "^8.16.0", + "estree-util-visit": "^2.0.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", + "hast-util-to-html": "^9.0.5", "hast-util-to-text": "^4.0.2", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", @@ -45,6 +53,7 @@ "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", + "source-map": "^0.7.6", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", @@ -58,7 +67,9 @@ "@types/unist": "^3.0.3", "astro-scripts": "workspace:*", "esbuild": "^0.28.0", + "mdast-util-mdx": "^3.0.0", "mdast-util-mdx-expression": "^2.0.1", + "mdast-util-mdx-jsx": "^3.2.0", "shiki": "^4.0.0" }, "publishConfig": { diff --git a/packages/markdown/remark/src/mdx/create-processor-browser.ts b/packages/markdown/remark/src/mdx/create-processor-browser.ts new file mode 100644 index 000000000000..4d0c27299401 --- /dev/null +++ b/packages/markdown/remark/src/mdx/create-processor-browser.ts @@ -0,0 +1,18 @@ +import type { + AstroMarkdownOptions, + MdxRenderer, + MdxRendererOptions, +} from '@astrojs/internal-helpers/markdown'; +import type { UnifiedResolvedOptions } from '../processor.js'; + +// Browser counterpart of `create-processor.ts`. MDX compilation relies on Node-only +// APIs (`@mdx-js/mdx`, `node:fs`, …), so it is unavailable in browser/edge bundles. +export function createUnifiedMdxProcessor( + _shared: AstroMarkdownOptions, + _mdx: MdxRendererOptions, + _options: UnifiedResolvedOptions, +): MdxRenderer { + throw new Error( + 'MDX compilation is not available in the browser build of `@astrojs/markdown-remark`.', + ); +} diff --git a/packages/integrations/mdx/src/plugins.ts b/packages/markdown/remark/src/mdx/create-processor.ts similarity index 52% rename from packages/integrations/mdx/src/plugins.ts rename to packages/markdown/remark/src/mdx/create-processor.ts index 11afdbe504c7..9fe2c48b81ea 100644 --- a/packages/integrations/mdx/src/plugins.ts +++ b/packages/markdown/remark/src/mdx/create-processor.ts @@ -1,32 +1,106 @@ -import { - rehypeHeadingIds, - rehypePrism, - rehypeShiki, - remarkCollectImages, -} from '@astrojs/markdown-remark'; +import type { + AstroMarkdownOptions, + MdxRenderer, + MdxRendererOptions, + RemarkRehype, + ShikiConfig, + Smartypants, + SyntaxHighlightConfig, + SyntaxHighlightConfigType, +} from '@astrojs/internal-helpers/markdown'; import { createProcessor, nodeTypes } from '@mdx-js/mdx'; import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; import remarkSmartypants from 'remark-smartypants'; import { SourceMapGenerator } from 'source-map'; import type { PluggableList } from 'unified'; -import type { ResolvedMdxOptions } from './index.js'; -import { rehypeAnalyzeAstroMetadata } from './rehype-analyze-astro-metadata.js'; +import { VFile } from 'vfile'; +import { rehypeHeadingIds } from '../rehype-collect-headings.js'; +import { rehypePrism } from '../rehype-prism.js'; +import { rehypeShiki } from '../rehype-shiki.js'; +import { remarkCollectImages } from '../remark-collect-images.js'; +import type { UnifiedResolvedOptions } from '../processor.js'; +import { getAstroMetadata, rehypeAnalyzeAstroMetadata } from './rehype-analyze-astro-metadata.js'; import { rehypeApplyFrontmatterExport } from './rehype-apply-frontmatter-export.js'; -import { rehypeInjectHeadingsExport } from './rehype-collect-headings.js'; +import { rehypeInjectHeadingsExport } from './rehype-inject-headings-export.js'; import { rehypeImageToComponent } from './rehype-images-to-component.js'; import rehypeMetaString from './rehype-meta-string.js'; -import { rehypeOptimizeStatic } from './rehype-optimize-static.js'; +import { type OptimizeOptions, rehypeOptimizeStatic } from './rehype-optimize-static.js'; +import { filterStringPlugins } from './utils.js'; // Skip nonessential plugins during performance benchmark runs const isPerformanceBenchmark = Boolean(process.env.ASTRO_PERFORMANCE_BENCHMARK); +/** Fully-resolved inputs the unified MDX pipeline needs to build its processor. */ +interface UnifiedMdxOptions { + syntaxHighlight: SyntaxHighlightConfig | SyntaxHighlightConfigType | false | undefined; + shikiConfig: ShikiConfig; + gfm: boolean; + smartypants: boolean | Smartypants; + remarkPlugins: PluggableList; + rehypePlugins: PluggableList; + remarkRehype: RemarkRehype; + recmaPlugins: PluggableList; + optimize: boolean | OptimizeOptions; +} + interface MdxProcessorExtraOptions { sourcemap: boolean; } +/** + * Build the `MdxRenderer` for the `unified` processor. Called via + * `unified().createMdxRenderer` — `options` are the processor's own remark/rehype + * plugins, `shared` the cross-cutting markdown options, `mdx` the MDX-only inputs. + */ +export function createUnifiedMdxProcessor( + shared: AstroMarkdownOptions, + mdx: MdxRendererOptions, + options: UnifiedResolvedOptions, +): MdxRenderer { + const mdxOptions: UnifiedMdxOptions = { + syntaxHighlight: shared.syntaxHighlight, + shikiConfig: shared.shikiConfig ?? {}, + // `shared` carries the more specific `mdx({ gfm })`, so it outranks the processor's own. + gfm: shared.gfm ?? options.gfm ?? true, + smartypants: shared.smartypants ?? options.smartypants ?? true, + remarkPlugins: filterStringPlugins(options.remarkPlugins), + rehypePlugins: filterStringPlugins(options.rehypePlugins), + remarkRehype: options.remarkRehype, + recmaPlugins: options.recmaPlugins, + optimize: mdx.optimize, + }; + + const processor = createMdxProcessor(mdxOptions, { sourcemap: mdx.sourcemap ?? false }); + + return { + async process(content, filePath, frontmatter) { + const vfile = new VFile({ + value: content, + path: filePath, + data: { + astro: { frontmatter }, + applyFrontmatterExport: { srcDir: mdx.srcDir }, + }, + }); + const compiled = await processor.process(vfile); + const astroMetadata = getAstroMetadata(vfile); + if (!astroMetadata) { + throw new Error( + 'Internal MDX error: Astro metadata is not set by rehype-analyze-astro-metadata', + ); + } + return { + code: String(compiled.value), + map: compiled.map ? JSON.stringify(compiled.map) : null, + astroMetadata, + }; + }, + }; +} + export function createMdxProcessor( - mdxOptions: ResolvedMdxOptions, + mdxOptions: UnifiedMdxOptions, extraOptions: MdxProcessorExtraOptions, ) { return createProcessor({ @@ -43,7 +117,7 @@ export function createMdxProcessor( }); } -function getRemarkPlugins(mdxOptions: ResolvedMdxOptions): PluggableList { +function getRemarkPlugins(mdxOptions: UnifiedMdxOptions): PluggableList { let remarkPlugins: PluggableList = []; if (!isPerformanceBenchmark) { @@ -62,7 +136,7 @@ function getRemarkPlugins(mdxOptions: ResolvedMdxOptions): PluggableList { return remarkPlugins; } -function getRehypePlugins(mdxOptions: ResolvedMdxOptions): PluggableList { +function getRehypePlugins(mdxOptions: UnifiedMdxOptions): PluggableList { let rehypePlugins: PluggableList = [ // ensure `data.meta` is preserved in `properties.metastring` for rehype syntax highlighters rehypeMetaString, diff --git a/packages/integrations/mdx/src/rehype-analyze-astro-metadata.ts b/packages/markdown/remark/src/mdx/rehype-analyze-astro-metadata.ts similarity index 96% rename from packages/integrations/mdx/src/rehype-analyze-astro-metadata.ts rename to packages/markdown/remark/src/mdx/rehype-analyze-astro-metadata.ts index 4985423e3099..5cc30fa71e5d 100644 --- a/packages/integrations/mdx/src/rehype-analyze-astro-metadata.ts +++ b/packages/markdown/remark/src/mdx/rehype-analyze-astro-metadata.ts @@ -1,6 +1,5 @@ import type { AstroMetadata, RehypePlugin } from '@astrojs/internal-helpers/markdown'; -import { AstroError, AstroErrorData } from 'astro/errors'; -import { resolvePath } from 'astro/markdown'; +import { resolvePath } from '@astrojs/internal-helpers/mdx'; import type { Program } from 'estree'; import type { RootContent } from 'hast'; import type {} from 'mdast-util-mdx'; @@ -46,10 +45,12 @@ export const rehypeAnalyzeAstroMetadata: RehypePlugin = () => { // Match this component with its import source const matchedImport = findMatchingImport(tagName, imports); if (!matchedImport) { - throw new AstroError( - AstroErrorData.NoMatchingImport.message(node.name!), - AstroErrorData.NoMatchingImport.hint, + // Astro's dev overlay reads `hint` off the thrown error and renders it separately. + const error: Error & { hint?: string } = new Error( + `Could not render \`${node.name}\`. No matching import has been found for \`${node.name}\`.`, ); + error.hint = 'Please make sure the component is properly imported.'; + throw error; } // If this is an Astro component, that means the `client:` directive is misused as it doesn't diff --git a/packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts b/packages/markdown/remark/src/mdx/rehype-apply-frontmatter-export.ts similarity index 100% rename from packages/integrations/mdx/src/rehype-apply-frontmatter-export.ts rename to packages/markdown/remark/src/mdx/rehype-apply-frontmatter-export.ts diff --git a/packages/integrations/mdx/src/rehype-images-to-component.ts b/packages/markdown/remark/src/mdx/rehype-images-to-component.ts similarity index 97% rename from packages/integrations/mdx/src/rehype-images-to-component.ts rename to packages/markdown/remark/src/mdx/rehype-images-to-component.ts index ad287422c517..c82d9a691251 100644 --- a/packages/integrations/mdx/src/rehype-images-to-component.ts +++ b/packages/markdown/remark/src/mdx/rehype-images-to-component.ts @@ -7,11 +7,9 @@ import { ASTRO_IMAGE_ELEMENT, ASTRO_IMAGE_IMPORT, USES_ASTRO_IMAGE_FLAG, -} from './image-constants.js'; +} from '@astrojs/internal-helpers/mdx'; import { jsToTreeNode } from './utils.js'; -export { ASTRO_IMAGE_ELEMENT, ASTRO_IMAGE_IMPORT, USES_ASTRO_IMAGE_FLAG }; - function createArrayAttribute(name: string, values: (string | number)[]): MdxJsxAttribute { return { type: 'mdxJsxAttribute', diff --git a/packages/integrations/mdx/src/rehype-collect-headings.ts b/packages/markdown/remark/src/mdx/rehype-inject-headings-export.ts similarity index 100% rename from packages/integrations/mdx/src/rehype-collect-headings.ts rename to packages/markdown/remark/src/mdx/rehype-inject-headings-export.ts diff --git a/packages/integrations/mdx/src/rehype-meta-string.ts b/packages/markdown/remark/src/mdx/rehype-meta-string.ts similarity index 100% rename from packages/integrations/mdx/src/rehype-meta-string.ts rename to packages/markdown/remark/src/mdx/rehype-meta-string.ts diff --git a/packages/integrations/mdx/src/rehype-optimize-static.ts b/packages/markdown/remark/src/mdx/rehype-optimize-static.ts similarity index 100% rename from packages/integrations/mdx/src/rehype-optimize-static.ts rename to packages/markdown/remark/src/mdx/rehype-optimize-static.ts diff --git a/packages/markdown/remark/src/mdx/utils.ts b/packages/markdown/remark/src/mdx/utils.ts new file mode 100644 index 000000000000..34019102bd3b --- /dev/null +++ b/packages/markdown/remark/src/mdx/utils.ts @@ -0,0 +1,54 @@ +import type { RehypePlugins, RemarkPlugins } from '@astrojs/internal-helpers/markdown'; +import type { Options as AcornOpts } from 'acorn'; +import { parse } from 'acorn'; +import type { MdxjsEsm } from 'mdast-util-mdx'; +import type { Pluggable, PluggableList } from 'unified'; + +export function jsToTreeNode( + jsString: string, + acornOpts: AcornOpts = { + ecmaVersion: 'latest', + sourceType: 'module', + }, +): MdxjsEsm { + return { + type: 'mdxjsEsm', + value: '', + data: { + // @ts-expect-error `parse` return types is incompatible but it should work in runtime + estree: { + ...parse(jsString, acornOpts), + type: 'Program', + sourceType: 'module', + }, + }, + }; +} + +/** + * The MDX compiler cannot resolve string-form plugins the way the `.md` pipeline can, + * so drop them (with a warning) instead of letting the compiler throw. + */ +export function filterStringPlugins( + plugins: RemarkPlugins | RehypePlugins | PluggableList, +): PluggableList { + const validPlugins: PluggableList = []; + let hasInvalidPlugin = false; + for (const plugin of plugins) { + if (typeof plugin === 'string') { + console.warn(`[@astrojs/mdx] \`${plugin}\` not applied.`); + hasInvalidPlugin = true; + } else if (Array.isArray(plugin) && typeof plugin[0] === 'string') { + console.warn(`[@astrojs/mdx] \`${plugin[0]}\` not applied.`); + hasInvalidPlugin = true; + } else { + validPlugins.push(plugin as Pluggable); + } + } + if (hasInvalidPlugin) { + console.warn( + `[@astrojs/mdx] To inherit Markdown plugins in MDX, use explicit imports in your config instead of "strings." See https://docs.astro.build/en/guides/markdown-content/#markdown-processor-plugins`, + ); + } + return validPlugins; +} diff --git a/packages/markdown/remark/src/processor.ts b/packages/markdown/remark/src/processor.ts index 19772a60c449..7063ed6fe73f 100644 --- a/packages/markdown/remark/src/processor.ts +++ b/packages/markdown/remark/src/processor.ts @@ -1,5 +1,7 @@ +import { createUnifiedMdxProcessor } from '#mdx-processor'; import type { MarkdownProcessor, + PluggableList, RehypePlugins, RemarkPlugins, RemarkRehype, @@ -10,6 +12,8 @@ export interface UnifiedProcessorOptions { remarkPlugins?: RemarkPlugins; rehypePlugins?: RehypePlugins; remarkRehype?: RemarkRehype; + /** recma (estree/JSX) plugins for the MDX compiler. Only affect `.mdx` files when using the `@astrojs/mdx` integration */ + recmaPlugins?: PluggableList; /** Enable GitHub-Flavored Markdown. Defaults to `true`. */ gfm?: boolean; /** Enable SmartyPants typography. Defaults to `true`; pass an object to configure it. */ @@ -24,6 +28,7 @@ export interface UnifiedResolvedOptions { remarkPlugins: RemarkPlugins; rehypePlugins: RehypePlugins; remarkRehype: RemarkRehype; + recmaPlugins: PluggableList; gfm?: boolean; smartypants?: boolean | Smartypants; } @@ -52,6 +57,7 @@ export function unified( remarkPlugins: [...(opts.remarkPlugins ?? [])], rehypePlugins: [...(opts.rehypePlugins ?? [])], remarkRehype: { ...opts.remarkRehype }, + recmaPlugins: [...(opts.recmaPlugins ?? [])], gfm: opts.gfm, smartypants: opts.smartypants, }, @@ -69,6 +75,9 @@ export function unified( smartypants: processor.options.smartypants ?? shared.smartypants, }); }, + async createMdxRenderer(shared, mdx) { + return createUnifiedMdxProcessor(shared, mdx, processor.options); + }, }; return processor; } diff --git a/packages/markdown/remark/src/rehype-collect-headings.ts b/packages/markdown/remark/src/rehype-collect-headings.ts index 4e65c44f581e..81441a94fdd2 100644 --- a/packages/markdown/remark/src/rehype-collect-headings.ts +++ b/packages/markdown/remark/src/rehype-collect-headings.ts @@ -40,8 +40,8 @@ export function rehypeHeadingIds(): ReturnType { return; } } - if (rawNodeTypes.has(child.type)) { - if (isMDX || codeTagNames.has(parent.tagName)) { + if (rawNodeTypes.has(child.type) && 'value' in child) { + if (isMDX || ('tagName' in parent && codeTagNames.has(parent.tagName))) { let value = child.value; if (isMdxTextExpression(child) && frontmatter) { const frontmatterPath = getMdxFrontmatterVariablePath(child); diff --git a/packages/integrations/mdx/test/units/mdx-compilation.test.ts b/packages/markdown/remark/test/mdx-compilation.test.ts similarity index 92% rename from packages/integrations/mdx/test/units/mdx-compilation.test.ts rename to packages/markdown/remark/test/mdx-compilation.test.ts index 0117e6fcc03c..90f1f80fc709 100644 --- a/packages/integrations/mdx/test/units/mdx-compilation.test.ts +++ b/packages/markdown/remark/test/mdx-compilation.test.ts @@ -1,19 +1,22 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; -import { rehypeHeadingIds } from '@astrojs/markdown-remark'; import { compile as _compile, type CompileOptions, nodeTypes } from '@mdx-js/mdx'; +import type * as estree from 'estree'; import { visit as estreeVisit } from 'estree-util-visit'; import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; import remarkSmartypants from 'remark-smartypants'; +import type * as unified from 'unified'; import { visit } from 'unist-util-visit'; -import { ignoreStringPlugins } from '../../dist/utils.js'; import { - SpyLogger, - type RecmaPlugin, + rehypeHeadingIds, type RehypePlugin, type RemarkPlugin, -} from '../test-utils.ts'; + type RemarkPlugins, +} from '../dist/index.js'; +import { filterStringPlugins } from '../dist/mdx/utils.js'; + +type RecmaPlugin = unified.Plugin; /** * Compile MDX to JSX string output for inspection. @@ -259,15 +262,11 @@ describe('MDX heading IDs', () => { }); describe('MDX string-based plugin filtering', () => { - it('does not apply string-based remark plugins', async () => { - // When a string-based plugin is provided, the ignoreStringPlugins - // function filters it out. We test the filter function directly in utils.test.js. - // Here we verify that only function plugins affect output. - const spyLogger = new SpyLogger(); - const logger = spyLogger.forkIntegrationLogger('test-spy'); - - const plugins = ['remark-toc', () => (tree: unknown) => tree]; - const filtered = ignoreStringPlugins(plugins, logger); + it('does not apply string-based plugins', () => { + // The MDX compiler cannot resolve string-form plugins, so `filterStringPlugins` + // drops them (with a warning) and keeps only function plugins. + const fnPlugin = () => (tree: unknown) => tree; + const filtered = filterStringPlugins(['remark-toc', fnPlugin] as RemarkPlugins); assert.equal(filtered.length, 1, 'Should filter out string plugin'); assert.equal(typeof filtered[0], 'function', 'Should keep function plugin'); diff --git a/packages/integrations/mdx/test/units/rehype-optimize-static.test.ts b/packages/markdown/remark/test/mdx-rehype-optimize-static.test.ts similarity index 96% rename from packages/integrations/mdx/test/units/rehype-optimize-static.test.ts rename to packages/markdown/remark/test/mdx-rehype-optimize-static.test.ts index f93b8d3f3338..02ec671ab52c 100644 --- a/packages/integrations/mdx/test/units/rehype-optimize-static.test.ts +++ b/packages/markdown/remark/test/mdx-rehype-optimize-static.test.ts @@ -1,7 +1,7 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import { compile as _compile, type CompileOptions } from '@mdx-js/mdx'; -import { rehypeOptimizeStatic } from '../../dist/rehype-optimize-static.js'; +import { rehypeOptimizeStatic } from '../dist/mdx/rehype-optimize-static.js'; async function compile(mdxCode: string, options?: Readonly) { const result = await _compile(mdxCode, { diff --git a/packages/integrations/mdx/test/units/rehype-plugins.test.ts b/packages/markdown/remark/test/mdx-rehype-plugins.test.ts similarity index 96% rename from packages/integrations/mdx/test/units/rehype-plugins.test.ts rename to packages/markdown/remark/test/mdx-rehype-plugins.test.ts index 89e65acaa53b..3d28ad7d4aa9 100644 --- a/packages/integrations/mdx/test/units/rehype-plugins.test.ts +++ b/packages/markdown/remark/test/mdx-rehype-plugins.test.ts @@ -2,8 +2,8 @@ import assert from 'node:assert/strict'; import { describe, it } from 'node:test'; import type * as hast from 'hast'; import { VFile } from 'vfile'; -import { rehypeInjectHeadingsExport } from '../../dist/rehype-collect-headings.js'; -import rehypeMetaString from '../../dist/rehype-meta-string.js'; +import { rehypeInjectHeadingsExport } from '../dist/mdx/rehype-inject-headings-export.js'; +import rehypeMetaString from '../dist/mdx/rehype-meta-string.js'; describe('rehypeMetaString', () => { function createCodeNode(meta: string | undefined): hast.Element { diff --git a/packages/markdown/remark/test/mdx-utils.test.ts b/packages/markdown/remark/test/mdx-utils.test.ts new file mode 100644 index 000000000000..e1a5458cfba8 --- /dev/null +++ b/packages/markdown/remark/test/mdx-utils.test.ts @@ -0,0 +1,93 @@ +import assert from 'node:assert/strict'; +import { afterEach, beforeEach, describe, it } from 'node:test'; +import type { RemarkPlugins } from '../dist/index.js'; +import { filterStringPlugins, jsToTreeNode } from '../dist/mdx/utils.js'; + +describe('mdx utils', () => { + describe('jsToTreeNode', () => { + it('parses a simple export statement', () => { + const node = jsToTreeNode('export const x = 1;'); + const estree = node.data!.estree!; + assert.equal(node.type, 'mdxjsEsm'); + assert.equal(estree.type, 'Program'); + assert.equal(estree.sourceType, 'module'); + assert.ok(estree.body.length > 0); + }); + + it('parses an import statement', () => { + const node = jsToTreeNode("import foo from 'bar';"); + assert.equal(node.type, 'mdxjsEsm'); + assert.equal(node.data!.estree!.body[0].type, 'ImportDeclaration'); + }); + + it('parses a function export', () => { + const node = jsToTreeNode('export function getHeadings() { return []; }'); + assert.equal(node.type, 'mdxjsEsm'); + const decl = node.data!.estree!.body[0]; + assert.equal(decl.type, 'ExportNamedDeclaration'); + }); + + it('throws on invalid JS', () => { + assert.throws(() => jsToTreeNode('this is not valid javascript {{{'), { + name: 'SyntaxError', + }); + }); + }); + + describe('filterStringPlugins', () => { + let warnings: unknown[][]; + const originalWarn = console.warn; + + beforeEach(() => { + warnings = []; + console.warn = (...args: unknown[]) => { + warnings.push(args); + }; + }); + afterEach(() => { + console.warn = originalWarn; + }); + + it('returns function plugins unchanged', () => { + const plugin1 = () => {}; + const plugin2 = () => {}; + const result = filterStringPlugins([plugin1, plugin2] as RemarkPlugins); + assert.equal(result.length, 2); + assert.equal(result[0], plugin1); + assert.equal(result[1], plugin2); + assert.equal(warnings.length, 0); + }); + + it('filters out string-based plugins', () => { + const fnPlugin = () => {}; + const result = filterStringPlugins(['remark-toc', fnPlugin] as RemarkPlugins); + assert.equal(result.length, 1); + assert.equal(result[0], fnPlugin); + }); + + it('filters out array-based string plugins [string, options]', () => { + const fnPlugin = () => {}; + const result = filterStringPlugins([['remark-toc', {}], fnPlugin] as RemarkPlugins); + assert.equal(result.length, 1); + assert.equal(result[0], fnPlugin); + }); + + it('logs warnings for string plugins', () => { + filterStringPlugins(['remark-toc', ['rehype-highlight', {}]] as RemarkPlugins); + // One warning per string plugin + one summary warning + assert.equal(warnings.length, 3); + }); + + it('returns empty array for all string plugins', () => { + const result = filterStringPlugins(['remark-toc'] as RemarkPlugins); + assert.equal(result.length, 0); + }); + + it('handles array-based function plugins [function, options]', () => { + const fnPlugin = () => {}; + const result = filterStringPlugins([[fnPlugin, { option: true }]] as RemarkPlugins); + assert.equal(result.length, 1); + assert.equal(warnings.length, 0); + }); + }); +}); diff --git a/packages/markdown/satteri/package.json b/packages/markdown/satteri/package.json index d81f08cf54ae..10e41f32754f 100644 --- a/packages/markdown/satteri/package.json +++ b/packages/markdown/satteri/package.json @@ -32,6 +32,8 @@ "satteri": "^0.10.3" }, "devDependencies": { + "@types/estree": "^1.0.8", + "@types/hast": "^3.0.4", "astro-scripts": "workspace:*" }, "publishConfig": { diff --git a/packages/integrations/mdx/src/satteri/charset.ts b/packages/markdown/satteri/src/mdx/charset.ts similarity index 89% rename from packages/integrations/mdx/src/satteri/charset.ts rename to packages/markdown/satteri/src/mdx/charset.ts index c36cd70c629e..93d7cd59c79b 100644 --- a/packages/integrations/mdx/src/satteri/charset.ts +++ b/packages/markdown/satteri/src/mdx/charset.ts @@ -11,7 +11,13 @@ const leadingComponentRe = /^\s*<\s*([A-Za-z][A-Za-z0-9]*)\b/; // Scans MDX source directly because Sätteri exposes no root-level visitor. // Skips imports/exports/blank lines and bails when the first content line is a // capitalized JSX element (treated as a wrapping layout). -export function shouldAddCharset(content: string, filePath: string, srcDir: URL): boolean { +export function shouldAddCharset( + content: string, + filePath: string, + srcDir: URL | undefined, +): boolean { + // `@astrojs/mdx` 7.0.x never passes `srcDir`, so the `src/pages` check below cannot run. + if (!srcDir) return false; const srcDirPath = fileURLToPath(srcDir).replace(/\\/g, '/'); const pagesDir = path.posix.join(srcDirPath, 'pages'); const normalizedFilePath = filePath.replace(/\\/g, '/'); diff --git a/packages/integrations/mdx/src/satteri/index.ts b/packages/markdown/satteri/src/mdx/create-processor.ts similarity index 74% rename from packages/integrations/mdx/src/satteri/index.ts rename to packages/markdown/satteri/src/mdx/create-processor.ts index 9de7ff119993..a1401618123d 100644 --- a/packages/integrations/mdx/src/satteri/index.ts +++ b/packages/markdown/satteri/src/mdx/create-processor.ts @@ -1,14 +1,16 @@ import { pathToFileURL } from 'node:url'; import { isFrontmatterValid } from '@astrojs/internal-helpers/frontmatter'; -import type { MarkdownHeading } from '@astrojs/internal-helpers/markdown'; -import type { SatteriAstroData, SatteriResolvedOptions } from '@astrojs/markdown-satteri'; +import type { + AstroMarkdownOptions, + MarkdownHeading, + MdxRendererOptions, + MdxRenderResult, +} from '@astrojs/internal-helpers/markdown'; import { - satteriCollectImagesPlugin, - satteriCreateHighlightFn, - satteriHeadingIdsPlugin, - satteriHighlightPlugin, -} from '@astrojs/markdown-satteri'; -import { createDefaultAstroMetadata } from 'astro/markdown'; + ASTRO_IMAGE_IMPORT, + createDefaultAstroMetadata, + USES_ASTRO_IMAGE_FLAG, +} from '@astrojs/internal-helpers/mdx'; import { mdxToJs, type HastNode, @@ -18,10 +20,16 @@ import { type MdastPluginEntry, type MdxCompileOptions, } from 'satteri'; -import { ASTRO_IMAGE_IMPORT, USES_ASTRO_IMAGE_FLAG } from '../image-constants.js'; -import type { ResolvedMdxOptions } from '../index.js'; +import type { SatteriResolvedOptions } from '../processor.js'; +import { + createCollectImagesPlugin, + createHeadingIdsPlugin, + createHighlightFn, + createHighlightPlugin, + type SatteriAstroData, +} from '../satteri-processor.js'; import { shouldAddCharset } from './charset.js'; -import { type AstroMetadata, createAstroMetadataPlugin } from './hast-astro-metadata.js'; +import { createAstroMetadataPlugin } from './hast-astro-metadata.js'; import { createImageToComponentPlugin, type ImageImportInfo } from './hast-images-to-component.js'; type HighlightFn = (code: string, lang: string, meta?: string) => Promise; @@ -35,29 +43,18 @@ declare module 'hast' { } } -export interface CompileMdxResult { - code: string; - astroMetadata: AstroMetadata; -} - -interface CreateMdxProcessorContext { - srcDir: URL; -} - -export function createMdxProcessor( - mdxOptions: ResolvedMdxOptions, +export function createSatteriMdxProcessor( + shared: AstroMarkdownOptions, + mdx: MdxRendererOptions, satteriOptions: SatteriResolvedOptions, - ctx: CreateMdxProcessorContext, ) { let highlightFn: HighlightFn | undefined; let initPromise: Promise | undefined; function initHighlighter() { - initPromise = satteriCreateHighlightFn(mdxOptions.syntaxHighlight, mdxOptions.shikiConfig).then( - (fn) => { - highlightFn = fn; - }, - ); + initPromise = createHighlightFn(shared.syntaxHighlight, shared.shikiConfig).then((fn) => { + highlightFn = fn; + }); } return { @@ -65,7 +62,7 @@ export function createMdxProcessor( content: string, filePath: string, frontmatter: Record, - ): Promise { + ): Promise { if (!highlightFn && !initPromise) { initHighlighter(); } @@ -78,8 +75,8 @@ export function createMdxProcessor( remoteImagePaths: new Set(), }; - const collectImages = satteriCollectImagesPlugin(); - const headingIds = satteriHeadingIdsPlugin(); + const collectImages = createCollectImagesPlugin(); + const headingIds = createHeadingIdsPlugin(); const astroMeta = createAstroMetadataPlugin(filePath); const imageImportInfo: ImageImportInfo = { importedImages: new Map(), @@ -87,7 +84,7 @@ export function createMdxProcessor( }; const imageToComponent = createImageToComponentPlugin(imageImportInfo); - const syntaxHighlight = mdxOptions.syntaxHighlight; + const syntaxHighlight = shared.syntaxHighlight; const excludeLangs = typeof syntaxHighlight === 'object' ? syntaxHighlight.excludeLangs : undefined; @@ -96,7 +93,7 @@ export function createMdxProcessor( const hastPlugins: HastPluginEntry[] = []; if (highlightFn) { - hastPlugins.push(satteriHighlightPlugin(highlightFn, excludeLangs)); + hastPlugins.push(createHighlightPlugin(highlightFn, excludeLangs)); } if (satteriOptions.hastPlugins.length) { hastPlugins.push(...satteriOptions.hastPlugins); @@ -104,11 +101,9 @@ export function createMdxProcessor( hastPlugins.push(imageToComponent, headingIds, astroMeta); let optimizeStatic: MdxCompileOptions['optimizeStatic']; - if (mdxOptions.optimize) { + if (mdx.optimize) { const ignoreElements = - typeof mdxOptions.optimize === 'object' - ? mdxOptions.optimize.ignoreElementNames - : undefined; + typeof mdx.optimize === 'object' ? mdx.optimize.ignoreElementNames : undefined; optimizeStatic = { component: 'Fragment', @@ -117,20 +112,20 @@ export function createMdxProcessor( }; } + const { gfm, smartPunctuation } = satteriOptions.features; + const mdxResult = await mdxToJs(content, { mdastPlugins: allMdastPlugins, hastPlugins, optimizeStatic, + // `shared` carries the more specific `mdx({ gfm })` and wins, but only over booleans. features: { ...satteriOptions.features, - // `mdxOptions.gfm`/`smartypants` are always boolean-shaped; skip the override when - // satteri's feature is an object so granular config isn't clobbered. - ...(typeof satteriOptions.features.gfm === 'object' - ? {} - : { gfm: mdxOptions.gfm !== false }), - ...(typeof satteriOptions.features.smartPunctuation === 'object' - ? {} - : { smartPunctuation: mdxOptions.smartypants !== false }), + gfm: typeof gfm === 'object' ? gfm : (shared.gfm ?? gfm ?? true) !== false, + smartPunctuation: + typeof smartPunctuation === 'object' + ? smartPunctuation + : (shared.smartypants ?? smartPunctuation ?? true) !== false, }, fileURL: pathToFileURL(filePath), jsxImportSource: 'astro', @@ -188,7 +183,7 @@ export default function MDXContent(props) { children: content, }); }`; - } else if (shouldAddCharset(content, filePath, ctx.srcDir)) { + } else if (shouldAddCharset(content, filePath, mdx.srcDir)) { // Default MDX pages without a layout to UTF-8 so users don't have to think about it. compiled = compiled.replace(/^function MDXContent\(/m, 'function __OriginalMDXContent__('); compiled += ` @@ -205,6 +200,7 @@ export default function MDXContent(props) { return { code: compiled, + map: null, astroMetadata, }; }, diff --git a/packages/integrations/mdx/src/satteri/hast-astro-metadata.ts b/packages/markdown/satteri/src/mdx/hast-astro-metadata.ts similarity index 98% rename from packages/integrations/mdx/src/satteri/hast-astro-metadata.ts rename to packages/markdown/satteri/src/mdx/hast-astro-metadata.ts index c9ee8c627207..b1113fb9d676 100644 --- a/packages/integrations/mdx/src/satteri/hast-astro-metadata.ts +++ b/packages/markdown/satteri/src/mdx/hast-astro-metadata.ts @@ -1,5 +1,5 @@ import type { AstroMetadata } from '@astrojs/internal-helpers/markdown'; -import { createDefaultAstroMetadata, resolvePath } from 'astro/markdown'; +import { createDefaultAstroMetadata, resolvePath } from '@astrojs/internal-helpers/mdx'; import type { Identifier, Literal } from 'estree'; import { defineHastPlugin, diff --git a/packages/integrations/mdx/src/satteri/hast-images-to-component.ts b/packages/markdown/satteri/src/mdx/hast-images-to-component.ts similarity index 97% rename from packages/integrations/mdx/src/satteri/hast-images-to-component.ts rename to packages/markdown/satteri/src/mdx/hast-images-to-component.ts index a7e7857a088a..b7073db0a19e 100644 --- a/packages/integrations/mdx/src/satteri/hast-images-to-component.ts +++ b/packages/markdown/satteri/src/mdx/hast-images-to-component.ts @@ -4,7 +4,7 @@ import { type HastPluginDefinition, type MdxJsxAttributeNode, } from 'satteri'; -import { ASTRO_IMAGE_ELEMENT } from '../image-constants.js'; +import { ASTRO_IMAGE_ELEMENT } from '@astrojs/internal-helpers/mdx'; import { makeJsxAttr, makeJsxExprAttr } from './jsx-utils.js'; export interface ImageImportInfo { diff --git a/packages/integrations/mdx/src/satteri/jsx-utils.ts b/packages/markdown/satteri/src/mdx/jsx-utils.ts similarity index 100% rename from packages/integrations/mdx/src/satteri/jsx-utils.ts rename to packages/markdown/satteri/src/mdx/jsx-utils.ts diff --git a/packages/markdown/satteri/src/processor.ts b/packages/markdown/satteri/src/processor.ts index e208907d6341..0221ab26bdad 100644 --- a/packages/markdown/satteri/src/processor.ts +++ b/packages/markdown/satteri/src/processor.ts @@ -6,6 +6,7 @@ import type { MdastPluginEntry, MdastPluginList, } from 'satteri'; +import { createSatteriMdxProcessor } from './mdx/create-processor.js'; import { createSatteriMarkdownProcessor } from './satteri-processor.js'; export interface SatteriFeatures extends Omit { @@ -67,6 +68,9 @@ export function satteri( features: processor.options.features, }); }, + async createMdxRenderer(shared, mdx) { + return createSatteriMdxProcessor(shared, mdx, processor.options); + }, }; return processor; } diff --git a/packages/markdown/satteri/test/mdx-renderer-options.test.ts b/packages/markdown/satteri/test/mdx-renderer-options.test.ts new file mode 100644 index 000000000000..5b5829c63738 --- /dev/null +++ b/packages/markdown/satteri/test/mdx-renderer-options.test.ts @@ -0,0 +1,33 @@ +import assert from 'node:assert/strict'; +import path from 'node:path'; +import { describe, it } from 'node:test'; +import { pathToFileURL } from 'node:url'; +import { satteri } from '../dist/index.js'; + +const SHARED = { syntaxHighlight: false, shikiConfig: {} } as const; + +// A hardcoded `file:///project/src/` has no drive letter, which Windows rejects. +const SRC_DIR_PATH = path.resolve('project', 'src'); +const SRC_DIR = pathToFileURL(SRC_DIR_PATH + path.sep); +const PAGE_PATH = path.join(SRC_DIR_PATH, 'pages', 'index.mdx'); + +describe('satteri createMdxRenderer', () => { + // Upgrading astro alone reaches this: `@astrojs/mdx` 7.0.x calls it without `srcDir`. + it('renders when the caller omits `srcDir`', async () => { + const renderer = await satteri().createMdxRenderer!(SHARED, { optimize: false } as any); + const { code } = await renderer.process('# Hello\n\nSome text.\n', PAGE_PATH, {}); + + assert.match(code, /MDXContent/); + assert.doesNotMatch(code, /charset/); + }); + + it('injects the charset for a layout-less page when `srcDir` is passed', async () => { + const renderer = await satteri().createMdxRenderer!(SHARED, { + optimize: false, + srcDir: SRC_DIR, + }); + const { code } = await renderer.process('# Hello\n\nSome text.\n', PAGE_PATH, {}); + + assert.match(code, /charset/); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79867e882d09..922b33f216b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5354,58 +5354,22 @@ importers: '@astrojs/internal-helpers': specifier: workspace:* version: link:../../internal-helpers - '@astrojs/markdown-remark': + '@astrojs/markdown-satteri': specifier: workspace:* - version: link:../../markdown/remark - '@mdx-js/mdx': - specifier: ^3.1.1 - version: 3.1.1 - acorn: - specifier: ^8.16.0 - version: 8.17.0 + version: link:../../markdown/satteri es-module-lexer: specifier: ^2.0.0 version: 2.0.0 - estree-util-visit: - specifier: ^2.0.0 - version: 2.0.0 - hast-util-to-html: - specifier: ^9.0.5 - version: 9.0.5 - piccolore: - specifier: ^0.1.3 - version: 0.1.3 - rehype-raw: - specifier: ^7.0.0 - version: 7.0.0 - remark-gfm: - specifier: ^4.0.1 - version: 4.0.1 - remark-smartypants: - specifier: ^3.0.2 - version: 3.0.2 - source-map: - specifier: ^0.7.6 - version: 0.7.6 - unist-util-visit: - specifier: ^5.1.0 - version: 5.1.0 - vfile: - specifier: ^6.0.3 - version: 6.0.3 devDependencies: - '@astrojs/markdown-satteri': + '@astrojs/markdown-remark': specifier: workspace:* - version: link:../../markdown/satteri + version: link:../../markdown/remark '@shikijs/rehype': specifier: ^4.0.2 version: 4.0.2 '@shikijs/twoslash': specifier: ^4.0.2 version: 4.0.2(supports-color@8.1.1)(typescript@6.0.3) - '@types/estree': - specifier: ^1.0.8 - version: 1.0.8 '@types/hast': specifier: ^3.0.4 version: 3.0.5 @@ -5424,12 +5388,6 @@ importers: linkedom: specifier: ^0.18.12 version: 0.18.13 - mdast-util-mdx: - specifier: ^3.0.0 - version: 3.0.0 - mdast-util-mdx-jsx: - specifier: ^3.2.0 - version: 3.2.0 rehype-mathjax: specifier: ^7.1.0 version: 7.1.0 @@ -5439,9 +5397,6 @@ importers: remark-math: specifier: ^6.0.0 version: 6.0.0 - remark-rehype: - specifier: ^11.1.2 - version: 11.1.2 remark-toc: specifier: ^9.0.0 version: 9.0.0 @@ -5454,6 +5409,9 @@ importers: unified: specifier: ^11.0.5 version: 11.0.5 + unist-util-visit: + specifier: ^5.1.0 + version: 5.1.0 vite: specifier: ^8.0.13 version: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0) @@ -7017,12 +6975,24 @@ importers: '@astrojs/prism': specifier: workspace:* version: link:../../astro-prism + '@mdx-js/mdx': + specifier: ^3.1.1 + version: 3.1.1 + acorn: + specifier: ^8.16.0 + version: 8.17.0 + estree-util-visit: + specifier: ^2.0.0 + version: 2.0.0 github-slugger: specifier: ^2.0.0 version: 2.0.0 hast-util-from-html: specifier: ^2.0.3 version: 2.0.3 + hast-util-to-html: + specifier: ^9.0.5 + version: 9.0.5 hast-util-to-text: specifier: ^4.0.2 version: 4.0.2 @@ -7047,6 +7017,9 @@ importers: remark-smartypants: specifier: ^3.0.2 version: 3.0.2 + source-map: + specifier: ^0.7.6 + version: 0.7.6 unified: specifier: ^11.0.5 version: 11.0.5 @@ -7081,9 +7054,15 @@ importers: esbuild: specifier: ^0.28.0 version: 0.28.1 + mdast-util-mdx: + specifier: ^3.0.0 + version: 3.0.0 mdast-util-mdx-expression: specifier: ^2.0.1 version: 2.0.1 + mdast-util-mdx-jsx: + specifier: ^3.2.0 + version: 3.2.0 shiki: specifier: ^4.0.0 version: 4.0.2 @@ -7103,6 +7082,12 @@ importers: specifier: ^0.10.3 version: 0.10.3 devDependencies: + '@types/estree': + specifier: ^1.0.8 + version: 1.0.8 + '@types/hast': + specifier: ^3.0.4 + version: 3.0.5 astro-scripts: specifier: workspace:* version: link:../../../scripts From 19dae210d42bd22662ee678c173676573b6168f2 Mon Sep 17 00:00:00 2001 From: ocavue Date: Sun, 30 Aug 2026 15:19:47 +1200 Subject: [PATCH 10/19] fix(vercel): update vercel dependencies (#17450) --- .changeset/vercel-deps.md | 6 + packages/integrations/vercel/package.json | 8 +- pnpm-lock.yaml | 170 +++++++++++++++++----- pnpm-workspace.yaml | 8 + 4 files changed, 151 insertions(+), 41 deletions(-) create mode 100644 .changeset/vercel-deps.md diff --git a/.changeset/vercel-deps.md b/.changeset/vercel-deps.md new file mode 100644 index 000000000000..b55d23b81794 --- /dev/null +++ b/.changeset/vercel-deps.md @@ -0,0 +1,6 @@ +--- +"@astrojs/vercel": patch +--- + +Updates dependency `@vercel/analytics` to v2. See the [changelog](https://github.com/vercel/analytics/releases/tag/v2.0.0) for more details. +Updates dependency `@vercel/routing-utils` to v6. See the [changelog](https://github.com/vercel/vercel/blob/@vercel/routing-utils@6.4.0/packages/routing-utils/CHANGELOG.md#600) for more details. diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json index 9dafb44c76c0..1524f8965c0a 100644 --- a/packages/integrations/vercel/package.json +++ b/packages/integrations/vercel/package.json @@ -39,10 +39,10 @@ }, "dependencies": { "@astrojs/internal-helpers": "workspace:*", - "@vercel/analytics": "^1.6.1", - "@vercel/functions": "^3.4.3", - "@vercel/nft": "^1.3.2", - "@vercel/routing-utils": "^5.3.3", + "@vercel/analytics": "^2.0.1", + "@vercel/functions": "^3.7.5", + "@vercel/nft": "^1.10.2", + "@vercel/routing-utils": "^6.4.0", "esbuild": "^0.28.0", "tinyglobby": "^0.2.15" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 922b33f216b5..1c653cc928d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -869,7 +869,7 @@ importers: version: 0.7.5 unstorage: specifier: ^1.17.5 - version: 1.17.5(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.4.3) + version: 1.17.5(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.9.5) vite: specifier: ^8.0.13 version: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0) @@ -5615,10 +5615,10 @@ importers: version: 5.2.0 '@netlify/vite-plugin': specifier: ^2.12.3 - version: 2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(supports-color@8.1.1)(vite@8.2.1) + version: 2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.9.5)(supports-color@8.1.1)(vite@8.2.1) '@vercel/nft': specifier: ^1.3.2 - version: 1.3.2 + version: 1.11.0 esbuild: specifier: ^0.28.0 version: 0.28.1 @@ -6274,17 +6274,17 @@ importers: specifier: workspace:* version: link:../../internal-helpers '@vercel/analytics': - specifier: ^1.6.1 - version: 1.6.1(react@19.2.4)(svelte@5.55.3)(vue@3.5.30) + specifier: ^2.0.1 + version: 2.0.1(react@19.2.4)(svelte@5.55.3)(vue@3.5.30) '@vercel/functions': - specifier: ^3.4.3 - version: 3.4.3(@aws-sdk/credential-provider-web-identity@3.972.49) + specifier: ^3.7.5 + version: 3.9.5(@aws-sdk/credential-provider-web-identity@3.972.49)(ws@8.21.0) '@vercel/nft': - specifier: ^1.3.2 - version: 1.3.2 + specifier: ^1.10.2 + version: 1.11.0 '@vercel/routing-utils': - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^6.4.0 + version: 6.5.0 esbuild: specifier: ^0.28.0 version: 0.28.1 @@ -10857,12 +10857,13 @@ packages: peerDependencies: valibot: ^1.2.0 - '@vercel/analytics@1.6.1': - resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==} + '@vercel/analytics@2.0.1': + resolution: {integrity: sha512-MTQG6V9qQrt1tsDeF+2Uoo5aPjqbVPys1xvnIftXSJYG2SrwXRHnqEvVoYID7BTruDz4lCd2Z7rM1BdkUehk2g==} peerDependencies: '@remix-run/react': ^2 '@sveltejs/kit': ^1 || ^2 next: '>= 13' + nuxt: '>= 3' react: ^18 || ^19 || ^19.0.0-rc svelte: '>= 4' vue: ^3 @@ -10874,6 +10875,8 @@ packages: optional: true next: optional: true + nuxt: + optional: true react: optional: true svelte: @@ -10883,31 +10886,41 @@ packages: vue-router: optional: true - '@vercel/functions@3.4.3': - resolution: {integrity: sha512-kA14KIUVgAY6VXbhZ5jjY+s0883cV3cZqIU3WhrSRxuJ9KvxatMjtmzl0K23HK59oOUjYl7HaE/eYMmhmqpZzw==} + '@vercel/cli-config@0.2.4': + resolution: {integrity: sha512-kZ5SojbrV06GHoU6QIWGwDXLov+s9rWZ7QqdqKfJfBGCNUieGfgaCjeeenNy8Y+QC0bwC0dZ2B4l5Hvdmrgpdw==} + + '@vercel/cli-exec@1.0.1': + resolution: {integrity: sha512-g9XerViJ/paZujufXYcu5XYI2vU2rtB4sgdpjUHde5RnOkdmpu0ngH46LCFGHoPXO/C+qDPSczIHIRN+8Q2YKQ==} + engines: {node: '>= 18'} + + '@vercel/functions@3.9.5': + resolution: {integrity: sha512-EUfqlb7AzoEh7URlMNAO4jbJiLWz9grDBHvfjKTDvEP9c8y3DqX3SWPvfaQkUjtkm3b83flhaUUMuewdHa+qmw==} engines: {node: '>= 20'} peerDependencies: '@aws-sdk/credential-provider-web-identity': '*' + ws: '>=8' peerDependenciesMeta: '@aws-sdk/credential-provider-web-identity': optional: true + ws: + optional: true '@vercel/nft@0.29.4': resolution: {integrity: sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==} engines: {node: '>=18'} hasBin: true - '@vercel/nft@1.3.2': - resolution: {integrity: sha512-HC8venRc4Ya7vNeBsJneKHHMDDWpQie7VaKhAIOst3MKO+DES+Y/SbzSp8mFkD7OzwAE2HhHkeSuSmwS20mz3A==} + '@vercel/nft@1.11.0': + resolution: {integrity: sha512-m1QFg+U+3yPOnP1xSYJ73UIRxLOXdts1JOhiOiyPYqEsALgrXFFINvgUaD6R6iNvaBFAjHllBCbkfx4FuOdpaA==} engines: {node: '>=20'} hasBin: true - '@vercel/oidc@3.2.0': - resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} + '@vercel/oidc@3.8.5': + resolution: {integrity: sha512-RwXYtnt6za+5UO4IaLywN/6B95AlLqynPRUWRJxeJ/qufwkcLUbZNUxYtzT0uMpuraWhlNcGqPNGkTnZr4BGBw==} engines: {node: '>= 20'} - '@vercel/routing-utils@5.3.3': - resolution: {integrity: sha512-KYm2sLNUD48gDScv8ob4ejc3Gww2jcJyW80hTdYlenAPz/5BQar1Gyh38xrUuZ532TUwSb5mV1uRbAuiykq0EQ==} + '@vercel/routing-utils@6.5.0': + resolution: {integrity: sha512-2PXgATJyJYEaIuDEhljZ+sfOfJEsBl6OEzD+jHhy6NAb0k2mHdbTjcK3zTBNnYq0nebrXpq5ZGiwOxBFoUwB3A==} '@vitejs/plugin-react@5.2.0': resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} @@ -12477,6 +12490,10 @@ packages: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -12798,6 +12815,10 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -13023,6 +13044,10 @@ packages: resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} hasBin: true + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} @@ -13288,6 +13313,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@5.10.0: + resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} @@ -14227,6 +14255,10 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + npm-run-path@5.3.0: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -14318,6 +14350,10 @@ packages: resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} engines: {node: '>=18'} + os-paths@4.4.0: + resolution: {integrity: sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg==} + engines: {node: '>= 6.0'} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -16478,6 +16514,14 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + xdg-app-paths@5.5.1: + resolution: {integrity: sha512-hI3flOB4PLZIy5prbtTpirobtPE2ZtZ52szO+2mM9Efp6ErM398La+C1lIpNWDfNoQk+6Lsi6nMcCwVB7pxeMQ==} + engines: {node: '>= 6.0'} + + xdg-portable@7.3.0: + resolution: {integrity: sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw==} + engines: {node: '>= 6.0'} + xml-naming@0.1.0: resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} engines: {node: '>=16.0.0'} @@ -16600,6 +16644,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.11: + resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -19023,7 +19070,7 @@ snapshots: uuid: 13.0.0 write-file-atomic: 5.0.1 - '@netlify/dev@4.18.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(supports-color@8.1.1)': + '@netlify/dev@4.18.3(@azure/identity@4.13.0)(@vercel/functions@3.9.5)(supports-color@8.1.1)': dependencies: '@netlify/ai': 0.4.1 '@netlify/blobs': 10.7.5 @@ -19033,7 +19080,7 @@ snapshots: '@netlify/edge-functions-dev': 1.0.17 '@netlify/functions-dev': 1.2.8(supports-color@8.1.1) '@netlify/headers': 2.1.8 - '@netlify/images': 1.3.7(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.4.3) + '@netlify/images': 1.3.7(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.9.5) '@netlify/redirects': 3.1.10 '@netlify/runtime': 4.1.21 '@netlify/static': 3.1.7 @@ -19142,9 +19189,9 @@ snapshots: dependencies: '@netlify/headers-parser': 9.0.3 - '@netlify/images@1.3.7(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.4.3)': + '@netlify/images@1.3.7(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.9.5)': dependencies: - ipx: 3.1.1(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.4.3) + ipx: 3.1.1(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.9.5) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -19214,9 +19261,9 @@ snapshots: '@netlify/types@2.6.0': {} - '@netlify/vite-plugin@2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(supports-color@8.1.1)(vite@8.2.1)': + '@netlify/vite-plugin@2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.9.5)(supports-color@8.1.1)(vite@8.2.1)': dependencies: - '@netlify/dev': 4.18.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(supports-color@8.1.1) + '@netlify/dev': 4.18.3(@azure/identity@4.13.0)(@vercel/functions@3.9.5)(supports-color@8.1.1) '@netlify/dev-utils': 4.4.3 dedent: 1.7.1 vite: 8.2.1(@types/node@22.19.19)(esbuild@0.28.1)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0) @@ -20412,17 +20459,27 @@ snapshots: dependencies: valibot: 1.4.2(typescript@6.0.3) - '@vercel/analytics@1.6.1(react@19.2.4)(svelte@5.55.3)(vue@3.5.30)': + '@vercel/analytics@2.0.1(react@19.2.4)(svelte@5.55.3)(vue@3.5.30)': optionalDependencies: react: 19.2.4 svelte: 5.55.3 vue: 3.5.30(typescript@6.0.3) - '@vercel/functions@3.4.3(@aws-sdk/credential-provider-web-identity@3.972.49)': + '@vercel/cli-config@0.2.4': + dependencies: + xdg-app-paths: 5.5.1 + zod: 4.1.11 + + '@vercel/cli-exec@1.0.1': + dependencies: + execa: 5.1.1 + + '@vercel/functions@3.9.5(@aws-sdk/credential-provider-web-identity@3.972.49)(ws@8.21.0)': dependencies: - '@vercel/oidc': 3.2.0 + '@vercel/oidc': 3.8.5 optionalDependencies: '@aws-sdk/credential-provider-web-identity': 3.972.49 + ws: 8.21.0 '@vercel/nft@0.29.4': dependencies: @@ -20443,7 +20500,7 @@ snapshots: - rollup - supports-color - '@vercel/nft@1.3.2': + '@vercel/nft@1.11.0': dependencies: '@mapbox/node-pre-gyp': 2.0.3 '@rollup/pluginutils': 5.3.0 @@ -20462,9 +20519,13 @@ snapshots: - rollup - supports-color - '@vercel/oidc@3.2.0': {} + '@vercel/oidc@3.8.5': + dependencies: + '@vercel/cli-config': 0.2.4 + '@vercel/cli-exec': 1.0.1 + jose: 5.10.0 - '@vercel/routing-utils@5.3.3': + '@vercel/routing-utils@6.5.0': dependencies: path-to-regexp: 6.1.0 path-to-regexp-updated: path-to-regexp@6.3.0 @@ -22287,6 +22348,18 @@ snapshots: dependencies: eventsource-parser: 3.0.8 + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -22672,6 +22745,8 @@ snapshots: dependencies: pump: 3.0.3 + get-stream@6.0.1: {} + get-stream@8.0.1: {} get-tsconfig@5.0.0-beta.4: @@ -23034,6 +23109,8 @@ snapshots: human-id@4.1.3: {} + human-signals@2.1.0: {} + human-signals@5.0.0: {} hyperid@3.3.0: @@ -23093,7 +23170,7 @@ snapshots: ipaddr.js@2.3.0: {} - ipx@3.1.1(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.4.3): + ipx@3.1.1(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.9.5): dependencies: '@fastify/accept-negotiator': 2.0.1 citty: 0.1.6 @@ -23109,7 +23186,7 @@ snapshots: sharp: 0.34.5 svgo: 4.0.1 ufo: 1.6.3 - unstorage: 1.17.5(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.4.3) + unstorage: 1.17.5(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.9.5) xss: 1.0.15 transitivePeerDependencies: - '@azure/app-configuration' @@ -23268,6 +23345,8 @@ snapshots: jiti@2.6.1: {} + jose@5.10.0: {} + jose@6.2.3: {} jpeg-js@0.4.4: {} @@ -24449,6 +24528,10 @@ snapshots: normalize-path@3.0.0: {} + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + npm-run-path@5.3.0: dependencies: path-key: 4.0.0 @@ -24546,6 +24629,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.2 + os-paths@4.4.0: {} + outdent@0.5.0: {} ovsx@0.10.10(supports-color@8.1.1): @@ -26540,7 +26625,7 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.5 - unstorage@1.17.5(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.4.3): + unstorage@1.17.5(@azure/identity@4.13.0)(@netlify/blobs@10.7.5)(@vercel/functions@3.9.5): dependencies: anymatch: 3.1.3 chokidar: 5.0.0 @@ -26553,7 +26638,7 @@ snapshots: optionalDependencies: '@azure/identity': 4.13.0 '@netlify/blobs': 10.7.5 - '@vercel/functions': 3.4.3(@aws-sdk/credential-provider-web-identity@3.972.49) + '@vercel/functions': 3.9.5(@aws-sdk/credential-provider-web-identity@3.972.49)(ws@8.21.0) untun@0.1.3: dependencies: @@ -27042,6 +27127,15 @@ snapshots: dependencies: is-wsl: 3.1.1 + xdg-app-paths@5.5.1: + dependencies: + os-paths: 4.4.0 + xdg-portable: 7.3.0 + + xdg-portable@7.3.0: + dependencies: + os-paths: 4.4.0 + xml-naming@0.1.0: {} xml2js@0.5.0: @@ -27187,6 +27281,8 @@ snapshots: zod@3.25.76: {} + zod@4.1.11: {} + zod@4.3.6: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7effbfdfbef2..bcb5bbde1396 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -70,6 +70,14 @@ trustPolicyExclude: - '@netlify/edge-bundler@14.10.2||14.10.1' - '@netlify/zip-it-and-ship-it@14.5.6' - '@netlify/serverless-functions-api@2.15.1' + # Vercel packages that do not have trusted publishing enabled on npm + # TODO: Remove once trusted publishing is enabled. + - '@vercel/functions@3.9.5' + - '@vercel/routing-utils@6.5.0' + # Transitive dependencies of '@vercel/functions' + - '@vercel/oidc@3.8.5' + - '@vercel/cli-exec@1.0.1' + - '@vercel/cli-config@0.2.4' # Outdated dependencies of @vitejs/plugin-react@^5 and @vscode/vsce # TODO: Update the Vite plugin to ^6 in Astro v7 and remove this - 'semver@6.3.1 || 5.7.2' From 2548abf1874f2fdfc7438aab51cfea03424753cc Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 31 Aug 2026 12:22:43 +0100 Subject: [PATCH 11/19] fix: invalid internal declarations (#17869) Co-authored-by: Florian Lefebvre --- .changeset/clear-pets-help.md | 5 +++ packages/astro/dev-only.d.ts | 34 +++++++++---------- .../src/core/app/entrypoints/virtual/dev.ts | 4 +-- .../vite-plugin-app/createAstroServerApp.ts | 2 +- 4 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 .changeset/clear-pets-help.md diff --git a/.changeset/clear-pets-help.md b/.changeset/clear-pets-help.md new file mode 100644 index 000000000000..c6f87af4b639 --- /dev/null +++ b/.changeset/clear-pets-help.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a case where the logger was improperly initialized at runtime in dev. diff --git a/packages/astro/dev-only.d.ts b/packages/astro/dev-only.d.ts index a0f3b08e6b57..42e8f2b9f3f4 100644 --- a/packages/astro/dev-only.d.ts +++ b/packages/astro/dev-only.d.ts @@ -24,34 +24,29 @@ declare module 'virtual:astro:actions/options' { } declare module 'virtual:astro:actions/entrypoint' { - import type { SSRActions } from './src/index.js'; - export const server: SSRActions; + export const server: import('./src/index.js').SSRActions; } declare module 'virtual:astro:manifest' { - import type { SSRManifest } from './src/index.js'; - export const manifest: SSRManifest; + export const manifest: import('./src/index.js').SSRManifest; } declare module 'virtual:astro:routes' { - import type { RoutesList } from './src/types/astro.js'; - export const routes: RoutesList[]; + export const routes: import('./src/core/app/types.js').RouteInfo[]; } declare module 'virtual:astro:renderers' { - import type { AstroRenderer } from './src/index.js'; - export const renderers: AstroRenderer[]; + export const renderers: import('./src/index.js').AstroRenderer[]; } declare module 'virtual:astro:middleware' { - import type { AstroMiddlewareInstance } from './src/index.js'; - const middleware: AstroMiddlewareInstance; + const middleware: import('./src/index.js').AstroMiddlewareInstance; export default middleware; + export = middleware; } declare module 'virtual:astro:session-driver' { - import type { Driver } from 'unstorage'; - export const driver: Driver; + export const driver: import('unstorage').Driver; } declare module 'virtual:astro:pages' { @@ -73,18 +68,21 @@ declare module 'virtual:astro:adapter-config' { } declare module 'virtual:astro:dev-css' { - import type { ImportedDevStyles } from './src/types/astro.js'; - export const css: Set; + export const css: Set; } declare module 'virtual:astro:dev-css-all' { - import type { ImportedDevStyles } from './src/types/astro.js'; - export const devCSSMap: Map Promise<{ css: Set }>>; + export const devCSSMap: Map< + string, + () => Promise<{ css: Set }> + >; } declare module 'virtual:astro:component-metadata' { - import type { SSRComponentMetadata } from './src/types/public/internal.js'; - export const componentMetadataEntries: [string, SSRComponentMetadata][]; + export const componentMetadataEntries: [ + string, + import('./src/types/public/internal.js').SSRComponentMetadata, + ][]; } declare module 'virtual:astro:app' { diff --git a/packages/astro/src/core/app/entrypoints/virtual/dev.ts b/packages/astro/src/core/app/entrypoints/virtual/dev.ts index 0a1591a30849..186356dde521 100644 --- a/packages/astro/src/core/app/entrypoints/virtual/dev.ts +++ b/packages/astro/src/core/app/entrypoints/virtual/dev.ts @@ -21,7 +21,7 @@ let hmrWired = false; export const createApp: CreateApp = ({ streaming } = {}) => { // Composition order: logger → environment → facade ctor // (which warms the route table) → fetch handler → HMR wiring. - setLogger(manifest, createConsoleLogger(manifest.logLevel)); + setLogger(manifest, createConsoleLogger({ level: manifest.logLevel })); setEnvironment(manifest, createNonRunnableEnvironment()); const app = new DevFacadeApp(manifest, streaming); app.setFetchHandler(fetchable); @@ -38,7 +38,7 @@ export const createApp: CreateApp = ({ streaming } = {}) => { const { routes: newRoutes } = await import('virtual:astro:routes'); updateRouteTable( manifest, - newRoutes.map((r: RouteInfo) => r.routeData), + newRoutes.map((route: RouteInfo) => route.routeData), ); } catch (e: any) { // Log error but don't crash - route updates are non-critical diff --git a/packages/astro/src/vite-plugin-app/createAstroServerApp.ts b/packages/astro/src/vite-plugin-app/createAstroServerApp.ts index b459ada9a4ce..e8f35148a1fd 100644 --- a/packages/astro/src/vite-plugin-app/createAstroServerApp.ts +++ b/packages/astro/src/vite-plugin-app/createAstroServerApp.ts @@ -73,7 +73,7 @@ export default async function createAstroServerApp( const { routes: newRoutes } = await import('virtual:astro:routes'); updateRouteTable( manifest, - newRoutes.map((r: RouteInfo) => r.routeData), + newRoutes.map((route: RouteInfo) => route.routeData), ); actualLogger.debug('router', 'Routes updated via HMR'); } catch (e: any) { From f7191cc4257330b6ca435fb4dae66d315b16115d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Grof=20=E2=94=82=20Dev?= <147989511+jx-grxf@users.noreply.github.com> Date: Mon, 31 Aug 2026 15:04:26 +0200 Subject: [PATCH 12/19] Omit empty srcset on content collection markdown images (#17872) The content layer's image renderer always wrote the srcset attribute, so an image without responsive candidates produced srcset="", which is invalid HTML. The older Markdown pipeline in vite-plugin-markdown only sets the attribute when there are candidates; mirror that here. --- .changeset/content-image-empty-srcset.md | 5 +++++ packages/astro/src/content/runtime.ts | 4 +++- packages/astro/test/core-image.test.ts | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .changeset/content-image-empty-srcset.md diff --git a/.changeset/content-image-empty-srcset.md b/.changeset/content-image-empty-srcset.md new file mode 100644 index 000000000000..9d2ffa77ed76 --- /dev/null +++ b/.changeset/content-image-empty-srcset.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes Markdown images in content collections rendering an empty `srcset` attribute when no responsive candidates are generated. diff --git a/packages/astro/src/content/runtime.ts b/packages/astro/src/content/runtime.ts index 018b59e0f1ea..44d874ca8536 100644 --- a/packages/astro/src/content/runtime.ts +++ b/packages/astro/src/content/runtime.ts @@ -504,7 +504,9 @@ async function updateImageReferencesInBody(html: string, fileName: string) { return Object.entries({ ...attributes, src: image.src, - srcset: image.srcSet.attribute, + // An empty `srcset` is invalid HTML, so only emit it when there are + // actual candidates. This matches `vite-plugin-markdown/images.ts`. + ...(image.srcSet.values.length > 0 ? { srcset: image.srcSet.attribute } : {}), // This attribute is used by the toolbar audit ...(import.meta.env.DEV ? { 'data-image-component': 'true' } : {}), }) diff --git a/packages/astro/test/core-image.test.ts b/packages/astro/test/core-image.test.ts index 9774c6351616..3b06fed871d3 100644 --- a/packages/astro/test/core-image.test.ts +++ b/packages/astro/test/core-image.test.ts @@ -1183,6 +1183,22 @@ describe('build ssg', () => { assert.equal($contentImg.attr('alt'), '', 'alt attribute should be empty string, not missing'); }); + it('content collection images omit srcset when there are no candidates', async () => { + const html = await fixture.readFile('/blog/empty-alt/index.html'); + + const $ = cheerio.load(html); + const $contentImg = $('img').filter(function () { + // Find the image rendered from markdown content (not the frontmatter images) + return !$(this).closest('#direct-image, #nested-image').length; + }); + assert.equal($contentImg.length, 1, 'should have one content image'); + assert.equal( + $contentImg.attr('srcset'), + undefined, + 'srcset attribute should be missing, not an empty string', + ); + }); + it('quality attribute produces a different file', async () => { const html = await fixture.readFile('/quality/index.html'); const $ = cheerio.load(html); From bd7af0b8d8c23138d3b1ca7d63d6953da9e009cf Mon Sep 17 00:00:00 2001 From: ocavue Date: Tue, 1 Sep 2026 01:04:35 +1200 Subject: [PATCH 13/19] chore(deps): update react to v19 in fixtures (#16905) --- .../astro-island-hydration-error/package.json | 4 +- .../fixtures/client-idle-timeout/package.json | 4 +- .../e2e/fixtures/client-only/package.json | 4 +- .../e2e/fixtures/cloudflare/package.json | 8 +- .../fixtures/csp-server-islands/package.json | 4 +- .../custom-client-directives/package.json | 4 +- .../astro/e2e/fixtures/errors/package.json | 4 +- .../fixtures/multiple-frameworks/package.json | 4 +- .../fixtures/nested-in-preact/package.json | 4 +- .../e2e/fixtures/nested-in-react/package.json | 4 +- .../e2e/fixtures/nested-in-solid/package.json | 4 +- .../fixtures/nested-in-svelte/package.json | 4 +- .../e2e/fixtures/nested-in-vue/package.json | 4 +- .../fixtures/nested-recursive/package.json | 4 +- .../astro/e2e/fixtures/pass-js/package.json | 4 +- .../e2e/fixtures/react-component/package.json | 4 +- .../e2e/fixtures/server-islands/package.json | 4 +- .../e2e/fixtures/ts-resolution/package.json | 4 +- .../fixtures/view-transitions/package.json | 4 +- .../performance/fixtures/md/package.json | 8 +- .../performance/fixtures/mdoc/package.json | 8 +- .../performance/fixtures/mdx/package.json | 8 +- .../performance/fixtures/utils/package.json | 4 +- .../fixtures/astro-assets-prefix/package.json | 4 +- .../fixtures/astro-client-only/package.json | 4 +- .../astro-component-bundling/package.json | 4 +- .../test/fixtures/astro-dynamic/package.json | 4 +- .../fixtures/astro-partial-html/package.json | 4 +- .../fixtures/astro-slots-nested/package.json | 4 +- .../component-library-shared/package.json | 2 +- .../fixtures/component-library/package.json | 4 +- .../container-custom-renderers/package.json | 4 +- .../fixtures/css-deduplication/package.json | 8 +- .../fixtures/css-order-import/package.json | 4 +- .../fixtures/impostor-mdx-file/package.json | 4 +- packages/astro/test/fixtures/jsx/package.json | 4 +- .../package.json | 4 +- .../packages/react-lib/package.json | 2 +- .../fixtures/react-and-solid/package.json | 4 +- .../fixtures/react-jsx-export/package.json | 4 +- .../test/fixtures/slots-react/package.json | 4 +- .../test/fixtures/sourcemap/package.json | 4 +- .../package.json | 4 +- .../static-build-frameworks/package.json | 4 +- .../fixtures/view-transitions/package.json | 4 +- .../test/fixtures/vite-plugin/package.json | 8 +- .../mdx/test/fixtures/mdx-images/package.json | 4 +- .../test/fixtures/mdx-namespace/package.json | 4 +- .../mdx/test/fixtures/mdx-page/package.json | 4 +- .../mdx-plus-react-errors/package.json | 4 +- .../test/fixtures/mdx-plus-react/package.json | 4 +- pnpm-lock.yaml | 464 +++++++++--------- 52 files changed, 352 insertions(+), 336 deletions(-) diff --git a/packages/astro/e2e/fixtures/astro-island-hydration-error/package.json b/packages/astro/e2e/fixtures/astro-island-hydration-error/package.json index af1efa6ae4cc..b01182de6000 100644 --- a/packages/astro/e2e/fixtures/astro-island-hydration-error/package.json +++ b/packages/astro/e2e/fixtures/astro-island-hydration-error/package.json @@ -5,7 +5,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/e2e/fixtures/client-idle-timeout/package.json b/packages/astro/e2e/fixtures/client-idle-timeout/package.json index af4c416058ad..a5152e90fc95 100644 --- a/packages/astro/e2e/fixtures/client-idle-timeout/package.json +++ b/packages/astro/e2e/fixtures/client-idle-timeout/package.json @@ -7,7 +7,7 @@ "astro": "workspace:*" }, "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/e2e/fixtures/client-only/package.json b/packages/astro/e2e/fixtures/client-only/package.json index 8dd269c82ac3..edd42fd59ead 100644 --- a/packages/astro/e2e/fixtures/client-only/package.json +++ b/packages/astro/e2e/fixtures/client-only/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/e2e/fixtures/cloudflare/package.json b/packages/astro/e2e/fixtures/cloudflare/package.json index b56e85e311ba..53b35adc7016 100644 --- a/packages/astro/e2e/fixtures/cloudflare/package.json +++ b/packages/astro/e2e/fixtures/cloudflare/package.json @@ -19,12 +19,12 @@ "@test/e2e-my-lib": "workspace:*", "vue": "^3.5.30", "@vitejs/plugin-vue": "^6.0.5", - "@types/react": "^18.3.28", - "@types/react-dom": "^18.3.7", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "astro": "workspace:*", "clsx": "^2.1.1", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "sharp": "^0.35.0" } } diff --git a/packages/astro/e2e/fixtures/csp-server-islands/package.json b/packages/astro/e2e/fixtures/csp-server-islands/package.json index ac100715b55f..30270cdabb6d 100644 --- a/packages/astro/e2e/fixtures/csp-server-islands/package.json +++ b/packages/astro/e2e/fixtures/csp-server-islands/package.json @@ -10,7 +10,7 @@ "astro": "workspace:*", "@astrojs/mdx": "workspace:*", "@astrojs/node": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/e2e/fixtures/custom-client-directives/package.json b/packages/astro/e2e/fixtures/custom-client-directives/package.json index 4da681d10497..88a0e4ad8c45 100644 --- a/packages/astro/e2e/fixtures/custom-client-directives/package.json +++ b/packages/astro/e2e/fixtures/custom-client-directives/package.json @@ -5,7 +5,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/e2e/fixtures/errors/package.json b/packages/astro/e2e/fixtures/errors/package.json index 5b74ea59c4e3..688d13dacb03 100644 --- a/packages/astro/e2e/fixtures/errors/package.json +++ b/packages/astro/e2e/fixtures/errors/package.json @@ -10,8 +10,8 @@ "@astrojs/vue": "workspace:*", "astro": "workspace:*", "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "sass": "^1.98.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", diff --git a/packages/astro/e2e/fixtures/multiple-frameworks/package.json b/packages/astro/e2e/fixtures/multiple-frameworks/package.json index 7156d60402c9..f72cefc71ad2 100644 --- a/packages/astro/e2e/fixtures/multiple-frameworks/package.json +++ b/packages/astro/e2e/fixtures/multiple-frameworks/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/e2e/fixtures/nested-in-preact/package.json b/packages/astro/e2e/fixtures/nested-in-preact/package.json index 6cd7e67f1b79..63ea6c74715f 100644 --- a/packages/astro/e2e/fixtures/nested-in-preact/package.json +++ b/packages/astro/e2e/fixtures/nested-in-preact/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/e2e/fixtures/nested-in-react/package.json b/packages/astro/e2e/fixtures/nested-in-react/package.json index 7fc82ecd236b..10e928ca3d63 100644 --- a/packages/astro/e2e/fixtures/nested-in-react/package.json +++ b/packages/astro/e2e/fixtures/nested-in-react/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/e2e/fixtures/nested-in-solid/package.json b/packages/astro/e2e/fixtures/nested-in-solid/package.json index 5773c83f7ac4..429919585f65 100644 --- a/packages/astro/e2e/fixtures/nested-in-solid/package.json +++ b/packages/astro/e2e/fixtures/nested-in-solid/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/e2e/fixtures/nested-in-svelte/package.json b/packages/astro/e2e/fixtures/nested-in-svelte/package.json index 801794dd3aa9..1167e1c176a1 100644 --- a/packages/astro/e2e/fixtures/nested-in-svelte/package.json +++ b/packages/astro/e2e/fixtures/nested-in-svelte/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/e2e/fixtures/nested-in-vue/package.json b/packages/astro/e2e/fixtures/nested-in-vue/package.json index 56d57339474d..b8238801b4f3 100644 --- a/packages/astro/e2e/fixtures/nested-in-vue/package.json +++ b/packages/astro/e2e/fixtures/nested-in-vue/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/e2e/fixtures/nested-recursive/package.json b/packages/astro/e2e/fixtures/nested-recursive/package.json index e7b4cf62cf59..708a8d30a0fa 100644 --- a/packages/astro/e2e/fixtures/nested-recursive/package.json +++ b/packages/astro/e2e/fixtures/nested-recursive/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/e2e/fixtures/pass-js/package.json b/packages/astro/e2e/fixtures/pass-js/package.json index eef5f040883d..f3d176c3b13c 100644 --- a/packages/astro/e2e/fixtures/pass-js/package.json +++ b/packages/astro/e2e/fixtures/pass-js/package.json @@ -7,7 +7,7 @@ "astro": "workspace:*" }, "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/e2e/fixtures/react-component/package.json b/packages/astro/e2e/fixtures/react-component/package.json index bd52c6dcacea..e4c89b8372c7 100644 --- a/packages/astro/e2e/fixtures/react-component/package.json +++ b/packages/astro/e2e/fixtures/react-component/package.json @@ -6,7 +6,7 @@ "@astrojs/react": "workspace:*", "astro": "workspace:*", "@astrojs/mdx": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/e2e/fixtures/server-islands/package.json b/packages/astro/e2e/fixtures/server-islands/package.json index 9958ee287857..cd2e71b080dd 100644 --- a/packages/astro/e2e/fixtures/server-islands/package.json +++ b/packages/astro/e2e/fixtures/server-islands/package.json @@ -10,7 +10,7 @@ "astro": "workspace:*", "@astrojs/mdx": "workspace:*", "@astrojs/node": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/e2e/fixtures/ts-resolution/package.json b/packages/astro/e2e/fixtures/ts-resolution/package.json index 1d67716d0a2f..dad3b79e85c8 100644 --- a/packages/astro/e2e/fixtures/ts-resolution/package.json +++ b/packages/astro/e2e/fixtures/ts-resolution/package.json @@ -5,7 +5,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/e2e/fixtures/view-transitions/package.json b/packages/astro/e2e/fixtures/view-transitions/package.json index ac0687dc22d3..75952fa2743b 100644 --- a/packages/astro/e2e/fixtures/view-transitions/package.json +++ b/packages/astro/e2e/fixtures/view-transitions/package.json @@ -9,8 +9,8 @@ "@astrojs/svelte": "workspace:*", "@astrojs/vue": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/performance/fixtures/md/package.json b/packages/astro/performance/fixtures/md/package.json index 9237ac74476b..e30ee725faf9 100644 --- a/packages/astro/performance/fixtures/md/package.json +++ b/packages/astro/performance/fixtures/md/package.json @@ -14,10 +14,10 @@ "dependencies": { "@astrojs/react": "workspace:*", "@performance/utils": "workspace:*", - "@types/react": "^18.3.28", - "@types/react-dom": "^18.3.7", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/performance/fixtures/mdoc/package.json b/packages/astro/performance/fixtures/mdoc/package.json index f98097c556d6..c59cd84e0f4d 100644 --- a/packages/astro/performance/fixtures/mdoc/package.json +++ b/packages/astro/performance/fixtures/mdoc/package.json @@ -15,10 +15,10 @@ "@astrojs/markdoc": "workspace:*", "@astrojs/react": "workspace:*", "@performance/utils": "workspace:*", - "@types/react": "^18.3.28", - "@types/react-dom": "^18.3.7", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/performance/fixtures/mdx/package.json b/packages/astro/performance/fixtures/mdx/package.json index 57869acb77f9..4d0cdfefd277 100644 --- a/packages/astro/performance/fixtures/mdx/package.json +++ b/packages/astro/performance/fixtures/mdx/package.json @@ -15,10 +15,10 @@ "@astrojs/mdx": "workspace:*", "@astrojs/react": "workspace:*", "@performance/utils": "workspace:*", - "@types/react": "^18.3.28", - "@types/react-dom": "^18.3.7", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/performance/fixtures/utils/package.json b/packages/astro/performance/fixtures/utils/package.json index 7ad34d8f01c5..77e4dc32b219 100644 --- a/packages/astro/performance/fixtures/utils/package.json +++ b/packages/astro/performance/fixtures/utils/package.json @@ -9,8 +9,8 @@ "license": "unlicensed", "devDependencies": { "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "exports": { ".": "./index.ts" diff --git a/packages/astro/test/fixtures/astro-assets-prefix/package.json b/packages/astro/test/fixtures/astro-assets-prefix/package.json index 1b535089e175..8a8ac2449e91 100644 --- a/packages/astro/test/fixtures/astro-assets-prefix/package.json +++ b/packages/astro/test/fixtures/astro-assets-prefix/package.json @@ -6,7 +6,7 @@ "@astrojs/mdx": "workspace:*", "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/astro-client-only/package.json b/packages/astro/test/fixtures/astro-client-only/package.json index 35a2de852ed9..ea6639926f14 100644 --- a/packages/astro/test/fixtures/astro-client-only/package.json +++ b/packages/astro/test/fixtures/astro-client-only/package.json @@ -7,8 +7,8 @@ "@astrojs/svelte": "workspace:*", "@test/astro-client-only-pkg": "file:./pkg", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "svelte": "^5.54.0" } } diff --git a/packages/astro/test/fixtures/astro-component-bundling/package.json b/packages/astro/test/fixtures/astro-component-bundling/package.json index 2ad0b9769c5c..7e021a86dbe8 100644 --- a/packages/astro/test/fixtures/astro-component-bundling/package.json +++ b/packages/astro/test/fixtures/astro-component-bundling/package.json @@ -5,7 +5,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/astro-dynamic/package.json b/packages/astro/test/fixtures/astro-dynamic/package.json index 690cff9b69ab..ed362d89ec0a 100644 --- a/packages/astro/test/fixtures/astro-dynamic/package.json +++ b/packages/astro/test/fixtures/astro-dynamic/package.json @@ -6,8 +6,8 @@ "@astrojs/react": "workspace:*", "@astrojs/svelte": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "svelte": "^5.54.0" } } diff --git a/packages/astro/test/fixtures/astro-partial-html/package.json b/packages/astro/test/fixtures/astro-partial-html/package.json index 081c407ec486..b8de73a19c98 100644 --- a/packages/astro/test/fixtures/astro-partial-html/package.json +++ b/packages/astro/test/fixtures/astro-partial-html/package.json @@ -5,7 +5,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/astro-slots-nested/package.json b/packages/astro/test/fixtures/astro-slots-nested/package.json index a541489704a0..a90b97008ed4 100644 --- a/packages/astro/test/fixtures/astro-slots-nested/package.json +++ b/packages/astro/test/fixtures/astro-slots-nested/package.json @@ -10,8 +10,8 @@ "@astrojs/vue": "workspace:*", "astro": "workspace:*", "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/test/fixtures/component-library-shared/package.json b/packages/astro/test/fixtures/component-library-shared/package.json index c0c551857d11..504ae352dd01 100644 --- a/packages/astro/test/fixtures/component-library-shared/package.json +++ b/packages/astro/test/fixtures/component-library-shared/package.json @@ -19,6 +19,6 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1" + "react": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/component-library/package.json b/packages/astro/test/fixtures/component-library/package.json index cc484807c9c1..9ae90df67cf4 100644 --- a/packages/astro/test/fixtures/component-library/package.json +++ b/packages/astro/test/fixtures/component-library/package.json @@ -9,8 +9,8 @@ "@test/component-library-shared": "workspace:*", "astro": "workspace:*", "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "svelte": "^5.54.0" } } diff --git a/packages/astro/test/fixtures/container-custom-renderers/package.json b/packages/astro/test/fixtures/container-custom-renderers/package.json index 7d3587bd7435..6b646f2f1a51 100644 --- a/packages/astro/test/fixtures/container-custom-renderers/package.json +++ b/packages/astro/test/fixtures/container-custom-renderers/package.json @@ -7,8 +7,8 @@ "@astrojs/react": "workspace:*", "@astrojs/vue": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "vue": "^3.5.30" } } diff --git a/packages/astro/test/fixtures/css-deduplication/package.json b/packages/astro/test/fixtures/css-deduplication/package.json index 314960a97772..c40d809d0a9a 100644 --- a/packages/astro/test/fixtures/css-deduplication/package.json +++ b/packages/astro/test/fixtures/css-deduplication/package.json @@ -5,9 +5,9 @@ "dependencies": { "astro": "workspace:*", "@astrojs/react": "workspace:*", - "@types/react": "^18.3.28", - "@types/react-dom": "^18.3.7", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/css-order-import/package.json b/packages/astro/test/fixtures/css-order-import/package.json index b8aae59eb132..447a8757567b 100644 --- a/packages/astro/test/fixtures/css-order-import/package.json +++ b/packages/astro/test/fixtures/css-order-import/package.json @@ -4,7 +4,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/impostor-mdx-file/package.json b/packages/astro/test/fixtures/impostor-mdx-file/package.json index 5217981c256e..1bf7455b2837 100644 --- a/packages/astro/test/fixtures/impostor-mdx-file/package.json +++ b/packages/astro/test/fixtures/impostor-mdx-file/package.json @@ -5,7 +5,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/jsx/package.json b/packages/astro/test/fixtures/jsx/package.json index 2fb85db4289f..9b7e13a66ef0 100644 --- a/packages/astro/test/fixtures/jsx/package.json +++ b/packages/astro/test/fixtures/jsx/package.json @@ -13,8 +13,8 @@ }, "dependencies": { "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11", "svelte": "^5.54.0", "vue": "^3.5.30" diff --git a/packages/astro/test/fixtures/lightningcss-css-modules-content/package.json b/packages/astro/test/fixtures/lightningcss-css-modules-content/package.json index 311c3372fc4c..76ef52ab5213 100644 --- a/packages/astro/test/fixtures/lightningcss-css-modules-content/package.json +++ b/packages/astro/test/fixtures/lightningcss-css-modules-content/package.json @@ -7,7 +7,7 @@ "@astrojs/mdx": "workspace:*", "@astrojs/react": "workspace:*", "lightningcss": "^1.32.0", - "react": "^18.0.0", - "react-dom": "^18.0.0" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/preact-compat-component/packages/react-lib/package.json b/packages/astro/test/fixtures/preact-compat-component/packages/react-lib/package.json index 240d87f4b13e..0b7837c6add6 100644 --- a/packages/astro/test/fixtures/preact-compat-component/packages/react-lib/package.json +++ b/packages/astro/test/fixtures/preact-compat-component/packages/react-lib/package.json @@ -4,6 +4,6 @@ "private": true, "type": "module", "dependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/react-and-solid/package.json b/packages/astro/test/fixtures/react-and-solid/package.json index a78e455451a4..dd268e79fa8c 100644 --- a/packages/astro/test/fixtures/react-and-solid/package.json +++ b/packages/astro/test/fixtures/react-and-solid/package.json @@ -5,8 +5,8 @@ "@astrojs/react": "workspace:*", "@astrojs/solid-js": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "solid-js": "^1.9.11" } } diff --git a/packages/astro/test/fixtures/react-jsx-export/package.json b/packages/astro/test/fixtures/react-jsx-export/package.json index 71d8ba3ac54f..e611ebaf7760 100644 --- a/packages/astro/test/fixtures/react-jsx-export/package.json +++ b/packages/astro/test/fixtures/react-jsx-export/package.json @@ -7,7 +7,7 @@ "astro": "workspace:*" }, "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/slots-react/package.json b/packages/astro/test/fixtures/slots-react/package.json index 59b4b026c081..bf34cfd94d47 100644 --- a/packages/astro/test/fixtures/slots-react/package.json +++ b/packages/astro/test/fixtures/slots-react/package.json @@ -6,7 +6,7 @@ "@astrojs/mdx": "workspace:*", "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/sourcemap/package.json b/packages/astro/test/fixtures/sourcemap/package.json index 1d7009432767..79dfdf35502e 100644 --- a/packages/astro/test/fixtures/sourcemap/package.json +++ b/packages/astro/test/fixtures/sourcemap/package.json @@ -5,7 +5,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/special-chars-in-component-imports/package.json b/packages/astro/test/fixtures/special-chars-in-component-imports/package.json index a0233a37cd93..313e82344a61 100644 --- a/packages/astro/test/fixtures/special-chars-in-component-imports/package.json +++ b/packages/astro/test/fixtures/special-chars-in-component-imports/package.json @@ -6,7 +6,7 @@ "@astrojs/mdx": "workspace:*", "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/static-build-frameworks/package.json b/packages/astro/test/fixtures/static-build-frameworks/package.json index 2e49f8df4985..d33ba3eae117 100644 --- a/packages/astro/test/fixtures/static-build-frameworks/package.json +++ b/packages/astro/test/fixtures/static-build-frameworks/package.json @@ -7,7 +7,7 @@ "@astrojs/react": "workspace:*", "astro": "workspace:*", "preact": "^10.29.0", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/astro/test/fixtures/view-transitions/package.json b/packages/astro/test/fixtures/view-transitions/package.json index 041db4c4053a..08b4d8b98cb5 100644 --- a/packages/astro/test/fixtures/view-transitions/package.json +++ b/packages/astro/test/fixtures/view-transitions/package.json @@ -5,7 +5,7 @@ "dependencies": { "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/integrations/cloudflare/test/fixtures/vite-plugin/package.json b/packages/integrations/cloudflare/test/fixtures/vite-plugin/package.json index 4a861a8f69d4..1d01e35f1d89 100644 --- a/packages/integrations/cloudflare/test/fixtures/vite-plugin/package.json +++ b/packages/integrations/cloudflare/test/fixtures/vite-plugin/package.json @@ -15,11 +15,11 @@ "svelte":"^5.53.5", "vue": "^3.5.29", "@vitejs/plugin-vue": "^6.0.4", - "@types/react": "^18.3.28", - "@types/react-dom": "^18.3.7", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "sharp": "^0.35.0" } } diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/package.json b/packages/integrations/mdx/test/fixtures/mdx-images/package.json index 44b4a9fcf757..5a087b958445 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-images/package.json +++ b/packages/integrations/mdx/test/fixtures/mdx-images/package.json @@ -4,7 +4,7 @@ "dependencies": { "@astrojs/mdx": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/integrations/mdx/test/fixtures/mdx-namespace/package.json b/packages/integrations/mdx/test/fixtures/mdx-namespace/package.json index 435bb7f5419b..8b836d6cb006 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-namespace/package.json +++ b/packages/integrations/mdx/test/fixtures/mdx-namespace/package.json @@ -5,7 +5,7 @@ "@astrojs/mdx": "workspace:*", "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/integrations/mdx/test/fixtures/mdx-page/package.json b/packages/integrations/mdx/test/fixtures/mdx-page/package.json index 0429b4ee410c..ed8f3d26df7b 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-page/package.json +++ b/packages/integrations/mdx/test/fixtures/mdx-page/package.json @@ -4,7 +4,7 @@ "dependencies": { "@astrojs/mdx": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/package.json b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/package.json index b76dc19a6e7f..9e6273a4e680 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/package.json +++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react-errors/package.json @@ -5,7 +5,7 @@ "@astrojs/mdx": "workspace:*", "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json b/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json index a177efaff804..4c29e945df42 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json +++ b/packages/integrations/mdx/test/fixtures/mdx-plus-react/package.json @@ -5,7 +5,7 @@ "@astrojs/mdx": "workspace:*", "@astrojs/react": "workspace:*", "astro": "workspace:*", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c653cc928d2..7ccd838add07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1109,20 +1109,20 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/e2e/fixtures/client-idle-timeout: dependencies: react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) devDependencies: '@astrojs/react': specifier: workspace:* @@ -1137,11 +1137,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -1192,11 +1192,11 @@ importers: specifier: workspace:* version: link:packages/my-lib '@types/react': - specifier: ^18.3.28 - version: 18.3.28 + specifier: ^19.0.0 + version: 19.2.18 '@types/react-dom': - specifier: ^18.3.7 - version: 18.3.7(@types/react@18.3.28) + specifier: ^19.0.0 + version: 19.2.5(@types/react@19.2.18) '@vitejs/plugin-vue': specifier: ^6.0.5 version: 6.0.5(vite@8.2.1)(vue@3.5.30) @@ -1210,11 +1210,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) sharp: specifier: ^0.35.0 version: 0.35.2 @@ -1257,11 +1257,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/e2e/fixtures/css: dependencies: @@ -1278,11 +1278,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/e2e/fixtures/dev-toolbar: dependencies: @@ -1341,11 +1341,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) sass: specifier: ^1.98.0 version: 1.98.0 @@ -1402,11 +1402,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -1458,11 +1458,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -1498,11 +1498,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -1538,11 +1538,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -1578,11 +1578,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -1618,11 +1618,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -1658,11 +1658,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -1701,11 +1701,11 @@ importers: packages/astro/e2e/fixtures/pass-js: dependencies: react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) devDependencies: '@astrojs/react': specifier: workspace:* @@ -1774,11 +1774,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/e2e/fixtures/react19-preact-hook-error: dependencies: @@ -1817,11 +1817,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/e2e/fixtures/server-islands-key: dependencies: @@ -1909,11 +1909,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/e2e/fixtures/view-transitions: dependencies: @@ -1936,11 +1936,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -1981,20 +1981,20 @@ importers: specifier: workspace:* version: link:../utils '@types/react': - specifier: ^18.3.28 - version: 18.3.28 + specifier: ^19.0.0 + version: 19.2.18 '@types/react-dom': - specifier: ^18.3.7 - version: 18.3.7(@types/react@18.3.28) + specifier: ^19.0.0 + version: 19.2.5(@types/react@19.2.18) astro: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/performance/fixtures/mdoc: dependencies: @@ -2008,20 +2008,20 @@ importers: specifier: workspace:* version: link:../utils '@types/react': - specifier: ^18.3.28 - version: 18.3.28 + specifier: ^19.0.0 + version: 19.2.18 '@types/react-dom': - specifier: ^18.3.7 - version: 18.3.7(@types/react@18.3.28) + specifier: ^19.0.0 + version: 19.2.5(@types/react@19.2.18) astro: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/performance/fixtures/mdx: dependencies: @@ -2035,20 +2035,20 @@ importers: specifier: workspace:* version: link:../utils '@types/react': - specifier: ^18.3.28 - version: 18.3.28 + specifier: ^19.0.0 + version: 19.2.18 '@types/react-dom': - specifier: ^18.3.7 - version: 18.3.7(@types/react@18.3.28) + specifier: ^19.0.0 + version: 19.2.5(@types/react@19.2.18) astro: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/performance/fixtures/utils: devDependencies: @@ -2056,11 +2056,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/0-css: dependencies: @@ -2190,11 +2190,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/astro-basic: dependencies: @@ -2280,11 +2280,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) svelte: specifier: ^5.54.0 version: 5.55.3 @@ -2300,11 +2300,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/astro-component-code: dependencies: @@ -2348,11 +2348,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) svelte: specifier: ^5.54.0 version: 5.55.3 @@ -2552,11 +2552,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/astro-preview-allowed-hosts: dependencies: @@ -2618,11 +2618,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -2750,11 +2750,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) svelte: specifier: ^5.54.0 version: 5.55.3 @@ -2765,8 +2765,8 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 devDependencies: astro: specifier: workspace:* @@ -2802,11 +2802,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) vue: specifier: ^3.5.30 version: 3.5.30(typescript@6.0.3) @@ -3137,20 +3137,20 @@ importers: specifier: workspace:* version: link:../../../../integrations/react '@types/react': - specifier: ^18.3.28 - version: 18.3.28 + specifier: ^19.0.0 + version: 19.2.18 '@types/react-dom': - specifier: ^18.3.7 - version: 18.3.7(@types/react@18.3.28) + specifier: ^19.0.0 + version: 19.2.5(@types/react@19.2.18) astro: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/css-double-bundle: dependencies: @@ -3203,11 +3203,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/css-order-layout: dependencies: @@ -3503,11 +3503,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/incremental-build: dependencies: @@ -3605,11 +3605,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -3690,11 +3690,11 @@ importers: specifier: ^1.32.0 version: 1.33.0 react: - specifier: ^18.0.0 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.0.0 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/lightningcss-scoped-nesting: dependencies: @@ -3899,8 +3899,8 @@ importers: packages/astro/test/fixtures/preact-compat-component/packages/react-lib: dependencies: react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 packages/astro/test/fixtures/preact-component: dependencies: @@ -3947,11 +3947,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) solid-js: specifier: ^1.9.11 version: 1.9.13 @@ -3959,11 +3959,11 @@ importers: packages/astro/test/fixtures/react-jsx-export: dependencies: react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) devDependencies: '@astrojs/react': specifier: workspace:* @@ -4104,11 +4104,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/slots-solid: dependencies: @@ -4188,11 +4188,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/space in folder name/app: dependencies: @@ -4212,11 +4212,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/ssr-assets: dependencies: @@ -4335,11 +4335,11 @@ importers: specifier: ^10.29.0 version: 10.29.8(preact-render-to-string@6.6.6) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/static-build-page-url-format: dependencies: @@ -4424,11 +4424,11 @@ importers: specifier: workspace:* version: link:../../.. react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/astro/test/fixtures/virtual-astro-file: dependencies: @@ -5000,11 +5000,11 @@ importers: specifier: workspace:* version: link:../../../../vue '@types/react': - specifier: ^18.3.28 - version: 18.3.28 + specifier: ^19.0.0 + version: 19.2.18 '@types/react-dom': - specifier: ^18.3.7 - version: 18.3.7(@types/react@18.3.28) + specifier: ^19.0.0 + version: 19.2.5(@types/react@19.2.18) '@vitejs/plugin-vue': specifier: ^6.0.4 version: 6.0.5(vite@8.2.1)(vue@3.5.30) @@ -5012,11 +5012,11 @@ importers: specifier: workspace:* version: link:../../../../../astro react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) sharp: specifier: ^0.35.0 version: 0.35.2 @@ -5488,11 +5488,11 @@ importers: specifier: workspace:* version: link:../../../../../astro react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/integrations/mdx/test/fixtures/mdx-infinite-loop: dependencies: @@ -5521,11 +5521,11 @@ importers: specifier: workspace:* version: link:../../../../../astro react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/integrations/mdx/test/fixtures/mdx-optimize: dependencies: @@ -5545,11 +5545,11 @@ importers: specifier: workspace:* version: link:../../../../../astro react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/integrations/mdx/test/fixtures/mdx-plus-react: dependencies: @@ -5563,11 +5563,11 @@ importers: specifier: workspace:* version: link:../../../../../astro react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/integrations/mdx/test/fixtures/mdx-plus-react-errors: dependencies: @@ -5581,11 +5581,11 @@ importers: specifier: workspace:* version: link:../../../../../astro react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.2.4 react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) packages/integrations/mdx/test/fixtures/mdx-vite-env-vars: dependencies: @@ -10723,9 +10723,17 @@ packages: peerDependencies: '@types/react': ^18.0.0 + '@types/react-dom@19.2.5': + resolution: {integrity: sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==} + peerDependencies: + '@types/react': ^19.2.0 + '@types/react@18.3.28': resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + '@types/react@19.2.18': + resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==} + '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} @@ -20262,11 +20270,19 @@ snapshots: dependencies: '@types/react': 18.3.28 + '@types/react-dom@19.2.5(@types/react@19.2.18)': + dependencies: + '@types/react': 19.2.18 + '@types/react@18.3.28': dependencies: '@types/prop-types': 15.7.15 csstype: 3.2.3 + '@types/react@19.2.18': + dependencies: + csstype: 3.2.3 + '@types/retry@0.12.0': {} '@types/retry@0.12.2': {} From 4c422e2603170db2d9fcf476de91763d618d6e9a Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 31 Aug 2026 14:16:49 +0100 Subject: [PATCH 14/19] ci: use github runner (#17875) --- .github/workflows/issue-needs-repro.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-needs-repro.yml b/.github/workflows/issue-needs-repro.yml index cff70f1d7dbc..52247c734655 100644 --- a/.github/workflows/issue-needs-repro.yml +++ b/.github/workflows/issue-needs-repro.yml @@ -11,7 +11,7 @@ concurrency: jobs: reply-labeled: if: github.repository == 'withastro/astro' - runs-on: depot-ubuntu-24.04-arm-small + runs-on: ubuntu-latest steps: - name: Remove triaging label if: >- From 1870eea746185f711a88efaec1dc52d84d9ac4ad Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 31 Aug 2026 15:22:11 +0100 Subject: [PATCH 15/19] ci: use smaller runner for small jobs (#17876) --- .github/workflows/issue-needs-repro.yml | 2 +- .github/workflows/issue-state-issue.yml | 2 +- .github/workflows/issue-wontfix.yml | 2 +- .github/workflows/label.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/issue-needs-repro.yml b/.github/workflows/issue-needs-repro.yml index 52247c734655..34113ffb8af1 100644 --- a/.github/workflows/issue-needs-repro.yml +++ b/.github/workflows/issue-needs-repro.yml @@ -11,7 +11,7 @@ concurrency: jobs: reply-labeled: if: github.repository == 'withastro/astro' - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - name: Remove triaging label if: >- diff --git a/.github/workflows/issue-state-issue.yml b/.github/workflows/issue-state-issue.yml index 0e2c76a7ebd4..e02cf37c0625 100644 --- a/.github/workflows/issue-state-issue.yml +++ b/.github/workflows/issue-state-issue.yml @@ -7,7 +7,7 @@ on: jobs: close-issues: if: github.repository == 'withastro/astro' - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - name: Checkout repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 diff --git a/.github/workflows/issue-wontfix.yml b/.github/workflows/issue-wontfix.yml index f720ca000c7e..501a61615cc0 100644 --- a/.github/workflows/issue-wontfix.yml +++ b/.github/workflows/issue-wontfix.yml @@ -7,7 +7,7 @@ on: jobs: wontfix: if: github.event.label.name == 'wontfix' - runs-on: ubuntu-latest + runs-on: ubuntu-slim steps: - name: Comment and close issue env: diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index da496de82047..17ffe92a3bc8 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -7,7 +7,7 @@ on: jobs: triage: - runs-on: ubuntu-latest + runs-on: ubuntu-slim if: github.repository_owner == 'withastro' steps: - uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0 From 10c7e636cd14232473ae856d7e22e886f5c65689 Mon Sep 17 00:00:00 2001 From: "astro-factory[bot]" <316791938+astro-factory[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 10:23:07 -0400 Subject: [PATCH 16/19] fix(build): replace manifest placeholder when SSR output is minified (#17874) Rolldown's minifier rewrites string literals as template literals (backticks), but the regex matching the `@@ASTRO_MANIFEST_REPLACE@@` placeholder only accepted single and double quotes. Add backtick to the character class in the manifest and server islands replacement regexes. Fixes #17843 Co-authored-by: factory[bot] --- .changeset/beige-times-dream.md | 5 + .../src/core/build/plugins/plugin-manifest.ts | 3 +- .../vite-plugin-server-islands.ts | 5 +- .../test/units/build/plugin-manifest.test.ts | 127 ++++++++++++++++++ 4 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 .changeset/beige-times-dream.md create mode 100644 packages/astro/test/units/build/plugin-manifest.test.ts diff --git a/.changeset/beige-times-dream.md b/.changeset/beige-times-dream.md new file mode 100644 index 000000000000..0e671a4a001e --- /dev/null +++ b/.changeset/beige-times-dream.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes SSR manifest placeholder not being replaced when the server build is minified, which caused a runtime `Invalid URL` crash at server boot diff --git a/packages/astro/src/core/build/plugins/plugin-manifest.ts b/packages/astro/src/core/build/plugins/plugin-manifest.ts index 2813a4318da9..282d666e7001 100644 --- a/packages/astro/src/core/build/plugins/plugin-manifest.ts +++ b/packages/astro/src/core/build/plugins/plugin-manifest.ts @@ -62,7 +62,8 @@ import { sessionConfigToManifest } from '../../session/utils.js'; */ export const MANIFEST_REPLACE = '@@ASTRO_MANIFEST_REPLACE@@'; -const replaceExp = new RegExp(`['"]${MANIFEST_REPLACE}['"]`, 'g'); +// Backtick included: Rolldown's minifier may rewrite string literals as template literals. +const replaceExp = new RegExp(`['"\`]${MANIFEST_REPLACE}['"\`]`, 'g'); /** * Post-build hook that injects the computed manifest into bundled chunks. diff --git a/packages/astro/src/core/server-islands/vite-plugin-server-islands.ts b/packages/astro/src/core/server-islands/vite-plugin-server-islands.ts index 64d467545e78..257cbc49ae12 100644 --- a/packages/astro/src/core/server-islands/vite-plugin-server-islands.ts +++ b/packages/astro/src/core/server-islands/vite-plugin-server-islands.ts @@ -11,8 +11,9 @@ const RESOLVED_SERVER_ISLAND_MANIFEST = '\0' + SERVER_ISLAND_MANIFEST; const serverIslandPlaceholderMap = "'$$server-islands-map$$'"; const serverIslandPlaceholderNameMap = "'$$server-islands-name-map$$'"; export const SERVER_ISLAND_MAP_MARKER = '$$server-islands-map$$'; -const serverIslandMapReplaceExp = /['"]\$\$server-islands-map\$\$['"]/g; -const serverIslandNameMapReplaceExp = /['"]\$\$server-islands-name-map\$\$['"]/g; +// Backtick included: Rolldown's minifier may rewrite string literals as template literals. +const serverIslandMapReplaceExp = /['"`]\$\$server-islands-map\$\$['"`]/g; +const serverIslandNameMapReplaceExp = /['"`]\$\$server-islands-name-map\$\$['"`]/g; export function vitePluginServerIslands({ settings, diff --git a/packages/astro/test/units/build/plugin-manifest.test.ts b/packages/astro/test/units/build/plugin-manifest.test.ts new file mode 100644 index 000000000000..e3ffb1043ebe --- /dev/null +++ b/packages/astro/test/units/build/plugin-manifest.test.ts @@ -0,0 +1,127 @@ +import assert from 'node:assert/strict'; +import { promises as fs } from 'node:fs'; +import path from 'node:path'; +import { describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; +import type { Plugin } from 'vite'; +import { AstroBuilder } from '../../../dist/core/build/index.js'; +import { MANIFEST_REPLACE } from '../../../dist/core/build/plugins/plugin-manifest.js'; +import { parseRoute } from '../../../dist/core/routing/parse-route.js'; +import { createBasicSettings, defaultLogger } from '../test-utils.ts'; +import { virtualAstroModules } from './test-helpers.ts'; + +async function readFilesRecursive(dir: string): Promise { + const entries = await fs.readdir(dir, { withFileTypes: true }); + const files = await Promise.all( + entries.map(async (entry) => { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + return readFilesRecursive(fullPath); + } + return [fullPath]; + }), + ); + return files.flat(); +} + +/** + * Vite plugin that enables minification for the SSR environment. + * This simulates what an integration would do via `astro:build:setup`. + */ +function enableSsrMinification(): Plugin { + return { + name: 'test-enable-ssr-minification', + configEnvironment(environmentName, config) { + if (environmentName === 'ssr') { + config.build ??= {}; + config.build.minify = true; + } + }, + }; +} + +describe('Build: Manifest injection', () => { + it('replaces manifest placeholder when server build is minified', async () => { + const root = new URL('./_temp-fixtures/', import.meta.url); + + const settings = await createBasicSettings({ + root: fileURLToPath(root), + output: 'server', + adapter: { + name: 'test-adapter', + hooks: { + 'astro:config:done': ({ setAdapter }) => { + setAdapter({ + name: 'test-adapter', + serverEntrypoint: 'astro/app', + exports: ['manifest', 'createApp'], + supportedAstroFeatures: { + serverOutput: 'stable', + }, + adapterFeatures: { + buildOutput: 'server', + }, + }); + }, + }, + }, + vite: { + plugins: [ + virtualAstroModules(root, { + 'src/pages/index.astro': [ + '---', + '---', + '', + 'Test', + '

Hello

', + '', + ].join('\n'), + }), + enableSsrMinification(), + ], + }, + }); + + const routesList = { + routes: [ + parseRoute('index.astro', settings, { + component: 'src/pages/index.astro', + prerender: false, + }), + ], + }; + + process.env.ASTRO_KEY = 'eKBaVEuI7YjfanEXHuJe/pwZKKt3LkAHeMxvTU7aR0M='; + + try { + const builder = new AstroBuilder(settings, { + logger: defaultLogger, + mode: 'production', + runtimeMode: 'production', + routesList, + sync: false, + }); + await builder.run(); + } finally { + delete process.env.ASTRO_KEY; + } + + const serverOutputDir = fileURLToPath(settings.config.build.server); + const outputFiles = await readFilesRecursive(serverOutputDir); + + // Find all server output files and verify none contain the unsubstituted placeholder + let foundManifestChunk = false; + for (const file of outputFiles) { + if (!file.endsWith('.mjs') && !file.endsWith('.js')) continue; + const content = await fs.readFile(file, 'utf-8'); + if (content.includes('deserializeManifest') || content.includes('_deserializeManifest')) { + foundManifestChunk = true; + assert.ok( + !content.includes(MANIFEST_REPLACE), + `Manifest placeholder should be replaced in minified output but was found in ${path.basename(file)}`, + ); + } + } + assert.ok(foundManifestChunk, 'Should find at least one chunk containing deserializeManifest'); + }); +}); From 4f002c953e0dc47da539e8b661e009e61fa1995e Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 31 Aug 2026 10:30:43 -0400 Subject: [PATCH 17/19] chore: configure Factory PR writer skill (#17877) --- .github/factory.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/factory.yml b/.github/factory.yml index 8a258440fe4b..006b00cf371b 100644 --- a/.github/factory.yml +++ b/.github/factory.yml @@ -20,6 +20,7 @@ review: triage: enabled: true skill: .agents/skills/triage + prWriterSkill: .agents/skills/astro-pr-writer model: anthropic/claude-opus-4-6 verificationModel: anthropic/claude-sonnet-4-6 autoPrOnFix: false From 76eff3d5fbc5f940acb1dcb341d4b1c9d95fa2a3 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Mon, 31 Aug 2026 16:40:43 +0100 Subject: [PATCH 18/19] fix: cache headers (#17878) * fix: cache headers * Apply suggestion from @matthewp Co-authored-by: Matthew Phillips * revert change * update tests * update tests --------- Co-authored-by: Matthew Phillips --- .changeset/cold-taxis-talk.md | 5 +++ .../astro/src/core/cache/runtime/cache.ts | 9 ++++++ .../astro/test/units/cache/app-cache.test.ts | 24 ++++++++++++++ .../astro/test/units/cache/runtime.test.ts | 31 +++++++++++++++++++ .../cloudflare/test/cache-provider.test.ts | 1 + 5 files changed, 70 insertions(+) create mode 100644 .changeset/cold-taxis-talk.md diff --git a/.changeset/cold-taxis-talk.md b/.changeset/cold-taxis-talk.md new file mode 100644 index 000000000000..b2d8ff8246e5 --- /dev/null +++ b/.changeset/cold-taxis-talk.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes browser heuristic caching for cached responses that include `Last-Modified` or `ETag` validators diff --git a/packages/astro/src/core/cache/runtime/cache.ts b/packages/astro/src/core/cache/runtime/cache.ts index f414c266d8df..c63697ba8e7a 100644 --- a/packages/astro/src/core/cache/runtime/cache.ts +++ b/packages/astro/src/core/cache/runtime/cache.ts @@ -122,6 +122,15 @@ export class AstroCache implements CacheLike { for (const [key, value] of headers) { response.headers.set(key, value); } + if ( + !response.headers.has('Cache-Control') && + !response.headers.has('Expires') && + (response.headers.has('Last-Modified') || response.headers.has('ETag')) + ) { + // `no-cache` requires revalidation before a stored response is reused. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control#no-cache + response.headers.set('Cache-Control', 'no-cache'); + } } /** @internal */ diff --git a/packages/astro/test/units/cache/app-cache.test.ts b/packages/astro/test/units/cache/app-cache.test.ts index 751fafefa40b..cb33c62e01fd 100644 --- a/packages/astro/test/units/cache/app-cache.test.ts +++ b/packages/astro/test/units/cache/app-cache.test.ts @@ -35,6 +35,18 @@ function cachedEndpoint() { ); } +function cachedValidatorEndpoint() { + return createEndpoint( + { + GET: (ctx: APIContext) => { + ctx.cache.set({ maxAge: 300, lastModified: new Date('2025-06-01T12:00:00Z') }); + return Response.json({ timestamp: Date.now() }); + }, + }, + { route: '/cached-validator' }, + ); +} + function headCachedEndpoint() { return createEndpoint( { @@ -209,6 +221,18 @@ describe('context.cache through App pipeline', () => { assert.equal(second.headers.get('Cache-Tag'), null); }); + it('retains no-cache on memory cache misses and hits with validators', async () => { + const overrides = createCacheManifestOverrides(); + const app = createTestApp([cachedValidatorEndpoint()], overrides); + + const first = await app.render(new Request('http://localhost/cached-validator')); + assert.equal(first.headers.get('Cache-Control'), 'no-cache'); + + const second = await app.render(new Request('http://localhost/cached-validator')); + assert.equal(second.headers.get('X-Astro-Cache'), 'HIT'); + assert.equal(second.headers.get('Cache-Control'), 'no-cache'); + }); + it('uncached route passes through without cache headers', async () => { const overrides = createCacheManifestOverrides(); const app = createTestApp([noCacheEndpoint()], overrides); diff --git a/packages/astro/test/units/cache/runtime.test.ts b/packages/astro/test/units/cache/runtime.test.ts index 170ed447c061..e789a65b8ad3 100644 --- a/packages/astro/test/units/cache/runtime.test.ts +++ b/packages/astro/test/units/cache/runtime.test.ts @@ -264,6 +264,37 @@ describe('applyCacheHeaders()', () => { assert.equal(response.headers.get('ETag'), '"v1"'); }); + it('adds no-cache when the response has a validator', () => { + const cache = new AstroCache(null); + cache.set({ maxAge: 60, lastModified: new Date('2025-06-01T12:00:00Z') }); + + const response = new Response('test'); + applyCacheHeaders(cache, response, dummyRequest); + assert.equal(response.headers.get('Cache-Control'), 'no-cache'); + }); + + it('does not add no-cache when the response has an Expires header', () => { + const cache = new AstroCache(null); + cache.set({ maxAge: 60, lastModified: new Date('2025-06-01T12:00:00Z') }); + + const response = new Response('test', { + headers: { Expires: 'Sun, 01 Jun 2025 12:01:00 GMT' }, + }); + applyCacheHeaders(cache, response, dummyRequest); + assert.equal(response.headers.get('Cache-Control'), null); + }); + + it('does not overwrite an explicit response Cache-Control header', () => { + const cache = new AstroCache(null); + cache.set({ maxAge: 60, lastModified: new Date('2025-06-01T12:00:00Z') }); + + const response = new Response('test', { + headers: { 'Cache-Control': 'private, max-age=60' }, + }); + applyCacheHeaders(cache, response, dummyRequest); + assert.equal(response.headers.get('Cache-Control'), 'private, max-age=60'); + }); + it('uses provider.setHeaders() when available', () => { const customHeaders = new Headers({ 'X-Custom-Cache': 'hit' }); const provider = createMockProvider({ diff --git a/packages/integrations/cloudflare/test/cache-provider.test.ts b/packages/integrations/cloudflare/test/cache-provider.test.ts index 2fb211b55767..b6875be7da13 100644 --- a/packages/integrations/cloudflare/test/cache-provider.test.ts +++ b/packages/integrations/cloudflare/test/cache-provider.test.ts @@ -98,6 +98,7 @@ describe('Cloudflare cache provider', () => { const lastModified = res.headers.get('Last-Modified'); assert.equal(lastModified, new Date('2026-01-15T10:00:00.000Z').toUTCString()); + assert.equal(res.headers.get('Cache-Control'), 'no-cache'); const etag = res.headers.get('ETag'); assert.ok(etag, 'ETag header should be present'); From 2fdf731428aa738d5dcf3041b4e78eb9d036968c Mon Sep 17 00:00:00 2001 From: "Houston (Bot)" <108291165+astrobot-houston@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:34:15 -0700 Subject: [PATCH 19/19] [ci] release (#17849) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../astro-markdown-remark-peer-range.md | 5 -- .changeset/beige-times-dream.md | 5 -- .changeset/clear-pets-help.md | 5 -- .changeset/cold-taxis-talk.md | 5 -- .changeset/common-mails-pump.md | 5 -- .changeset/content-image-empty-srcset.md | 5 -- .../fix-check-astro-project-references.md | 5 -- .changeset/great-bags-flash.md | 5 -- .changeset/internal-helpers-mdx-export.md | 5 -- .changeset/light-pandas-repeat.md | 5 -- .changeset/markdown-processors-own-mdx.md | 8 -- .../mdx-extend-markdown-config-processor.md | 5 -- .../mdx-legacy-plugin-options-warning.md | 5 -- .../mdx-processor-version-requirements.md | 15 ---- .changeset/nine-streets-retire.md | 5 -- .changeset/sour-poems-wave.md | 5 -- .changeset/unified-recma-plugins.md | 5 -- .changeset/vercel-deps.md | 6 -- examples/advanced-routing/package.json | 4 +- examples/basics/package.json | 2 +- examples/blog/package.json | 6 +- examples/component/package.json | 2 +- examples/container-with-vitest/package.json | 4 +- examples/framework-alpine/package.json | 2 +- examples/framework-multiple/package.json | 6 +- examples/framework-preact/package.json | 4 +- examples/framework-react/package.json | 4 +- examples/framework-solid/package.json | 2 +- examples/framework-svelte/package.json | 2 +- examples/framework-vue/package.json | 2 +- examples/hackernews/package.json | 4 +- examples/integration/package.json | 2 +- examples/minimal/package.json | 2 +- examples/portfolio/package.json | 2 +- examples/ssr/package.json | 4 +- examples/starlog/package.json | 2 +- examples/toolbar-app/package.json | 2 +- examples/with-markdoc/package.json | 4 +- examples/with-mdx/package.json | 6 +- examples/with-nanostores/package.json | 4 +- examples/with-tailwindcss/package.json | 4 +- examples/with-vitest/package.json | 2 +- packages/astro/CHANGELOG.md | 22 ++++++ packages/astro/package.json | 2 +- packages/integrations/cloudflare/CHANGELOG.md | 12 +++ packages/integrations/cloudflare/package.json | 2 +- packages/integrations/markdoc/CHANGELOG.md | 7 ++ packages/integrations/markdoc/package.json | 2 +- packages/integrations/mdx/CHANGELOG.md | 26 +++++++ packages/integrations/mdx/package.json | 2 +- packages/integrations/netlify/CHANGELOG.md | 8 ++ packages/integrations/netlify/package.json | 2 +- packages/integrations/node/CHANGELOG.md | 7 ++ packages/integrations/node/package.json | 2 +- packages/integrations/preact/CHANGELOG.md | 7 ++ packages/integrations/preact/package.json | 2 +- packages/integrations/react/CHANGELOG.md | 7 ++ packages/integrations/react/package.json | 2 +- packages/integrations/sitemap/CHANGELOG.md | 6 ++ packages/integrations/sitemap/package.json | 2 +- packages/integrations/vercel/CHANGELOG.md | 9 +++ packages/integrations/vercel/package.json | 2 +- packages/internal-helpers/CHANGELOG.md | 6 ++ packages/internal-helpers/package.json | 2 +- .../language-server/CHANGELOG.md | 6 ++ .../language-server/package.json | 2 +- packages/markdown/remark/CHANGELOG.md | 15 ++++ packages/markdown/remark/package.json | 2 +- packages/markdown/satteri/CHANGELOG.md | 13 ++++ packages/markdown/satteri/package.json | 2 +- pnpm-lock.yaml | 78 +++++++++---------- 71 files changed, 243 insertions(+), 196 deletions(-) delete mode 100644 .changeset/astro-markdown-remark-peer-range.md delete mode 100644 .changeset/beige-times-dream.md delete mode 100644 .changeset/clear-pets-help.md delete mode 100644 .changeset/cold-taxis-talk.md delete mode 100644 .changeset/common-mails-pump.md delete mode 100644 .changeset/content-image-empty-srcset.md delete mode 100644 .changeset/fix-check-astro-project-references.md delete mode 100644 .changeset/great-bags-flash.md delete mode 100644 .changeset/internal-helpers-mdx-export.md delete mode 100644 .changeset/light-pandas-repeat.md delete mode 100644 .changeset/markdown-processors-own-mdx.md delete mode 100644 .changeset/mdx-extend-markdown-config-processor.md delete mode 100644 .changeset/mdx-legacy-plugin-options-warning.md delete mode 100644 .changeset/mdx-processor-version-requirements.md delete mode 100644 .changeset/nine-streets-retire.md delete mode 100644 .changeset/sour-poems-wave.md delete mode 100644 .changeset/unified-recma-plugins.md delete mode 100644 .changeset/vercel-deps.md diff --git a/.changeset/astro-markdown-remark-peer-range.md b/.changeset/astro-markdown-remark-peer-range.md deleted file mode 100644 index 14d4516d93b1..000000000000 --- a/.changeset/astro-markdown-remark-peer-range.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes `@astrojs/markdown-remark` being pinned to an exact version. diff --git a/.changeset/beige-times-dream.md b/.changeset/beige-times-dream.md deleted file mode 100644 index 0e671a4a001e..000000000000 --- a/.changeset/beige-times-dream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes SSR manifest placeholder not being replaced when the server build is minified, which caused a runtime `Invalid URL` crash at server boot diff --git a/.changeset/clear-pets-help.md b/.changeset/clear-pets-help.md deleted file mode 100644 index c6f87af4b639..000000000000 --- a/.changeset/clear-pets-help.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes a case where the logger was improperly initialized at runtime in dev. diff --git a/.changeset/cold-taxis-talk.md b/.changeset/cold-taxis-talk.md deleted file mode 100644 index b2d8ff8246e5..000000000000 --- a/.changeset/cold-taxis-talk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes browser heuristic caching for cached responses that include `Last-Modified` or `ETag` validators diff --git a/.changeset/common-mails-pump.md b/.changeset/common-mails-pump.md deleted file mode 100644 index 804b3e8aee2e..000000000000 --- a/.changeset/common-mails-pump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes prerender conflict warnings to correctly identify the route that first rendered a duplicate pathname, instead of misattributing the conflict to an unrelated route that merely matches the URL pattern diff --git a/.changeset/content-image-empty-srcset.md b/.changeset/content-image-empty-srcset.md deleted file mode 100644 index 9d2ffa77ed76..000000000000 --- a/.changeset/content-image-empty-srcset.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes Markdown images in content collections rendering an empty `srcset` attribute when no responsive candidates are generated. diff --git a/.changeset/fix-check-astro-project-references.md b/.changeset/fix-check-astro-project-references.md deleted file mode 100644 index e6371e9aa164..000000000000 --- a/.changeset/fix-check-astro-project-references.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/language-server': patch ---- - -Fixes `astro check` silently skipping `.astro` files that are only reachable through a TypeScript project reference (a tsconfig referenced via `references` in another tsconfig). These files are now checked and reported like any other `.astro` file. diff --git a/.changeset/great-bags-flash.md b/.changeset/great-bags-flash.md deleted file mode 100644 index f080628bb11d..000000000000 --- a/.changeset/great-bags-flash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/cloudflare': patch ---- - -Added `@astrojs/prism` to the list of dependencies to optimise. The dev server is now faster for sites that use Prism as code highlighter. diff --git a/.changeset/internal-helpers-mdx-export.md b/.changeset/internal-helpers-mdx-export.md deleted file mode 100644 index 25a1215be80a..000000000000 --- a/.changeset/internal-helpers-mdx-export.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/internal-helpers': minor ---- - -Adds an `@astrojs/internal-helpers/mdx` entrypoint with the shared helpers the Markdown processor packages use to render `.mdx` files. diff --git a/.changeset/light-pandas-repeat.md b/.changeset/light-pandas-repeat.md deleted file mode 100644 index 12ff8d673550..000000000000 --- a/.changeset/light-pandas-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'astro': patch ---- - -Fixes a bug where editing a content collection entry during `astro dev` on Windows kept serving stale content until the dev server was restarted. The data store now notifies the dev server directly after each write instead of relying only on the file watcher, which can miss the atomic rename that commits the write on some platforms. diff --git a/.changeset/markdown-processors-own-mdx.md b/.changeset/markdown-processors-own-mdx.md deleted file mode 100644 index b839f53c5146..000000000000 --- a/.changeset/markdown-processors-own-mdx.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@astrojs/markdown-remark': minor -'@astrojs/markdown-satteri': minor ---- - -Adds MDX rendering to the `unified()` and `satteri()` processors. - -Both processors now compile `.mdx` files themselves. You still need to install `@astrojs/mdx` to add MDX support to your project. diff --git a/.changeset/mdx-extend-markdown-config-processor.md b/.changeset/mdx-extend-markdown-config-processor.md deleted file mode 100644 index 47c1723b95e7..000000000000 --- a/.changeset/mdx-extend-markdown-config-processor.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/mdx': patch ---- - -Fixes `.mdx` files still using `markdown.processor` when `extendMarkdownConfig` is `false`. They now use a clean default processor instead; pass `mdx({ processor })` to choose one explicitly. diff --git a/.changeset/mdx-legacy-plugin-options-warning.md b/.changeset/mdx-legacy-plugin-options-warning.md deleted file mode 100644 index b142fa7e4f5e..000000000000 --- a/.changeset/mdx-legacy-plugin-options-warning.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/mdx': patch ---- - -Adds a warning when the deprecated `remarkPlugins`, `rehypePlugins`, `recmaPlugins` and `remarkRehype` options are ignored because your Markdown processor does not run them. They still apply when your processor is `unified()`, and were previously dropped silently otherwise. diff --git a/.changeset/mdx-processor-version-requirements.md b/.changeset/mdx-processor-version-requirements.md deleted file mode 100644 index 541708155ef6..000000000000 --- a/.changeset/mdx-processor-version-requirements.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@astrojs/mdx': major ---- - -Moves MDX file processing to the Markdown processors. - -'@astrojs/mdx' is still required to add MDX support to your project. However, it now delegates the MDX files processing to Markdown processors. - -#### What should I do? - -If you haven't explicitly installed a Markdown processor, you don't need to do anything. - -Otherwise, ensure that your configured Markdown processor uses the following version: -- `@astrojs/markdown-satteri` 0.4.0 or later if you use `satteri()` -- `@astrojs/markdown-remark` 7.3.0 or later if you use `unified()` diff --git a/.changeset/nine-streets-retire.md b/.changeset/nine-streets-retire.md deleted file mode 100644 index 8b6f2159130d..000000000000 --- a/.changeset/nine-streets-retire.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/cloudflare': patch ---- - -Fixes React SSR failures on the first Cloudflare dev request when JSON logging is enabled diff --git a/.changeset/sour-poems-wave.md b/.changeset/sour-poems-wave.md deleted file mode 100644 index 3915e545446d..000000000000 --- a/.changeset/sour-poems-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/sitemap': patch ---- - -Fixes the sitemap outputting a URL with an empty path for the homepage (e.g. `https://example.com` instead of `https://example.com/`) when `trailingSlash` is set to `"never"` or `build.format` is set to `"file"` diff --git a/.changeset/unified-recma-plugins.md b/.changeset/unified-recma-plugins.md deleted file mode 100644 index 18fe94c681e3..000000000000 --- a/.changeset/unified-recma-plugins.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@astrojs/markdown-remark': minor ---- - -Adds a `recmaPlugins` option to `unified()` for adding recma (estree/JSX) plugins to the MDX compiler. diff --git a/.changeset/vercel-deps.md b/.changeset/vercel-deps.md deleted file mode 100644 index b55d23b81794..000000000000 --- a/.changeset/vercel-deps.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@astrojs/vercel": patch ---- - -Updates dependency `@vercel/analytics` to v2. See the [changelog](https://github.com/vercel/analytics/releases/tag/v2.0.0) for more details. -Updates dependency `@vercel/routing-utils` to v6. See the [changelog](https://github.com/vercel/vercel/blob/@vercel/routing-utils@6.4.0/packages/routing-utils/CHANGELOG.md#600) for more details. diff --git a/examples/advanced-routing/package.json b/examples/advanced-routing/package.json index f391cb945779..48ccf7ed627e 100644 --- a/examples/advanced-routing/package.json +++ b/examples/advanced-routing/package.json @@ -13,8 +13,8 @@ "astro": "astro" }, "dependencies": { - "@astrojs/node": "^11.1.4", - "astro": "^7.2.9", + "@astrojs/node": "^11.1.5", + "astro": "^7.2.10", "hono": "^4.12.14" }, "allowScripts": { diff --git a/examples/basics/package.json b/examples/basics/package.json index 4ad742083f03..5a41b28c8ac6 100644 --- a/examples/basics/package.json +++ b/examples/basics/package.json @@ -13,7 +13,7 @@ "astro": "astro" }, "dependencies": { - "astro": "^7.2.9" + "astro": "^7.2.10" }, "allowScripts": { "esbuild": true diff --git a/examples/blog/package.json b/examples/blog/package.json index 9312fb844add..794231c27848 100644 --- a/examples/blog/package.json +++ b/examples/blog/package.json @@ -13,10 +13,10 @@ "astro": "astro" }, "dependencies": { - "@astrojs/mdx": "^7.0.8", + "@astrojs/mdx": "^8.0.0", "@astrojs/rss": "^4.0.19", - "@astrojs/sitemap": "^3.7.3", - "astro": "^7.2.9", + "@astrojs/sitemap": "^3.7.4", + "astro": "^7.2.10", "sharp": "^0.35.0" }, "allowScripts": { diff --git a/examples/component/package.json b/examples/component/package.json index 9ec1efcc852b..ab75777c8d71 100644 --- a/examples/component/package.json +++ b/examples/component/package.json @@ -18,7 +18,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^7.2.9" + "astro": "^7.2.10" }, "peerDependencies": { "astro": "^5.0.0 || ^6.0.0" diff --git a/examples/container-with-vitest/package.json b/examples/container-with-vitest/package.json index dc0216148585..19bacf8156de 100644 --- a/examples/container-with-vitest/package.json +++ b/examples/container-with-vitest/package.json @@ -14,8 +14,8 @@ "test": "vitest run" }, "dependencies": { - "@astrojs/react": "^6.0.4", - "astro": "^7.2.9", + "@astrojs/react": "^6.0.5", + "astro": "^7.2.10", "react": "^18.3.1", "react-dom": "^18.3.1", "vitest": "^4.1.0" diff --git a/examples/framework-alpine/package.json b/examples/framework-alpine/package.json index d79c9264469f..db74cd1b44de 100644 --- a/examples/framework-alpine/package.json +++ b/examples/framework-alpine/package.json @@ -16,7 +16,7 @@ "@astrojs/alpinejs": "^1.0.0", "@types/alpinejs": "^3.13.11", "alpinejs": "^3.15.8", - "astro": "^7.2.9" + "astro": "^7.2.10" }, "allowScripts": { "esbuild": true diff --git a/examples/framework-multiple/package.json b/examples/framework-multiple/package.json index 448dc85b9182..6fd3d3651971 100644 --- a/examples/framework-multiple/package.json +++ b/examples/framework-multiple/package.json @@ -13,14 +13,14 @@ "astro": "astro" }, "dependencies": { - "@astrojs/preact": "^6.0.4", - "@astrojs/react": "^6.0.4", + "@astrojs/preact": "^6.0.5", + "@astrojs/react": "^6.0.5", "@astrojs/solid-js": "^7.0.2", "@astrojs/svelte": "^9.0.1", "@astrojs/vue": "^7.0.2", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", - "astro": "^7.2.9", + "astro": "^7.2.10", "preact": "^10.28.4", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/framework-preact/package.json b/examples/framework-preact/package.json index 04700b842c19..8c6bfbb517b4 100644 --- a/examples/framework-preact/package.json +++ b/examples/framework-preact/package.json @@ -13,9 +13,9 @@ "astro": "astro" }, "dependencies": { - "@astrojs/preact": "^6.0.4", + "@astrojs/preact": "^6.0.5", "@preact/signals": "^2.8.1", - "astro": "^7.2.9", + "astro": "^7.2.10", "preact": "^10.28.4" }, "allowScripts": { diff --git a/examples/framework-react/package.json b/examples/framework-react/package.json index 1666401351ca..f5963b8f59b5 100644 --- a/examples/framework-react/package.json +++ b/examples/framework-react/package.json @@ -13,10 +13,10 @@ "astro": "astro" }, "dependencies": { - "@astrojs/react": "^6.0.4", + "@astrojs/react": "^6.0.5", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", - "astro": "^7.2.9", + "astro": "^7.2.10", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/framework-solid/package.json b/examples/framework-solid/package.json index 8b2f3fd223eb..83775bec46a8 100644 --- a/examples/framework-solid/package.json +++ b/examples/framework-solid/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@astrojs/solid-js": "^7.0.2", - "astro": "^7.2.9", + "astro": "^7.2.10", "solid-js": "^1.9.11" }, "allowScripts": { diff --git a/examples/framework-svelte/package.json b/examples/framework-svelte/package.json index 4f54355d043c..0744354776e5 100644 --- a/examples/framework-svelte/package.json +++ b/examples/framework-svelte/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@astrojs/svelte": "^9.0.1", - "astro": "^7.2.9", + "astro": "^7.2.10", "svelte": "^5.53.5" }, "allowScripts": { diff --git a/examples/framework-vue/package.json b/examples/framework-vue/package.json index 366b874dc6c3..98adcf605ddb 100644 --- a/examples/framework-vue/package.json +++ b/examples/framework-vue/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@astrojs/vue": "^7.0.2", - "astro": "^7.2.9", + "astro": "^7.2.10", "vue": "^3.5.29" }, "allowScripts": { diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json index 066875c2daa4..2f35a0d8d2de 100644 --- a/examples/hackernews/package.json +++ b/examples/hackernews/package.json @@ -13,8 +13,8 @@ "astro": "astro" }, "dependencies": { - "@astrojs/node": "^11.1.4", - "astro": "^7.2.9" + "@astrojs/node": "^11.1.5", + "astro": "^7.2.10" }, "allowScripts": { "esbuild": true diff --git a/examples/integration/package.json b/examples/integration/package.json index c8974f7cb3b2..a1b49d922000 100644 --- a/examples/integration/package.json +++ b/examples/integration/package.json @@ -18,7 +18,7 @@ ], "scripts": {}, "devDependencies": { - "astro": "^7.2.9" + "astro": "^7.2.10" }, "peerDependencies": { "astro": "^4.0.0" diff --git a/examples/minimal/package.json b/examples/minimal/package.json index bcc11629237d..61c2edb4733e 100644 --- a/examples/minimal/package.json +++ b/examples/minimal/package.json @@ -13,7 +13,7 @@ "astro": "astro" }, "dependencies": { - "astro": "^7.2.9" + "astro": "^7.2.10" }, "allowScripts": { "esbuild": true diff --git a/examples/portfolio/package.json b/examples/portfolio/package.json index bd4173c09df0..fea387bde777 100644 --- a/examples/portfolio/package.json +++ b/examples/portfolio/package.json @@ -13,7 +13,7 @@ "astro": "astro" }, "dependencies": { - "astro": "^7.2.9" + "astro": "^7.2.10" }, "allowScripts": { "esbuild": true diff --git a/examples/ssr/package.json b/examples/ssr/package.json index 077c445c090d..7ce175bb0930 100644 --- a/examples/ssr/package.json +++ b/examples/ssr/package.json @@ -14,9 +14,9 @@ "server": "node dist/server/entry.mjs" }, "dependencies": { - "@astrojs/node": "^11.1.4", + "@astrojs/node": "^11.1.5", "@astrojs/svelte": "^9.0.1", - "astro": "^7.2.9", + "astro": "^7.2.10", "svelte": "^5.53.5" }, "allowScripts": { diff --git a/examples/starlog/package.json b/examples/starlog/package.json index 8a2ae5045935..010e0374a576 100644 --- a/examples/starlog/package.json +++ b/examples/starlog/package.json @@ -9,7 +9,7 @@ "astro": "astro" }, "dependencies": { - "astro": "^7.2.9", + "astro": "^7.2.10", "sass": "^1.97.3", "sharp": "^0.35.0" }, diff --git a/examples/toolbar-app/package.json b/examples/toolbar-app/package.json index fcc3ec41e8de..6bb16673100a 100644 --- a/examples/toolbar-app/package.json +++ b/examples/toolbar-app/package.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@types/node": "^22.10.6", - "astro": "^7.2.9" + "astro": "^7.2.10" }, "engines": { "node": ">=22.12.0" diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json index 4dfbc972fc89..191c93e20bd6 100644 --- a/examples/with-markdoc/package.json +++ b/examples/with-markdoc/package.json @@ -13,8 +13,8 @@ "astro": "astro" }, "dependencies": { - "@astrojs/markdoc": "^2.0.8", - "astro": "^7.2.9" + "@astrojs/markdoc": "^2.0.9", + "astro": "^7.2.10" }, "allowScripts": { "esbuild": true diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json index e5211f98cdb7..ebcbb9f60440 100644 --- a/examples/with-mdx/package.json +++ b/examples/with-mdx/package.json @@ -13,9 +13,9 @@ "astro": "astro" }, "dependencies": { - "@astrojs/mdx": "^7.0.8", - "@astrojs/preact": "^6.0.4", - "astro": "^7.2.9", + "@astrojs/mdx": "^8.0.0", + "@astrojs/preact": "^6.0.5", + "astro": "^7.2.10", "preact": "^10.28.4" }, "allowScripts": { diff --git a/examples/with-nanostores/package.json b/examples/with-nanostores/package.json index cb5d622fa40e..6f1fdcf2283d 100644 --- a/examples/with-nanostores/package.json +++ b/examples/with-nanostores/package.json @@ -13,9 +13,9 @@ "astro": "astro" }, "dependencies": { - "@astrojs/preact": "^6.0.4", + "@astrojs/preact": "^6.0.5", "@nanostores/preact": "^1.0.0", - "astro": "^7.2.9", + "astro": "^7.2.10", "nanostores": "^1.1.1", "preact": "^10.28.4" }, diff --git a/examples/with-tailwindcss/package.json b/examples/with-tailwindcss/package.json index eaadae6ab9dc..de89f1e3b454 100644 --- a/examples/with-tailwindcss/package.json +++ b/examples/with-tailwindcss/package.json @@ -13,10 +13,10 @@ "astro": "astro" }, "dependencies": { - "@astrojs/mdx": "^7.0.8", + "@astrojs/mdx": "^8.0.0", "@tailwindcss/vite": "^4.2.1", "@types/canvas-confetti": "^1.9.0", - "astro": "^7.2.9", + "astro": "^7.2.10", "canvas-confetti": "^1.9.4", "tailwindcss": "^4.2.1", "vite": "^8.0.13" diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json index e5f41aaf708c..495ff86b211e 100644 --- a/examples/with-vitest/package.json +++ b/examples/with-vitest/package.json @@ -14,7 +14,7 @@ "test": "vitest" }, "dependencies": { - "astro": "^7.2.9", + "astro": "^7.2.10", "vitest": "^5.0.0-beta.2" }, "allowScripts": { diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md index 5a84ae7b1771..16c7dce4da8d 100644 --- a/packages/astro/CHANGELOG.md +++ b/packages/astro/CHANGELOG.md @@ -1,5 +1,27 @@ # astro +## 7.2.10 + +### Patch Changes + +- [#17262](https://github.com/withastro/astro/pull/17262) [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fixes `@astrojs/markdown-remark` being pinned to an exact version. + +- [#17874](https://github.com/withastro/astro/pull/17874) [`10c7e63`](https://github.com/withastro/astro/commit/10c7e636cd14232473ae856d7e22e886f5c65689) Thanks [@astro-factory](https://github.com/apps/astro-factory)! - Fixes SSR manifest placeholder not being replaced when the server build is minified, which caused a runtime `Invalid URL` crash at server boot + +- [#17869](https://github.com/withastro/astro/pull/17869) [`2548abf`](https://github.com/withastro/astro/commit/2548abf1874f2fdfc7438aab51cfea03424753cc) Thanks [@ematipico](https://github.com/ematipico)! - Fixes a case where the logger was improperly initialized at runtime in dev. + +- [#17878](https://github.com/withastro/astro/pull/17878) [`76eff3d`](https://github.com/withastro/astro/commit/76eff3d5fbc5f940acb1dcb341d4b1c9d95fa2a3) Thanks [@ematipico](https://github.com/ematipico)! - Fixes browser heuristic caching for cached responses that include `Last-Modified` or `ETag` validators + +- [#17833](https://github.com/withastro/astro/pull/17833) [`413a6e7`](https://github.com/withastro/astro/commit/413a6e7a9b966124913893182b83cbd30a9fd3ab) Thanks [@astro-factory](https://github.com/apps/astro-factory)! - Fixes prerender conflict warnings to correctly identify the route that first rendered a duplicate pathname, instead of misattributing the conflict to an unrelated route that merely matches the URL pattern + +- [#17872](https://github.com/withastro/astro/pull/17872) [`f7191cc`](https://github.com/withastro/astro/commit/f7191cc4257330b6ca435fb4dae66d315b16115d) Thanks [@jx-grxf](https://github.com/jx-grxf)! - Fixes Markdown images in content collections rendering an empty `srcset` attribute when no responsive candidates are generated. + +- [#17755](https://github.com/withastro/astro/pull/17755) [`157c500`](https://github.com/withastro/astro/commit/157c500c38faa7ecf1251adbaeefcd109470d75c) Thanks [@matthewp](https://github.com/matthewp)! - Fixes a bug where editing a content collection entry during `astro dev` on Windows kept serving stale content until the dev server was restarted. The data store now notifies the dev server directly after each write instead of relying only on the file watcher, which can miss the atomic rename that commits the write on some platforms. + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40), [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + - @astrojs/markdown-satteri@0.4.0 + ## 7.2.9 ### Patch Changes diff --git a/packages/astro/package.json b/packages/astro/package.json index b1ba111b3aa6..d3b645cf8020 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -1,6 +1,6 @@ { "name": "astro", - "version": "7.2.9", + "version": "7.2.10", "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.", "type": "module", "author": "withastro", diff --git a/packages/integrations/cloudflare/CHANGELOG.md b/packages/integrations/cloudflare/CHANGELOG.md index 9aea275ba5e4..d69f116ddafe 100644 --- a/packages/integrations/cloudflare/CHANGELOG.md +++ b/packages/integrations/cloudflare/CHANGELOG.md @@ -1,5 +1,17 @@ # @astrojs/cloudflare +## 14.2.6 + +### Patch Changes + +- [#17854](https://github.com/withastro/astro/pull/17854) [`07b919f`](https://github.com/withastro/astro/commit/07b919f23e3041c4cc9c4f33004a19a32a5294b3) Thanks [@ematipico](https://github.com/ematipico)! - Added `@astrojs/prism` to the list of dependencies to optimise. The dev server is now faster for sites that use Prism as code highlighter. + +- [#17850](https://github.com/withastro/astro/pull/17850) [`1301c37`](https://github.com/withastro/astro/commit/1301c374435897654bf52d80d91d0947b72cf1a1) Thanks [@matthewp](https://github.com/matthewp)! - Fixes React SSR failures on the first Cloudflare dev request when JSON logging is enabled + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + - @astrojs/underscore-redirects@1.0.4 + ## 14.2.5 ### Patch Changes diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json index 3ad6c5607069..c545c113998e 100644 --- a/packages/integrations/cloudflare/package.json +++ b/packages/integrations/cloudflare/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/cloudflare", "description": "Deploy your site to Cloudflare Workers", - "version": "14.2.5", + "version": "14.2.6", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/integrations/markdoc/CHANGELOG.md b/packages/integrations/markdoc/CHANGELOG.md index a90f2a693fd3..472ca84c1b33 100644 --- a/packages/integrations/markdoc/CHANGELOG.md +++ b/packages/integrations/markdoc/CHANGELOG.md @@ -1,5 +1,12 @@ # @astrojs/markdoc +## 2.0.9 + +### Patch Changes + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + ## 2.0.8 ### Patch Changes diff --git a/packages/integrations/markdoc/package.json b/packages/integrations/markdoc/package.json index 0d3bb887e88f..1d55d6b53e6a 100644 --- a/packages/integrations/markdoc/package.json +++ b/packages/integrations/markdoc/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/markdoc", "description": "Add support for Markdoc in your Astro site", - "version": "2.0.8", + "version": "2.0.9", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/integrations/mdx/CHANGELOG.md b/packages/integrations/mdx/CHANGELOG.md index 52bc392d8777..8df71a468385 100644 --- a/packages/integrations/mdx/CHANGELOG.md +++ b/packages/integrations/mdx/CHANGELOG.md @@ -1,5 +1,31 @@ # @astrojs/mdx +## 8.0.0 + +### Major Changes + +- [#17262](https://github.com/withastro/astro/pull/17262) [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Moves MDX file processing to the Markdown processors. + + '@astrojs/mdx' is still required to add MDX support to your project. However, it now delegates the MDX files processing to Markdown processors. + + #### What should I do? + + If you haven't explicitly installed a Markdown processor, you don't need to do anything. + + Otherwise, ensure that your configured Markdown processor uses the following version: + - `@astrojs/markdown-satteri` 0.4.0 or later if you use `satteri()` + - `@astrojs/markdown-remark` 7.3.0 or later if you use `unified()` + +### Patch Changes + +- [#17262](https://github.com/withastro/astro/pull/17262) [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Fixes `.mdx` files still using `markdown.processor` when `extendMarkdownConfig` is `false`. They now use a clean default processor instead; pass `mdx({ processor })` to choose one explicitly. + +- [#17262](https://github.com/withastro/astro/pull/17262) [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Adds a warning when the deprecated `remarkPlugins`, `rehypePlugins`, `recmaPlugins` and `remarkRehype` options are ignored because your Markdown processor does not run them. They still apply when your processor is `unified()`, and were previously dropped silently otherwise. + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40), [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + - @astrojs/markdown-satteri@0.4.0 + ## 7.0.8 ### Patch Changes diff --git a/packages/integrations/mdx/package.json b/packages/integrations/mdx/package.json index 3e84c82c76e2..1cf56f66aeb3 100644 --- a/packages/integrations/mdx/package.json +++ b/packages/integrations/mdx/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/mdx", "description": "Add support for MDX pages in your Astro site", - "version": "7.0.8", + "version": "8.0.0", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/integrations/netlify/CHANGELOG.md b/packages/integrations/netlify/CHANGELOG.md index 03e42470202f..6ef25254b0e8 100644 --- a/packages/integrations/netlify/CHANGELOG.md +++ b/packages/integrations/netlify/CHANGELOG.md @@ -1,5 +1,13 @@ # @astrojs/netlify +## 8.2.5 + +### Patch Changes + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + - @astrojs/underscore-redirects@1.0.4 + ## 8.2.4 ### Patch Changes diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index 6ca1677e9949..e2310b698245 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/netlify", "description": "Deploy your site to Netlify", - "version": "8.2.4", + "version": "8.2.5", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/integrations/node/CHANGELOG.md b/packages/integrations/node/CHANGELOG.md index 514db70e8ee0..bdd121f8aa72 100644 --- a/packages/integrations/node/CHANGELOG.md +++ b/packages/integrations/node/CHANGELOG.md @@ -1,5 +1,12 @@ # @astrojs/node +## 11.1.5 + +### Patch Changes + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + ## 11.1.4 ### Patch Changes diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json index 090c0e3d8f99..f732dbb6eacf 100644 --- a/packages/integrations/node/package.json +++ b/packages/integrations/node/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/node", "description": "Deploy your site to a Node.js server", - "version": "11.1.4", + "version": "11.1.5", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/integrations/preact/CHANGELOG.md b/packages/integrations/preact/CHANGELOG.md index 088ccc09e494..4d172c4ad5ef 100644 --- a/packages/integrations/preact/CHANGELOG.md +++ b/packages/integrations/preact/CHANGELOG.md @@ -1,5 +1,12 @@ # @astrojs/preact +## 6.0.5 + +### Patch Changes + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + ## 6.0.4 ### Patch Changes diff --git a/packages/integrations/preact/package.json b/packages/integrations/preact/package.json index 242d026cf7b2..1fccf8f01a3f 100644 --- a/packages/integrations/preact/package.json +++ b/packages/integrations/preact/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/preact", "description": "Use Preact components within Astro", - "version": "6.0.4", + "version": "6.0.5", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/integrations/react/CHANGELOG.md b/packages/integrations/react/CHANGELOG.md index 11fa1a479d0c..0e2f9cad9383 100644 --- a/packages/integrations/react/CHANGELOG.md +++ b/packages/integrations/react/CHANGELOG.md @@ -1,5 +1,12 @@ # @astrojs/react +## 6.0.5 + +### Patch Changes + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + ## 6.0.4 ### Patch Changes diff --git a/packages/integrations/react/package.json b/packages/integrations/react/package.json index af7b3d54365d..67453ba04611 100644 --- a/packages/integrations/react/package.json +++ b/packages/integrations/react/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/react", "description": "Use React components within Astro", - "version": "6.0.4", + "version": "6.0.5", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/integrations/sitemap/CHANGELOG.md b/packages/integrations/sitemap/CHANGELOG.md index e6c17db40621..ad458c6f38af 100644 --- a/packages/integrations/sitemap/CHANGELOG.md +++ b/packages/integrations/sitemap/CHANGELOG.md @@ -1,5 +1,11 @@ # @astrojs/sitemap +## 3.7.4 + +### Patch Changes + +- [#17851](https://github.com/withastro/astro/pull/17851) [`52d3f56`](https://github.com/withastro/astro/commit/52d3f56999ecdf92510b2c16ed2a3a4785b9c73a) Thanks [@astro-factory](https://github.com/apps/astro-factory)! - Fixes the sitemap outputting a URL with an empty path for the homepage (e.g. `https://example.com` instead of `https://example.com/`) when `trailingSlash` is set to `"never"` or `build.format` is set to `"file"` + ## 3.7.3 ### Patch Changes diff --git a/packages/integrations/sitemap/package.json b/packages/integrations/sitemap/package.json index 68dde8a8c4af..25a7e453f54c 100644 --- a/packages/integrations/sitemap/package.json +++ b/packages/integrations/sitemap/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/sitemap", "description": "Generate a sitemap for your Astro site", - "version": "3.7.3", + "version": "3.7.4", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/integrations/vercel/CHANGELOG.md b/packages/integrations/vercel/CHANGELOG.md index bd014bb2ef71..6d4907f52a9f 100644 --- a/packages/integrations/vercel/CHANGELOG.md +++ b/packages/integrations/vercel/CHANGELOG.md @@ -1,5 +1,14 @@ # @astrojs/vercel +## 11.0.9 + +### Patch Changes + +- [#17450](https://github.com/withastro/astro/pull/17450) [`19dae21`](https://github.com/withastro/astro/commit/19dae210d42bd22662ee678c173676573b6168f2) Thanks [@ocavue](https://github.com/ocavue)! - Updates dependency `@vercel/analytics` to v2. See the [changelog](https://github.com/vercel/analytics/releases/tag/v2.0.0) for more details. + Updates dependency `@vercel/routing-utils` to v6. See the [changelog](https://github.com/vercel/vercel/blob/@vercel/routing-utils@6.4.0/packages/routing-utils/CHANGELOG.md#600) for more details. +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + ## 11.0.8 ### Patch Changes diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json index 1524f8965c0a..59fa4d05467e 100644 --- a/packages/integrations/vercel/package.json +++ b/packages/integrations/vercel/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/vercel", "description": "Deploy your site to Vercel", - "version": "11.0.8", + "version": "11.0.9", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/internal-helpers/CHANGELOG.md b/packages/internal-helpers/CHANGELOG.md index 591766598b54..7f683ae82092 100644 --- a/packages/internal-helpers/CHANGELOG.md +++ b/packages/internal-helpers/CHANGELOG.md @@ -1,5 +1,11 @@ # @astrojs/internal-helpers +## 0.11.0 + +### Minor Changes + +- [#17262](https://github.com/withastro/astro/pull/17262) [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Adds an `@astrojs/internal-helpers/mdx` entrypoint with the shared helpers the Markdown processor packages use to render `.mdx` files. + ## 0.10.4 ### Patch Changes diff --git a/packages/internal-helpers/package.json b/packages/internal-helpers/package.json index b2442bed0303..2bac2471fa23 100644 --- a/packages/internal-helpers/package.json +++ b/packages/internal-helpers/package.json @@ -1,7 +1,7 @@ { "name": "@astrojs/internal-helpers", "description": "Internal helpers used by core Astro packages.", - "version": "0.10.4", + "version": "0.11.0", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/language-tools/language-server/CHANGELOG.md b/packages/language-tools/language-server/CHANGELOG.md index 5e194d851743..38dc4a726df9 100644 --- a/packages/language-tools/language-server/CHANGELOG.md +++ b/packages/language-tools/language-server/CHANGELOG.md @@ -1,5 +1,11 @@ # @astrojs/language-server +## 2.16.16 + +### Patch Changes + +- [#17715](https://github.com/withastro/astro/pull/17715) [`a51c533`](https://github.com/withastro/astro/commit/a51c533224687120f0b279f740bdd80b62bc3769) Thanks [@wakqasahmed](https://github.com/wakqasahmed)! - Fixes `astro check` silently skipping `.astro` files that are only reachable through a TypeScript project reference (a tsconfig referenced via `references` in another tsconfig). These files are now checked and reported like any other `.astro` file. + ## 2.16.15 ### Patch Changes diff --git a/packages/language-tools/language-server/package.json b/packages/language-tools/language-server/package.json index 8fc5a2b407be..694c04aec7a0 100644 --- a/packages/language-tools/language-server/package.json +++ b/packages/language-tools/language-server/package.json @@ -1,6 +1,6 @@ { "name": "@astrojs/language-server", - "version": "2.16.15", + "version": "2.16.16", "author": "withastro", "license": "MIT", "repository": { diff --git a/packages/markdown/remark/CHANGELOG.md b/packages/markdown/remark/CHANGELOG.md index c7bb7c37486c..b190cc4c0b93 100644 --- a/packages/markdown/remark/CHANGELOG.md +++ b/packages/markdown/remark/CHANGELOG.md @@ -1,5 +1,20 @@ # @astrojs/markdown-remark +## 7.3.0 + +### Minor Changes + +- [#17262](https://github.com/withastro/astro/pull/17262) [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Adds MDX rendering to the `unified()` and `satteri()` processors. + + Both processors now compile `.mdx` files themselves. You still need to install `@astrojs/mdx` to add MDX support to your project. + +- [#17262](https://github.com/withastro/astro/pull/17262) [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Adds a `recmaPlugins` option to `unified()` for adding recma (estree/JSX) plugins to the MDX compiler. + +### Patch Changes + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + ## 7.2.4 ### Patch Changes diff --git a/packages/markdown/remark/package.json b/packages/markdown/remark/package.json index 8f2fdd9dabec..7521374a05e9 100644 --- a/packages/markdown/remark/package.json +++ b/packages/markdown/remark/package.json @@ -1,6 +1,6 @@ { "name": "@astrojs/markdown-remark", - "version": "7.2.4", + "version": "7.3.0", "type": "module", "author": "withastro", "license": "MIT", diff --git a/packages/markdown/satteri/CHANGELOG.md b/packages/markdown/satteri/CHANGELOG.md index 7428f31f46a8..33388517b69c 100644 --- a/packages/markdown/satteri/CHANGELOG.md +++ b/packages/markdown/satteri/CHANGELOG.md @@ -1,5 +1,18 @@ # @astrojs/markdown-satteri +## 0.4.0 + +### Minor Changes + +- [#17262](https://github.com/withastro/astro/pull/17262) [`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Adds MDX rendering to the `unified()` and `satteri()` processors. + + Both processors now compile `.mdx` files themselves. You still need to install `@astrojs/mdx` to add MDX support to your project. + +### Patch Changes + +- Updated dependencies [[`f8e9458`](https://github.com/withastro/astro/commit/f8e94585ab6c38e2702ee1e2e540858f72058a40)]: + - @astrojs/internal-helpers@0.11.0 + ## 0.3.8 ### Patch Changes diff --git a/packages/markdown/satteri/package.json b/packages/markdown/satteri/package.json index 10e41f32754f..85dcebdae93f 100644 --- a/packages/markdown/satteri/package.json +++ b/packages/markdown/satteri/package.json @@ -1,6 +1,6 @@ { "name": "@astrojs/markdown-satteri", - "version": "0.3.8", + "version": "0.4.0", "type": "module", "author": "withastro", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ccd838add07..c18ca7d28427 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -390,10 +390,10 @@ importers: examples/advanced-routing: dependencies: '@astrojs/node': - specifier: ^11.1.4 + specifier: ^11.1.5 version: link:../../packages/integrations/node astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro hono: specifier: ^4.12.14 @@ -402,22 +402,22 @@ importers: examples/basics: dependencies: astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro examples/blog: dependencies: '@astrojs/mdx': - specifier: ^7.0.8 + specifier: ^8.0.0 version: link:../../packages/integrations/mdx '@astrojs/rss': specifier: ^4.0.19 version: link:../../packages/astro-rss '@astrojs/sitemap': - specifier: ^3.7.3 + specifier: ^3.7.4 version: link:../../packages/integrations/sitemap astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro sharp: specifier: ^0.35.0 @@ -426,16 +426,16 @@ importers: examples/component: devDependencies: astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro examples/container-with-vitest: dependencies: '@astrojs/react': - specifier: ^6.0.4 + specifier: ^6.0.5 version: link:../../packages/integrations/react astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro react: specifier: ^18.3.1 @@ -466,16 +466,16 @@ importers: specifier: ^3.15.8 version: 3.15.8 astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro examples/framework-multiple: dependencies: '@astrojs/preact': - specifier: ^6.0.4 + specifier: ^6.0.5 version: link:../../packages/integrations/preact '@astrojs/react': - specifier: ^6.0.4 + specifier: ^6.0.5 version: link:../../packages/integrations/react '@astrojs/solid-js': specifier: ^7.0.2 @@ -493,7 +493,7 @@ importers: specifier: ^18.3.7 version: 18.3.7(@types/react@18.3.28) astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro preact: specifier: ^10.28.4 @@ -517,13 +517,13 @@ importers: examples/framework-preact: dependencies: '@astrojs/preact': - specifier: ^6.0.4 + specifier: ^6.0.5 version: link:../../packages/integrations/preact '@preact/signals': specifier: ^2.8.1 version: 2.8.2(preact@10.29.8) astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro preact: specifier: ^10.28.4 @@ -532,7 +532,7 @@ importers: examples/framework-react: dependencies: '@astrojs/react': - specifier: ^6.0.4 + specifier: ^6.0.5 version: link:../../packages/integrations/react '@types/react': specifier: ^18.3.28 @@ -541,7 +541,7 @@ importers: specifier: ^18.3.7 version: 18.3.7(@types/react@18.3.28) astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro react: specifier: ^18.3.1 @@ -556,7 +556,7 @@ importers: specifier: ^7.0.2 version: link:../../packages/integrations/solid astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro solid-js: specifier: ^1.9.11 @@ -568,7 +568,7 @@ importers: specifier: ^9.0.1 version: link:../../packages/integrations/svelte astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro svelte: specifier: ^5.53.5 @@ -580,7 +580,7 @@ importers: specifier: ^7.0.2 version: link:../../packages/integrations/vue astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro vue: specifier: ^3.5.29 @@ -589,40 +589,40 @@ importers: examples/hackernews: dependencies: '@astrojs/node': - specifier: ^11.1.4 + specifier: ^11.1.5 version: link:../../packages/integrations/node astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro examples/integration: devDependencies: astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro examples/minimal: dependencies: astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro examples/portfolio: dependencies: astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro examples/ssr: dependencies: '@astrojs/node': - specifier: ^11.1.4 + specifier: ^11.1.5 version: link:../../packages/integrations/node '@astrojs/svelte': specifier: ^9.0.1 version: link:../../packages/integrations/svelte astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro svelte: specifier: ^5.53.5 @@ -631,7 +631,7 @@ importers: examples/starlog: dependencies: astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro sass: specifier: ^1.97.3 @@ -646,28 +646,28 @@ importers: specifier: ^22.19.0 version: 22.19.19 astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro examples/with-markdoc: dependencies: '@astrojs/markdoc': - specifier: ^2.0.8 + specifier: ^2.0.9 version: link:../../packages/integrations/markdoc astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro examples/with-mdx: dependencies: '@astrojs/mdx': - specifier: ^7.0.8 + specifier: ^8.0.0 version: link:../../packages/integrations/mdx '@astrojs/preact': - specifier: ^6.0.4 + specifier: ^6.0.5 version: link:../../packages/integrations/preact astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro preact: specifier: ^10.28.4 @@ -676,13 +676,13 @@ importers: examples/with-nanostores: dependencies: '@astrojs/preact': - specifier: ^6.0.4 + specifier: ^6.0.5 version: link:../../packages/integrations/preact '@nanostores/preact': specifier: ^1.0.0 version: 1.0.0(nanostores@1.1.1)(preact@10.29.8) astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro nanostores: specifier: ^1.1.1 @@ -694,7 +694,7 @@ importers: examples/with-tailwindcss: dependencies: '@astrojs/mdx': - specifier: ^7.0.8 + specifier: ^8.0.0 version: link:../../packages/integrations/mdx '@tailwindcss/vite': specifier: ^4.2.1 @@ -703,7 +703,7 @@ importers: specifier: ^1.9.0 version: 1.9.0 astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro canvas-confetti: specifier: ^1.9.4 @@ -718,7 +718,7 @@ importers: examples/with-vitest: dependencies: astro: - specifier: ^7.2.9 + specifier: ^7.2.10 version: link:../../packages/astro vitest: specifier: ^5.0.0-beta.2