(unsafeInline ? undefined : sharedHashes);
+ if (!unsafeInline) {
+ for (const hash of ownHashes) {
+ hashes.add(`'${hash}'`);
+ }
}
let finalResources: string;
if (resources.length > 0) {
diff --git a/packages/astro/src/types/public/config.ts b/packages/astro/src/types/public/config.ts
index b5399b3eac62..2307a38e8451 100644
--- a/packages/astro/src/types/public/config.ts
+++ b/packages/astro/src/types/public/config.ts
@@ -819,7 +819,7 @@ export interface AstroUserConfig<
* - External scripts and external styles are not supported out of the box, but you can [provide your own hashes](https://docs.astro.build/en/reference/configuration-reference/#securitycspscriptdirectivehashes).
* - [Astro's view transitions](https://docs.astro.build/en/guides/view-transitions/) using the `` are not supported, but you can [consider migrating to the browser native View Transition API](https://events-3bg.pages.dev/jotter/astro-view-transitions/) instead if you are not using Astro's enhancements to the native View Transitions and Navigation APIs.
* - Shiki isn't currently supported. By design, Shiki functions use inline styles that cannot work with Astro CSP implementation. Consider [using ``](https://docs.astro.build/en/guides/syntax-highlighting/#prism-) when your project requires both CSP and syntax highlighting.
- * - `unsafe-inline` directives are incompatible with Astro's CSP implementation. By default, Astro will emit hashes for all its bundled scripts (e.g. client islands) and all modern browsers will automatically reject `unsafe-inline` when it occurs in a directive with a hash or nonce.
+ * - When `'unsafe-inline'` is included as a resource in a directive, Astro will not emit hashes on that directive. Per the CSP spec, browsers ignore `'unsafe-inline'` when a hash or nonce is present in the same directive, so Astro suppresses hashes to preserve `'unsafe-inline'` behavior. Note that this reduces the security of that directive to the level of `'unsafe-inline'`.
*
* :::note
* Due to the nature of the Vite dev server, this feature isn't supported while working in `dev` mode. Instead, you can test this in your Astro project using `build` and `preview`.
diff --git a/packages/astro/test/units/csp/render-csp.test.ts b/packages/astro/test/units/csp/render-csp.test.ts
index 4ba4b626b0cb..b37dc4bf91f0 100644
--- a/packages/astro/test/units/csp/render-csp.test.ts
+++ b/packages/astro/test/units/csp/render-csp.test.ts
@@ -211,4 +211,100 @@ describe('renderCspContent', () => {
"script-src 'self' 'sha256-default'; style-src 'self' ;",
);
});
+
+ it('suppresses hashes on style-src when unsafe-inline is present', () => {
+ assert.equal(
+ renderCspContent(
+ createCspResult({
+ styleDirective: {
+ resources: ["'unsafe-inline'"],
+ hashes: ['sha256-abc'],
+ },
+ }),
+ ),
+ "script-src 'self' ; style-src 'unsafe-inline' ;",
+ );
+ });
+
+ it('suppresses hashes on script-src when unsafe-inline is present', () => {
+ assert.equal(
+ renderCspContent(
+ createCspResult({
+ scriptDirective: {
+ resources: ["'unsafe-inline'"],
+ hashes: ['sha256-abc'],
+ strictDynamic: false,
+ },
+ }),
+ ),
+ "script-src 'unsafe-inline' ; style-src 'self' ;",
+ );
+ });
+
+ it('suppresses hashes on style-src-elem when unsafe-inline is present', () => {
+ assert.equal(
+ renderCspContent(
+ createCspResult({
+ styleDirective: {
+ resources: [{ resource: "'unsafe-inline'", kind: 'element' }],
+ hashes: ['sha256-abc'],
+ },
+ }),
+ ),
+ "script-src 'self' ; style-src 'self' ; style-src-elem 'unsafe-inline';",
+ );
+ });
+
+ it('suppresses hashes on script-src-elem when unsafe-inline is present', () => {
+ assert.equal(
+ renderCspContent(
+ createCspResult({
+ scriptDirective: {
+ resources: [{ resource: "'unsafe-inline'", kind: 'element' }],
+ hashes: ['sha256-abc'],
+ strictDynamic: false,
+ },
+ }),
+ ),
+ "script-src 'self' ; script-src-elem 'unsafe-inline'; style-src 'self' ;",
+ );
+ });
+
+ it('suppresses render-time extra hashes when unsafe-inline is on style-src', () => {
+ assert.equal(
+ renderCspContent(
+ createCspResult({
+ styleDirective: { resources: ["'unsafe-inline'"] },
+ extraStyleHashes: ['sha256-extra'],
+ }),
+ ),
+ "script-src 'self' ; style-src 'unsafe-inline' ;",
+ );
+ });
+
+ it('suppresses render-time extra hashes when unsafe-inline is on script-src', () => {
+ assert.equal(
+ renderCspContent(
+ createCspResult({
+ scriptDirective: { resources: ["'unsafe-inline'"] },
+ extraScriptHashes: ['sha256-extra'],
+ }),
+ ),
+ "script-src 'unsafe-inline' ; style-src 'self' ;",
+ );
+ });
+
+ it('keeps hashes on style-src when unsafe-inline is only on style-src-attr', () => {
+ assert.equal(
+ renderCspContent(
+ createCspResult({
+ styleDirective: {
+ resources: [{ resource: "'unsafe-inline'", kind: 'attribute' }],
+ hashes: ['sha256-abc'],
+ },
+ }),
+ ),
+ "script-src 'self' ; style-src 'self' 'sha256-abc'; style-src-attr 'unsafe-inline';",
+ );
+ });
});
From 0762a8385b5b5b093def3768a0c4d0464a9dccc4 Mon Sep 17 00:00:00 2001
From: HiDeoo <494699+HiDeoo@users.noreply.github.com>
Date: Thu, 20 Aug 2026 12:01:42 +0200
Subject: [PATCH 03/21] =?UTF-8?q?fix:=20accept=20all=20s=C3=A4tteri=20plug?=
=?UTF-8?q?in=20entry=20types=20(#17766)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.changeset/grumpy-bats-return.md | 6 +++++
.../integrations/mdx/src/satteri/index.ts | 9 ++++----
packages/markdown/satteri/src/processor.ts | 16 +++++++++----
.../markdown/satteri/src/satteri-processor.ts | 19 +++++++++++----
.../markdown/satteri/test/markdown.test.ts | 23 ++++++++++++++++++-
5 files changed, 57 insertions(+), 16 deletions(-)
create mode 100644 .changeset/grumpy-bats-return.md
diff --git a/.changeset/grumpy-bats-return.md b/.changeset/grumpy-bats-return.md
new file mode 100644
index 000000000000..05df2d2b98e6
--- /dev/null
+++ b/.changeset/grumpy-bats-return.md
@@ -0,0 +1,6 @@
+---
+'@astrojs/mdx': patch
+'@astrojs/markdown-satteri': patch
+---
+
+Fixes Sätteri processor option types to accept all plugin entries supported by Sätteri v0.10.3.
diff --git a/packages/integrations/mdx/src/satteri/index.ts b/packages/integrations/mdx/src/satteri/index.ts
index 4df623d3045b..9de7ff119993 100644
--- a/packages/integrations/mdx/src/satteri/index.ts
+++ b/packages/integrations/mdx/src/satteri/index.ts
@@ -13,7 +13,9 @@ import {
mdxToJs,
type HastNode,
type HastPluginDefinition,
+ type HastPluginEntry,
type MdastPluginDefinition,
+ type MdastPluginEntry,
type MdxCompileOptions,
} from 'satteri';
import { ASTRO_IMAGE_IMPORT, USES_ASTRO_IMAGE_FLAG } from '../image-constants.js';
@@ -90,12 +92,9 @@ export function createMdxProcessor(
typeof syntaxHighlight === 'object' ? syntaxHighlight.excludeLangs : undefined;
// Collect last so image-URL rewrites by user plugins are captured.
- const allMdastPlugins: MdastPluginDefinition[] = [
- ...satteriOptions.mdastPlugins,
- collectImages,
- ];
+ const allMdastPlugins: MdastPluginEntry[] = [...satteriOptions.mdastPlugins, collectImages];
- const hastPlugins: HastPluginDefinition[] = [];
+ const hastPlugins: HastPluginEntry[] = [];
if (highlightFn) {
hastPlugins.push(satteriHighlightPlugin(highlightFn, excludeLangs));
}
diff --git a/packages/markdown/satteri/src/processor.ts b/packages/markdown/satteri/src/processor.ts
index 517a36c9fa89..a643fe578cdc 100644
--- a/packages/markdown/satteri/src/processor.ts
+++ b/packages/markdown/satteri/src/processor.ts
@@ -1,10 +1,16 @@
import type { MarkdownProcessor } from '@astrojs/internal-helpers/markdown';
-import type { Features, HastPluginDefinition, MdastPluginDefinition } from 'satteri';
+import type {
+ Features,
+ HastPluginEntry,
+ HastPluginList,
+ MdastPluginEntry,
+ MdastPluginList,
+} from 'satteri';
import { createSatteriMarkdownProcessor } from './satteri-processor.js';
export interface SatteriProcessorOptions {
- mdastPlugins?: MdastPluginDefinition[];
- hastPlugins?: HastPluginDefinition[];
+ mdastPlugins?: MdastPluginList;
+ hastPlugins?: HastPluginList;
features?: Features;
}
@@ -13,8 +19,8 @@ export interface SatteriProcessorOptions {
* (the factory normalises absent inputs into defaults).
*/
export interface SatteriResolvedOptions {
- mdastPlugins: MdastPluginDefinition[];
- hastPlugins: HastPluginDefinition[];
+ mdastPlugins: MdastPluginEntry[];
+ hastPlugins: HastPluginEntry[];
features: Features;
}
diff --git a/packages/markdown/satteri/src/satteri-processor.ts b/packages/markdown/satteri/src/satteri-processor.ts
index 6cbacf4e3f4a..3c905b8bbeb0 100644
--- a/packages/markdown/satteri/src/satteri-processor.ts
+++ b/packages/markdown/satteri/src/satteri-processor.ts
@@ -5,7 +5,16 @@ import {
syntaxHighlightDefaults,
} from '@astrojs/internal-helpers/markdown';
import Slugger from 'github-slugger';
-import type { Features, HastNode, HastPluginDefinition, MdastPluginDefinition } from 'satteri';
+import type {
+ Features,
+ HastNode,
+ HastPluginDefinition,
+ HastPluginEntry,
+ HastPluginList,
+ MdastPluginDefinition,
+ MdastPluginEntry,
+ MdastPluginList,
+} from 'satteri';
import { createShikiHighlighter } from '@astrojs/internal-helpers/shiki';
import type {
AstroMarkdownOptions,
@@ -206,8 +215,8 @@ export function createHighlightPlugin(
}
export interface SatteriMarkdownProcessorOptions extends AstroMarkdownOptions {
- mdastPlugins?: MdastPluginDefinition[];
- hastPlugins?: HastPluginDefinition[];
+ mdastPlugins?: MdastPluginList;
+ hastPlugins?: HastPluginList;
features?: Features;
}
@@ -291,12 +300,12 @@ export async function createSatteriMarkdownProcessor(
};
// Collect last so image-URL rewrites by user plugins are captured.
- const allMdastPlugins: MdastPluginDefinition[] = [
+ const allMdastPlugins: MdastPluginEntry[] = [
...userMdastPlugins,
createCollectImagesPlugin(opts?.image),
];
- const hastPlugins: HastPluginDefinition[] = [];
+ const hastPlugins: HastPluginEntry[] = [];
if (highlightFn) {
hastPlugins.push(createHighlightPlugin(highlightFn, syntaxHighlightExcludeLangs));
}
diff --git a/packages/markdown/satteri/test/markdown.test.ts b/packages/markdown/satteri/test/markdown.test.ts
index 19574fc0ed13..0b95f2a61d84 100644
--- a/packages/markdown/satteri/test/markdown.test.ts
+++ b/packages/markdown/satteri/test/markdown.test.ts
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import type { HastPluginDefinition, MdastPluginDefinition } from 'satteri';
-import { createSatteriMarkdownProcessor, satteriHeadingIdsPlugin } from '../dist/index.js';
+import { createSatteriMarkdownProcessor, satteri, satteriHeadingIdsPlugin } from '../dist/index.js';
describe('satteri markdown', () => {
it('renders basic markdown', async () => {
@@ -136,4 +136,25 @@ describe('satteri markdown', () => {
assert.equal(metadata.frontmatter.title, 'hello');
assert.equal(metadata.frontmatter.injected, 'HELLO');
});
+
+ it('accepts conditional plugin factories', async () => {
+ const mdUppercasePlugin: MdastPluginDefinition = {
+ name: 'mdx-uppercase',
+ text(node, ctx) {
+ ctx.setProperty(node, 'value', node.value.toUpperCase());
+ },
+ };
+
+ const satteriProcessor = satteri({
+ mdastPlugins: [(ctx) => (ctx.sourceFormat === 'markdown' ? [mdUppercasePlugin] : null)],
+ });
+
+ const processor = await createSatteriMarkdownProcessor({
+ mdastPlugins: satteriProcessor.options.mdastPlugins,
+ });
+
+ const { code } = await processor.render('Hello');
+
+ assert.match(code, /HELLO<\/p>/);
+ });
});
From b872d6c602686ebf5298317dac7828d282d545a2 Mon Sep 17 00:00:00 2001
From: Florian Lefebvre
Date: Thu, 20 Aug 2026 12:12:58 +0200
Subject: [PATCH 04/21] Modify issue template for chat and support options
(#17746)
---
.github/ISSUE_TEMPLATE/config.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 5c5f4e11b8ef..20384533499d 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -2,7 +2,10 @@ blank_issues_enabled: false
contact_links:
- name: 💁 Support
url: https://astro.build/chat
- about: 'This issue tracker is not for support questions. Join us on Discord for assistance!'
+ about: This issue tracker is not for support questions. Join us on Discord for assistance!
+ - name: 💁 Support (GitHub)
+ url: https://github.com/withastro/community-support/discussions
+ about: No Discord account? Open a discussion on GitHub for assistance.
- name: 📘 Documentation
url: https://github.com/withastro/docs
about: File an issue or make an improvement to the docs website.
From 660991c820fbeb087b2f27361e6ebaeba8285358 Mon Sep 17 00:00:00 2001
From: "astro-factory[bot]"
<316791938+astro-factory[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 12:39:56 +0100
Subject: [PATCH 05/21] Fix build error location reporting for MDX and
aggregate plugin errors (#17757)
Co-authored-by: factory[bot]
---
.changeset/gentle-regions-shave.md | 6 ++
packages/astro/src/core/errors/dev/utils.ts | 6 +-
.../astro/test/units/errors/dev-utils.test.ts | 69 ++++++++++++++++++-
.../integrations/mdx/src/vite-plugin-mdx.ts | 13 +++-
4 files changed, 89 insertions(+), 5 deletions(-)
create mode 100644 .changeset/gentle-regions-shave.md
diff --git a/.changeset/gentle-regions-shave.md b/.changeset/gentle-regions-shave.md
new file mode 100644
index 000000000000..4ea58b32cf9d
--- /dev/null
+++ b/.changeset/gentle-regions-shave.md
@@ -0,0 +1,6 @@
+---
+'astro': patch
+'@astrojs/mdx': patch
+---
+
+Fixes build errors showing wrong file location, missing line:col, and misleading hints when a plugin error (e.g. from MDX) is wrapped by Vite's build error
diff --git a/packages/astro/src/core/errors/dev/utils.ts b/packages/astro/src/core/errors/dev/utils.ts
index e42e98f8ec24..31a98d114aeb 100644
--- a/packages/astro/src/core/errors/dev/utils.ts
+++ b/packages/astro/src/core/errors/dev/utils.ts
@@ -23,8 +23,8 @@ export function collectErrorMetadata(e: any, rootFolder?: URL): ErrorWithMetadat
AggregateError.is(e) || Array.isArray(e.errors) ? (e.errors as SSRError[]) : [e as SSRError];
err.forEach((error) => {
- if (e.stack) {
- const stackInfo = collectInfoFromStacktrace(e);
+ if (error.stack) {
+ const stackInfo = collectInfoFromStacktrace(error);
try {
error.stack = stripVTControlCharacters(stackInfo.stack);
} catch {}
@@ -68,7 +68,7 @@ export function collectErrorMetadata(e: any, rootFolder?: URL): ErrorWithMetadat
}
// Generic error (probably from Vite, and already formatted)
- error.hint = generateHint(e);
+ error.hint = generateHint(error);
// Strip ANSI for `message` property. Note that ESBuild errors may not have the property,
// but it will be handled and added below, which is already ANSI-free
diff --git a/packages/astro/test/units/errors/dev-utils.test.ts b/packages/astro/test/units/errors/dev-utils.test.ts
index 775e6f7ec05e..742c09870e35 100644
--- a/packages/astro/test/units/errors/dev-utils.test.ts
+++ b/packages/astro/test/units/errors/dev-utils.test.ts
@@ -1,6 +1,6 @@
import * as assert from 'node:assert/strict';
import { describe, it } from 'node:test';
-import { renderErrorMarkdown } from '../../../dist/core/errors/dev/utils.js';
+import { collectErrorMetadata, renderErrorMarkdown } from '../../../dist/core/errors/dev/utils.js';
describe('renderErrorMarkdown', () => {
describe('html target', () => {
@@ -222,3 +222,70 @@ describe('renderErrorMarkdown', () => {
});
});
});
+
+describe('collectErrorMetadata', () => {
+ it('uses sub-error properties when the parent error has an errors array', () => {
+ // Simulate how rolldown/Vite wraps a plugin error: the parent error has
+ // its own stack but no loc/plugin, while the sub-error carries the real info.
+ const subError = new Error('Something went wrong in component.astro');
+ subError.stack = `Error: Something went wrong in component.astro
+ at renderComponent (file:///project/src/components/Foo.astro:10:5)`;
+
+ const parentError = new Error('Build failed with 1 error');
+ parentError.stack = `Error: Build failed with 1 error
+ at buildEnvironment (file:///node_modules/vite/dist/node/chunks/node.js:33011:66)`;
+ // @ts-ignore - adding errors array like rolldown does
+ parentError.errors = [subError];
+
+ const result = collectErrorMetadata(parentError);
+
+ // The sub-error's own stack should be used, not the parent's
+ assert.ok(result.stack?.includes('renderComponent'));
+ assert.ok(!result.stack?.includes('buildEnvironment'));
+ });
+
+ it('preserves sub-error loc when parent error has no loc', () => {
+ const subError = new Error('Parse error');
+ // @ts-ignore
+ subError.loc = { file: '/project/src/pages/test.mdx', line: 10, column: 5 };
+ // @ts-ignore
+ subError.plugin = 'astro:mdx';
+ subError.stack = `Error: Parse error
+ at transform (file:///project/node_modules/@astrojs/mdx/dist/index.js:42:7)`;
+
+ const parentError = new Error('Build failed');
+ parentError.stack = `Error: Build failed
+ at buildEnvironment (file:///node_modules/vite/dist/node.js:100:20)`;
+ // @ts-ignore
+ parentError.errors = [subError];
+
+ const result = collectErrorMetadata(parentError);
+
+ assert.equal(result.loc?.file, '/project/src/pages/test.mdx');
+ assert.equal(result.loc?.line, 10);
+ assert.equal(result.loc?.column, 5);
+ assert.equal(result.plugin, 'astro:mdx');
+ });
+
+ it('does not generate misleading hints from parent error message', () => {
+ // The sub-error message has no browser API references, but the parent's
+ // stack/message might mention "window" or "document" incidentally.
+ const subError = new Error('Could not parse expression with oxc');
+ subError.stack = `Error: Could not parse expression with oxc
+ at transform (file:///project/node_modules/@astrojs/mdx/dist/index.js:42:7)`;
+
+ const parentError = new Error('Build failed - check document for details');
+ parentError.stack = `Error: Build failed
+ at build (file:///node_modules/vite/dist/node.js:100:20)`;
+ // @ts-ignore
+ parentError.errors = [subError];
+
+ const result = collectErrorMetadata(parentError);
+
+ // Should not get a "Browser APIs are not available" hint from the parent message
+ assert.ok(
+ !result.hint?.includes('Browser APIs'),
+ 'Should not generate browser API hint from parent error',
+ );
+ });
+});
diff --git a/packages/integrations/mdx/src/vite-plugin-mdx.ts b/packages/integrations/mdx/src/vite-plugin-mdx.ts
index 10c7d605390b..58a6a2924c35 100644
--- a/packages/integrations/mdx/src/vite-plugin-mdx.ts
+++ b/packages/integrations/mdx/src/vite-plugin-mdx.ts
@@ -72,7 +72,18 @@ export function vitePluginMdx(opts: VitePluginMdxOptions): Plugin {
// Surface compile failures as a dedicated MDX error with a source
// location so the dev overlay can point at the offending file.
err.name = 'MDXError';
- err.loc = { file: id, line: e.line, column: e.column };
+ // Some parser errors (e.g. from oxc) embed line:col only in the
+ // message as a "line:col: ..." prefix instead of setting properties.
+ let line = e.line;
+ let column = e.column;
+ if (line == null || column == null) {
+ const match = /^(\d+):(\d+):/.exec(e.message);
+ if (match) {
+ line ??= Number(match[1]);
+ column ??= Number(match[2]);
+ }
+ }
+ err.loc = { file: id, line, column };
// Compiler errors may arrive without a JS stack; capture one here.
Error.captureStackTrace(err);
throw err;
From e362d4cf540b27730482455c8fc02efe57d16702 Mon Sep 17 00:00:00 2001
From: Matthew Phillips
Date: Thu, 20 Aug 2026 07:42:18 -0400
Subject: [PATCH 06/21] Fix generated Netlify image config URLs (#17752)
---
.changeset/moody-geckos-train.md | 5 +++++
packages/integrations/netlify/src/index.ts | 4 ++--
.../netlify/test/functions/image-cdn.test.ts | 13 +++++++++++++
3 files changed, 20 insertions(+), 2 deletions(-)
create mode 100644 .changeset/moody-geckos-train.md
diff --git a/.changeset/moody-geckos-train.md b/.changeset/moody-geckos-train.md
new file mode 100644
index 000000000000..033bebc96236
--- /dev/null
+++ b/.changeset/moody-geckos-train.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/netlify': patch
+---
+
+Fixes generated Netlify Image CDN allowlists to reject remote URLs that contain an allowed image origin only within their path or query string
diff --git a/packages/integrations/netlify/src/index.ts b/packages/integrations/netlify/src/index.ts
index 90060a9c8673..4eb474cd3b60 100644
--- a/packages/integrations/netlify/src/index.ts
+++ b/packages/integrations/netlify/src/index.ts
@@ -49,7 +49,7 @@ export function remotePatternToRegex(
): string | undefined {
let { protocol, hostname, port, pathname } = pattern;
- let regexStr = '';
+ let regexStr = '^';
if (protocol) {
regexStr += `${protocol}://`;
@@ -125,7 +125,7 @@ function remoteImagesFromAstroConfig(
const remoteImages: string[] = [];
// Domains get a simple regex match
remoteImages.push(
- ...config.image.domains.map((domain) => `https?:\/\/${escapeRegex(domain)}\/.*`),
+ ...config.image.domains.map((domain) => `^https?:\/\/${escapeRegex(domain)}\/.*$`),
);
// Remote patterns need to be converted to regexes
remoteImages.push(
diff --git a/packages/integrations/netlify/test/functions/image-cdn.test.ts b/packages/integrations/netlify/test/functions/image-cdn.test.ts
index da4b487799b4..3d8d267722e0 100644
--- a/packages/integrations/netlify/test/functions/image-cdn.test.ts
+++ b/packages/integrations/netlify/test/functions/image-cdn.test.ts
@@ -116,6 +116,19 @@ describe('Image CDN', { timeout: 120000 }, () => {
);
});
+ it('rejects allowed patterns embedded in another URL', async () => {
+ assert.equal(
+ regexes[0]!.test(
+ 'http://169.254.169.254/latest/meta-data/?url=https://example.net/image.jpg',
+ ),
+ false,
+ );
+ assert.equal(
+ regexes[2]!.test('http://127.0.0.1:6379/?url=https://www.example.org/images/a.jpg'),
+ false,
+ );
+ });
+
it('treats metacharacters in a literal pathname as literals', async () => {
const spyLogger = new SpyLogger();
const logger = spyLogger.forkIntegrationLogger('test-spy');
From 86c9c3806a1934938544d78570e5c2ff36bc1e44 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 12:42:53 +0100
Subject: [PATCH 07/21] Update @astrojs/node (#17577)
---
packages/integrations/node/package.json | 8 +-
pnpm-lock.yaml | 237 +++++++++++++-----------
2 files changed, 133 insertions(+), 112 deletions(-)
diff --git a/packages/integrations/node/package.json b/packages/integrations/node/package.json
index ae99dfab80f5..32af2e1e1d23 100644
--- a/packages/integrations/node/package.json
+++ b/packages/integrations/node/package.json
@@ -40,7 +40,7 @@
"astro": "^7.2.1"
},
"devDependencies": {
- "@fastify/middie": "^9.1.0",
+ "@fastify/middie": "^9.3.3",
"@fastify/static": "^9.0.0",
"@types/express": "^5.0.6",
"@types/node": "^22.10.6",
@@ -49,10 +49,10 @@
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"cheerio": "1.2.0",
- "devalue": "^5.8.1",
+ "devalue": "^5.9.0",
"express": "^5.2.1",
- "fastify": "^5.7.4",
- "node-mocks-http": "^1.17.2"
+ "fastify": "^5.12.0",
+ "node-mocks-http": "^1.18.1"
},
"astro": {
"external": true
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a877f81b1274..d0965924728b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -230,7 +230,7 @@ importers:
version: 2.29.8(@types/node@22.19.19)
'@earendil-works/pi-ai':
specifier: ^0.83.0
- version: 0.83.0(supports-color@8.1.1)(ws@8.20.1)(zod@4.3.6)
+ version: 0.83.0(ws@8.20.1)(zod@4.3.6)
'@flue/runtime':
specifier: ^2.0.3
version: 2.0.3(typescript@6.0.3)(ws@8.20.1)(zod@4.3.6)
@@ -242,7 +242,7 @@ importers:
version: 0.2.0
eslint:
specifier: ^10.4.0
- version: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
+ version: 10.4.0(jiti@2.6.1)
eslint-plugin-regexp:
specifier: ^3.1.0
version: 3.1.0(eslint@10.4.0)
@@ -272,7 +272,7 @@ importers:
version: 6.0.3
typescript-eslint:
specifier: ^8.59.1
- version: 8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
+ version: 8.59.2(eslint@10.4.0)(typescript@6.0.3)
valibot:
specifier: ^1.2.0
version: 1.4.2(typescript@6.0.3)
@@ -768,7 +768,7 @@ importers:
version: 2.0.1
devalue:
specifier: ^5.8.1
- version: 5.8.1
+ version: 5.9.0
diff:
specifier: ^8.0.3
version: 8.0.4
@@ -937,7 +937,7 @@ importers:
version: 4.12.18
node-mocks-http:
specifier: ^1.17.2
- version: 1.17.2(@types/express@5.0.6)(@types/node@22.19.19)
+ version: 1.18.1(@types/express@5.0.6)(@types/node@22.19.19)
parse-srcset:
specifier: ^1.0.2
version: 1.0.2
@@ -4592,7 +4592,7 @@ importers:
version: 1.2.0
devalue:
specifier: ^5.8.1
- version: 5.8.1
+ version: 5.9.0
prismjs:
specifier: ^1.30.0
version: 1.30.0
@@ -5132,7 +5132,7 @@ importers:
version: link:../../../scripts
devalue:
specifier: ^5.8.1
- version: 5.8.1
+ version: 5.9.0
linkedom:
specifier: ^0.18.12
version: 0.18.12
@@ -5394,7 +5394,7 @@ importers:
version: 4.0.2
'@shikijs/twoslash':
specifier: ^4.0.2
- version: 4.0.2(supports-color@8.1.1)(typescript@6.0.3)
+ version: 4.0.2(typescript@6.0.3)
'@types/estree':
specifier: ^1.0.8
version: 1.0.8
@@ -5649,7 +5649,7 @@ 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.1.0)
+ version: 2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(vite@8.1.0)
'@vercel/nft':
specifier: ^1.3.2
version: 1.3.2
@@ -5677,7 +5677,7 @@ importers:
version: 1.2.0
devalue:
specifier: ^5.8.1
- version: 5.8.1
+ version: 5.9.0
typescript:
specifier: ^6.0.3
version: 6.0.3
@@ -5786,8 +5786,8 @@ importers:
version: 1.0.1
devDependencies:
'@fastify/middie':
- specifier: ^9.1.0
- version: 9.3.2
+ specifier: ^9.3.3
+ version: 9.3.3
'@fastify/static':
specifier: ^9.0.0
version: 9.0.0
@@ -5813,17 +5813,17 @@ importers:
specifier: 1.2.0
version: 1.2.0
devalue:
- specifier: ^5.8.1
- version: 5.8.1
+ specifier: ^5.9.0
+ version: 5.9.0
express:
specifier: ^5.2.1
- version: 5.2.1(supports-color@8.1.1)
+ version: 5.2.1
fastify:
- specifier: ^5.7.4
- version: 5.8.5
+ specifier: ^5.12.0
+ version: 5.12.0
node-mocks-http:
- specifier: ^1.17.2
- version: 1.17.2(@types/express@5.0.6)(@types/node@22.19.19)
+ specifier: ^1.18.1
+ version: 1.18.1(@types/express@5.0.6)(@types/node@22.19.19)
packages/integrations/node/test/fixtures/api-route:
dependencies:
@@ -6022,13 +6022,13 @@ importers:
version: link:../../internal-helpers
'@preact/preset-vite':
specifier: ^2.10.5
- version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(supports-color@8.1.1)(vite@8.1.0)
+ version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(vite@8.1.0)
'@preact/signals':
specifier: ^2.8.2
version: 2.8.2(preact@10.29.0)
devalue:
specifier: ^5.8.1
- version: 5.8.1
+ version: 5.9.0
preact-render-to-string:
specifier: ^6.6.6
version: 6.6.6(preact@10.29.0)
@@ -6056,7 +6056,7 @@ importers:
version: 5.2.0(vite@8.1.0)
devalue:
specifier: ^5.8.1
- version: 5.8.1
+ version: 5.9.0
ultrahtml:
specifier: ^1.6.0
version: 1.6.0
@@ -6568,7 +6568,7 @@ importers:
version: 8.1.0(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
vite-plugin-vue-devtools:
specifier: ^8.1.0
- version: 8.1.0(supports-color@8.1.1)(vite@8.1.0)(vue@3.5.30)
+ version: 8.1.0(vite@8.1.0)(vue@3.5.30)
devDependencies:
astro:
specifier: workspace:*
@@ -6596,7 +6596,7 @@ importers:
version: link:../../../../../astro
vite-svg-loader:
specifier: 5.1.1
- version: 5.1.1(supports-color@8.1.1)(vue@3.5.30)
+ version: 5.1.1(vue@3.5.30)
vue:
specifier: ^3.5.30
version: 3.5.30(typescript@6.0.3)
@@ -6611,7 +6611,7 @@ importers:
version: link:../../../../../astro
vite-svg-loader:
specifier: 5.1.1
- version: 5.1.1(supports-color@8.1.1)(vue@3.5.30)
+ version: 5.1.1(vue@3.5.30)
vue:
specifier: ^3.5.30
version: 3.5.30(typescript@6.0.3)
@@ -6635,7 +6635,7 @@ importers:
version: link:../../../../../astro
vite-svg-loader:
specifier: 5.1.1
- version: 5.1.1(supports-color@8.1.1)(vue@3.5.30)
+ version: 5.1.1(vue@3.5.30)
vue:
specifier: ^3.5.30
version: 3.5.30(typescript@6.0.3)
@@ -6971,7 +6971,7 @@ importers:
version: 11.7.5
ovsx:
specifier: ^0.10.10
- version: 0.10.10(supports-color@8.1.1)
+ version: 0.10.10
tsx:
specifier: ^4.22.0
version: 4.22.3
@@ -8756,8 +8756,8 @@ packages:
'@fastify/merge-json-schemas@0.2.1':
resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==}
- '@fastify/middie@9.3.2':
- resolution: {integrity: sha512-5C3xMHJxpfqoHd+xZSHPBI71fpzkoF6wMsYtgzXRyQUNvsIAxJm2yY4r2fUjF0h3rS9MXlo/aXLaXv3s4TL+JQ==}
+ '@fastify/middie@9.3.3':
+ resolution: {integrity: sha512-N2VRS+sfw/lxA/uLD6TvBpvfPfOV/RdYzM7ctAB58Cn2TAYxbRd8+JLFhNhMJoXlSbgChXYUnkn3hPU/JdA4Pg==}
'@fastify/proxy-addr@5.1.0':
resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==}
@@ -11799,8 +11799,8 @@ packages:
dettle@1.0.5:
resolution: {integrity: sha512-ZVyjhAJ7sCe1PNXEGveObOH9AC8QvMga3HJIghHawtG7mE4K5pW9nz/vDGAr/U7a3LWgdOzEE7ac9MURnyfaTA==}
- devalue@5.8.1:
- resolution: {integrity: sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==}
+ devalue@5.9.0:
+ resolution: {integrity: sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==}
devlop@1.1.0:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
@@ -12203,6 +12203,9 @@ packages:
fast-json-stringify@6.3.0:
resolution: {integrity: sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==}
+ fast-json-stringify@7.0.1:
+ resolution: {integrity: sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==}
+
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
@@ -12215,6 +12218,9 @@ packages:
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+ fast-uri@4.1.2:
+ resolution: {integrity: sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==}
+
fast-xml-builder@1.2.0:
resolution: {integrity: sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==}
@@ -12229,8 +12235,11 @@ packages:
fastify-plugin@5.1.0:
resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==}
- fastify@5.8.5:
- resolution: {integrity: sha512-Yqptv59pQzPgQUSIm87hMqHJmdkb1+GPxdE6vW6FRyVE9G86mt7rOghitiU4JHRaTyDUk9pfeKmDeu70lAwM4Q==}
+ fastify-plugin@6.0.0:
+ resolution: {integrity: sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg==}
+
+ fastify@5.12.0:
+ resolution: {integrity: sha512-A3RNEaDIHWaxFW8n8rNJaW1wQ+XAXuoU71llfUQJjuh5WaYLmKfRhhanaJBOx8m2EBPQkR6sDBovYdHNe9F6rA==}
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -12280,8 +12289,8 @@ packages:
resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
engines: {node: '>= 18.0.0'}
- find-my-way@9.5.0:
- resolution: {integrity: sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==}
+ find-my-way@9.8.0:
+ resolution: {integrity: sha512-JtyUgATO7qxRp2zKhrmWof74Mqxc1ikbwpwMY97p8ipuTj2QtreA4gK2JNAF6SOqqHnYYkwMUvsgQVi2AJxIyw==}
engines: {node: '>=20'}
find-process@2.1.1:
@@ -13776,8 +13785,8 @@ packages:
node-mock-http@1.0.4:
resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==}
- node-mocks-http@1.17.2:
- resolution: {integrity: sha512-HVxSnjNzE9NzoWMx9T9z4MLqwMpLwVvA0oVZ+L+gXskYXEJ6tFn3Kx4LargoB6ie7ZlCLplv7QbWO6N+MysWGA==}
+ node-mocks-http@1.18.1:
+ resolution: {integrity: sha512-hPMOLJZzhgT4i/zbYpfy1P2ulAlHtzFcrEGxPh/4pDRegCY3+p2sxDhQm1fxte5EgI/RwDx+NNW7v8kJcyxtMg==}
engines: {node: '>=14'}
peerDependencies:
'@types/express': ^4.17.21 || ^5.0.0
@@ -14413,8 +14422,8 @@ packages:
process-warning@4.0.1:
resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==}
- process-warning@5.0.0:
- resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
+ process-warning@5.1.0:
+ resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
@@ -17551,7 +17560,7 @@ snapshots:
'@earendil-works/pi-agent-core@0.83.0(ws@8.20.1)(zod@4.3.6)':
dependencies:
- '@earendil-works/pi-ai': 0.83.0(supports-color@8.1.1)(ws@8.20.1)(zod@4.3.6)
+ '@earendil-works/pi-ai': 0.83.0(ws@8.20.1)(zod@4.3.6)
diff: 8.0.4
ignore: 7.0.5
typebox: 1.3.7
@@ -17564,7 +17573,7 @@ snapshots:
- ws
- zod
- '@earendil-works/pi-ai@0.83.0(supports-color@8.1.1)(ws@8.20.1)(zod@4.3.6)':
+ '@earendil-works/pi-ai@0.83.0(ws@8.20.1)(zod@4.3.6)':
dependencies:
'@anthropic-ai/sdk': 0.91.1(zod@4.3.6)
'@aws-sdk/client-bedrock-runtime': 3.1048.0
@@ -17572,7 +17581,7 @@ snapshots:
'@mistralai/mistralai': 2.2.6(@opentelemetry/api@1.9.0)
'@opentelemetry/api': 1.9.0
'@smithy/node-http-handler': 4.7.3
- http-proxy-agent: 7.0.2(supports-color@8.1.1)
+ http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
openai: 6.26.0(ws@8.20.1)(zod@4.3.6)
partial-json: 0.1.7
@@ -17855,12 +17864,12 @@ snapshots:
'@eslint-community/eslint-utils@4.9.1(eslint@10.4.0)':
dependencies:
- eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
+ eslint: 10.4.0(jiti@2.6.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
- '@eslint/config-array@0.23.5(supports-color@8.1.1)':
+ '@eslint/config-array@0.23.5':
dependencies:
'@eslint/object-schema': 3.0.5
debug: 4.4.3(supports-color@8.1.1)
@@ -17905,11 +17914,11 @@ snapshots:
dependencies:
dequal: 2.0.3
- '@fastify/middie@9.3.2':
+ '@fastify/middie@9.3.3':
dependencies:
'@fastify/error': 4.2.0
- fastify-plugin: 5.1.0
- find-my-way: 9.5.0
+ fastify-plugin: 6.0.0
+ find-my-way: 9.8.0
path-to-regexp: 8.4.2
reusify: 1.1.0
@@ -17938,7 +17947,7 @@ snapshots:
'@flue/runtime@2.0.3(typescript@6.0.3)(ws@8.20.1)(zod@4.3.6)':
dependencies:
'@earendil-works/pi-agent-core': 0.83.0(ws@8.20.1)(zod@4.3.6)
- '@earendil-works/pi-ai': 0.83.0(supports-color@8.1.1)(ws@8.20.1)(zod@4.3.6)
+ '@earendil-works/pi-ai': 0.83.0(ws@8.20.1)(zod@4.3.6)
'@hono/node-server': 2.0.4(hono@4.12.18)
'@modelcontextprotocol/client': 2.0.0
'@valibot/to-json-schema': 1.5.0(valibot@1.4.2)
@@ -18434,7 +18443,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.4.3)':
dependencies:
'@netlify/ai': 0.4.1
'@netlify/blobs': 10.7.5
@@ -18442,7 +18451,7 @@ snapshots:
'@netlify/database-dev': 0.10.1
'@netlify/dev-utils': 4.4.3
'@netlify/edge-functions-dev': 1.0.17
- '@netlify/functions-dev': 1.2.8(supports-color@8.1.1)
+ '@netlify/functions-dev': 1.2.8
'@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/redirects': 3.1.10
@@ -18514,7 +18523,7 @@ snapshots:
dependencies:
'@netlify/types': 2.6.0
- '@netlify/functions-dev@1.2.8(supports-color@8.1.1)':
+ '@netlify/functions-dev@1.2.8':
dependencies:
'@netlify/blobs': 10.7.5
'@netlify/dev-utils': 4.4.3
@@ -18522,7 +18531,7 @@ snapshots:
'@netlify/zip-it-and-ship-it': 14.5.6
cron-parser: 4.9.0
decache: 4.6.2
- extract-zip: 2.0.1(supports-color@8.1.1)
+ extract-zip: 2.0.1
is-stream: 4.0.1
jwt-decode: 4.0.0
lambda-local: 2.2.0
@@ -18625,9 +18634,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.1.0)':
+ '@netlify/vite-plugin@2.12.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(vite@8.1.0)':
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.4.3)
'@netlify/dev-utils': 4.4.3
dedent: 1.7.1
vite: 8.1.0(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
@@ -18980,7 +18989,7 @@ snapshots:
'@poppinss/exception@1.2.3': {}
- '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.0)(supports-color@8.1.1)(vite@8.1.0)':
+ '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.0)(vite@8.1.0)':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0)
@@ -19237,11 +19246,11 @@ snapshots:
dependencies:
'@shikijs/types': 4.0.2
- '@shikijs/twoslash@4.0.2(supports-color@8.1.1)(typescript@6.0.3)':
+ '@shikijs/twoslash@4.0.2(typescript@6.0.3)':
dependencies:
'@shikijs/core': 4.0.2
'@shikijs/types': 4.0.2
- twoslash: 0.3.8(supports-color@8.1.1)(typescript@6.0.3)
+ twoslash: 0.3.8(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -19690,15 +19699,15 @@ snapshots:
'@types/node': 22.19.19
optional: true
- '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2)(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)':
+ '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2)(eslint@10.4.0)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.59.2
- '@typescript-eslint/type-utils': 8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript-eslint/type-utils': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.59.2
- eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
+ eslint: 10.4.0(jiti@2.6.1)
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3)
@@ -19706,14 +19715,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)':
+ '@typescript-eslint/parser@8.59.2(eslint@10.4.0)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.59.2
'@typescript-eslint/types': 8.59.2
'@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.59.2
debug: 4.4.3(supports-color@8.1.1)
- eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
+ eslint: 10.4.0(jiti@2.6.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -19749,13 +19758,13 @@ snapshots:
dependencies:
typescript: 6.0.3
- '@typescript-eslint/type-utils@8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)':
+ '@typescript-eslint/type-utils@8.59.2(eslint@10.4.0)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.59.2
'@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
debug: 4.4.3(supports-color@8.1.1)
- eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
+ eslint: 10.4.0(jiti@2.6.1)
ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3
transitivePeerDependencies:
@@ -19799,7 +19808,7 @@ snapshots:
'@typescript-eslint/scope-manager': 8.59.2
'@typescript-eslint/types': 8.59.2
'@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
- eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
+ eslint: 10.4.0(jiti@2.6.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -19809,7 +19818,7 @@ snapshots:
'@typescript-eslint/types': 8.59.2
eslint-visitor-keys: 5.0.1
- '@typescript/vfs@1.6.4(supports-color@8.1.1)(typescript@6.0.3)':
+ '@typescript/vfs@1.6.4(typescript@6.0.3)':
dependencies:
debug: 4.4.3(supports-color@8.1.1)
typescript: 6.0.3
@@ -19818,7 +19827,7 @@ snapshots:
'@typespec/ts-http-runtime@0.3.3':
dependencies:
- http-proxy-agent: 7.0.2(supports-color@8.1.1)
+ http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
tslib: 2.8.1
transitivePeerDependencies:
@@ -20064,7 +20073,7 @@ snapshots:
'@vscode/test-electron@2.5.2':
dependencies:
- http-proxy-agent: 7.0.2(supports-color@8.1.1)
+ http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
jszip: 3.10.1
ora: 8.2.0
@@ -20074,7 +20083,7 @@ snapshots:
'@vscode/test-electron@3.1.0':
dependencies:
- http-proxy-agent: 7.0.2(supports-color@8.1.1)
+ http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.6
jszip: 3.10.1
ora: 8.2.0
@@ -20152,7 +20161,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@vscode/vsce@3.7.1(supports-color@8.1.1)':
+ '@vscode/vsce@3.7.1':
dependencies:
'@azure/identity': 4.13.0
'@secretlint/node': 10.2.2
@@ -20175,7 +20184,7 @@ snapshots:
minimatch: 3.1.2
parse-semver: 1.1.1
read: 1.0.7
- secretlint: 10.2.2(supports-color@8.1.1)
+ secretlint: 10.2.2
semver: 7.8.5
tmp: 0.2.5
typed-rest-client: 1.8.11
@@ -20675,7 +20684,7 @@ snapshots:
blake3-wasm@2.1.5: {}
- body-parser@2.2.2(supports-color@8.1.1):
+ body-parser@2.2.2:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
@@ -21239,7 +21248,7 @@ snapshots:
dettle@1.0.5: {}
- devalue@5.8.1: {}
+ devalue@5.9.0: {}
devlop@1.1.0:
dependencies:
@@ -21526,7 +21535,7 @@ snapshots:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0)
'@eslint-community/regexpp': 4.12.2
comment-parser: 1.4.5
- eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
+ eslint: 10.4.0(jiti@2.6.1)
jsdoc-type-pratt-parser: 7.1.1
refa: 0.12.1
regexp-ast-analysis: 0.7.1
@@ -21543,11 +21552,11 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
- eslint@10.4.0(jiti@2.6.1)(supports-color@8.1.1):
+ eslint@10.4.0(jiti@2.6.1):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0)
'@eslint-community/regexpp': 4.12.2
- '@eslint/config-array': 0.23.5(supports-color@8.1.1)
+ '@eslint/config-array': 0.23.5
'@eslint/config-helpers': 0.6.0
'@eslint/core': 1.2.1
'@eslint/plugin-kit': 0.7.1
@@ -21681,10 +21690,10 @@ snapshots:
expect-type@1.3.0: {}
- express@5.2.1(supports-color@8.1.1):
+ express@5.2.1:
dependencies:
accepts: 2.0.0
- body-parser: 2.2.2(supports-color@8.1.1)
+ body-parser: 2.2.2
content-disposition: 1.0.1
content-type: 1.0.5
cookie: 0.7.2
@@ -21694,7 +21703,7 @@ snapshots:
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
- finalhandler: 2.1.1(supports-color@8.1.1)
+ finalhandler: 2.1.1
fresh: 2.0.0
http-errors: 2.0.1
merge-descriptors: 2.0.0
@@ -21705,7 +21714,7 @@ snapshots:
proxy-addr: 2.0.7
qs: 6.14.2
range-parser: 1.2.1
- router: 2.2.0(supports-color@8.1.1)
+ router: 2.2.0
send: 1.2.1
serve-static: 2.2.1
statuses: 2.0.2
@@ -21718,7 +21727,7 @@ snapshots:
extendable-error@0.1.7: {}
- extract-zip@2.0.1(supports-color@8.1.1):
+ extract-zip@2.0.1:
dependencies:
debug: 4.4.3(supports-color@8.1.1)
get-stream: 5.2.0
@@ -21759,6 +21768,15 @@ snapshots:
json-schema-ref-resolver: 3.0.0
rfdc: 1.4.1
+ fast-json-stringify@7.0.1:
+ dependencies:
+ '@fastify/merge-json-schemas': 0.2.1
+ ajv: 8.20.0
+ ajv-formats: 3.0.1
+ fast-uri: 4.1.2
+ json-schema-ref-resolver: 3.0.0
+ rfdc: 1.4.1
+
fast-levenshtein@2.0.6: {}
fast-querystring@1.1.2:
@@ -21769,6 +21787,8 @@ snapshots:
fast-uri@3.1.0: {}
+ fast-uri@4.1.2: {}
+
fast-xml-builder@1.2.0:
dependencies:
path-expression-matcher: 1.5.0
@@ -21791,7 +21811,9 @@ snapshots:
fastify-plugin@5.1.0: {}
- fastify@5.8.5:
+ fastify-plugin@6.0.0: {}
+
+ fastify@5.12.0:
dependencies:
'@fastify/ajv-compiler': 4.0.5
'@fastify/error': 4.2.0
@@ -21799,11 +21821,11 @@ snapshots:
'@fastify/proxy-addr': 5.1.0
abstract-logging: 2.0.1
avvio: 9.2.0
- fast-json-stringify: 6.3.0
- find-my-way: 9.5.0
+ fast-json-stringify: 7.0.1
+ find-my-way: 9.8.0
light-my-request: 6.6.0
pino: 10.3.1
- process-warning: 5.0.0
+ process-warning: 5.1.0
rfdc: 1.4.1
secure-json-parse: 4.1.0
semver: 7.8.5
@@ -21848,7 +21870,7 @@ snapshots:
filter-obj@6.1.0: {}
- finalhandler@2.1.1(supports-color@8.1.1):
+ finalhandler@2.1.1:
dependencies:
debug: 4.4.3(supports-color@8.1.1)
encodeurl: 2.0.0
@@ -21859,7 +21881,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- find-my-way@9.5.0:
+ find-my-way@9.8.0:
dependencies:
fast-deep-equal: 3.1.3
fast-querystring: 1.1.2
@@ -22384,7 +22406,7 @@ snapshots:
http-parser-js@0.5.10: {}
- http-proxy-agent@7.0.2(supports-color@8.1.1):
+ http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
debug: 4.4.3(supports-color@8.1.1)
@@ -23726,7 +23748,7 @@ snapshots:
node-mock-http@1.0.4: {}
- node-mocks-http@1.17.2(@types/express@5.0.6)(@types/node@22.19.19):
+ node-mocks-http@1.18.1(@types/express@5.0.6)(@types/node@22.19.19):
dependencies:
accepts: 1.3.8
content-disposition: 0.5.4
@@ -23735,7 +23757,6 @@ snapshots:
merge-descriptors: 1.0.3
methods: 1.1.2
mime: 1.6.0
- parseurl: 1.3.3
range-parser: 1.2.1
type-is: 1.6.18
optionalDependencies:
@@ -23866,9 +23887,9 @@ snapshots:
outdent@0.5.0: {}
- ovsx@0.10.10(supports-color@8.1.1):
+ ovsx@0.10.10:
dependencies:
- '@vscode/vsce': 3.7.1(supports-color@8.1.1)
+ '@vscode/vsce': 3.7.1
commander: 6.2.1
follow-redirects: 1.15.11
is-ci: 2.0.0
@@ -24114,7 +24135,7 @@ snapshots:
on-exit-leak-free: 2.1.2
pino-abstract-transport: 3.0.0
pino-std-serializers: 7.1.0
- process-warning: 5.0.0
+ process-warning: 5.1.0
quick-format-unescaped: 4.0.4
real-require: 0.2.0
safe-stable-stringify: 2.5.0
@@ -24459,7 +24480,7 @@ snapshots:
process-warning@4.0.1: {}
- process-warning@5.0.0: {}
+ process-warning@5.1.0: {}
process@0.11.10: {}
@@ -24931,7 +24952,7 @@ snapshots:
rosie-skills-freebsd-x64: 0.6.4
rosie-skills-linux-x64: 0.6.4
- router@2.2.0(supports-color@8.1.1):
+ router@2.2.0:
dependencies:
debug: 4.4.3(supports-color@8.1.1)
depd: 2.0.0
@@ -25010,7 +25031,7 @@ snapshots:
scule@1.3.0: {}
- secretlint@10.2.2(supports-color@8.1.1):
+ secretlint@10.2.2:
dependencies:
'@secretlint/config-creator': 10.2.2
'@secretlint/formatter': 10.2.2
@@ -25450,7 +25471,7 @@ snapshots:
aria-query: 5.3.1
axobject-query: 4.1.0
clsx: 2.1.1
- devalue: 5.8.1
+ devalue: 5.9.0
esm-env: 1.2.2
esrap: 2.2.4
is-reference: 3.0.3
@@ -25643,9 +25664,9 @@ snapshots:
twoslash-protocol@0.3.8: {}
- twoslash@0.3.8(supports-color@8.1.1)(typescript@6.0.3):
+ twoslash@0.3.8(typescript@6.0.3):
dependencies:
- '@typescript/vfs': 1.6.4(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript/vfs': 1.6.4(typescript@6.0.3)
twoslash-protocol: 0.3.8
typescript: 6.0.3
transitivePeerDependencies:
@@ -25692,13 +25713,13 @@ snapshots:
dependencies:
semver: 7.8.5
- typescript-eslint@8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3):
+ typescript-eslint@8.59.2(eslint@10.4.0)(typescript@6.0.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2)(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
- '@typescript-eslint/parser': 8.59.2(eslint@10.4.0)(supports-color@8.1.1)(typescript@6.0.3)
+ '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2)(eslint@10.4.0)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
'@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
'@typescript-eslint/utils': 8.59.2(eslint@10.4.0)(typescript@6.0.3)
- eslint: 10.4.0(jiti@2.6.1)(supports-color@8.1.1)
+ eslint: 10.4.0(jiti@2.6.1)
typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -25935,7 +25956,7 @@ snapshots:
dependencies:
vite: 8.1.0(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
- vite-plugin-inspect@11.3.3(supports-color@8.1.1)(vite@8.1.0):
+ vite-plugin-inspect@11.3.3(vite@8.1.0):
dependencies:
ansis: 4.2.0
debug: 4.4.3(supports-color@8.1.1)
@@ -25963,14 +25984,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- vite-plugin-vue-devtools@8.1.0(supports-color@8.1.1)(vite@8.1.0)(vue@3.5.30):
+ vite-plugin-vue-devtools@8.1.0(vite@8.1.0)(vue@3.5.30):
dependencies:
'@vue/devtools-core': 8.1.0(vue@3.5.30)
'@vue/devtools-kit': 8.1.0
'@vue/devtools-shared': 8.1.0
sirv: 3.0.2
vite: 8.1.0(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
- vite-plugin-inspect: 11.3.3(supports-color@8.1.1)(vite@8.1.0)
+ vite-plugin-inspect: 11.3.3(vite@8.1.0)
vite-plugin-vue-inspector: 5.3.2(vite@8.1.0)
transitivePeerDependencies:
- '@nuxt/kit'
@@ -26002,7 +26023,7 @@ snapshots:
stack-trace: 1.0.0-pre2
vite: 8.1.0(@types/node@22.19.19)(esbuild@0.28.0)(jiti@2.6.1)(sass@1.98.0)(tsx@4.22.3)(yaml@2.9.0)
- vite-svg-loader@5.1.1(supports-color@8.1.1)(vue@3.5.30):
+ vite-svg-loader@5.1.1(vue@3.5.30):
dependencies:
debug: 4.4.3(supports-color@8.1.1)
svgo: 3.3.3
From 1f60921b8e263e9bc9152eaa666c72b0a8a9a787 Mon Sep 17 00:00:00 2001
From: Florian Lefebvre
Date: Thu, 20 Aug 2026 13:46:13 +0200
Subject: [PATCH 08/21] chore: knip prod mode (#17734)
---
knip.js | 54 ++++++++++++++++++++++++++++++++++++++++++--------
package.json | 11 +++++-----
pnpm-lock.yaml | 7 +++----
3 files changed, 54 insertions(+), 18 deletions(-)
diff --git a/knip.js b/knip.js
index 0a36a8b58989..27bb395321fe 100644
--- a/knip.js
+++ b/knip.js
@@ -1,17 +1,36 @@
// @ts-check
-const srcEntry = 'src/**/*.{js,ts,cts}';
-const dtsEntry = '*.d.ts';
+
+// Patterns suffixed with `!` are the ones used in production mode (`knip --production`), which only
+// analyzes the code we ship. Patterns without the suffix are dev-only: Knip automatically negates
+// them in production mode. See https://knip.dev/features/production-mode
+const srcEntry = 'src/**/*.{js,ts,cts}!';
+const dtsEntry = '*.d.ts!';
const testEntry = 'test/**/*.test.{js,ts}';
+// `project` defines the files Knip analyzes, so it is where files are excluded from the analysis
+// altogether (`ignore` only suppresses issues in files that are still analyzed).
+// See https://knip.dev/guides/configuring-project-files
+const project = [
+ '**/*!',
+ // Fixtures and hosted test apps are standalone projects of their own
+ '!**/{test,e2e}/**/{fixtures,_temp-fixtures}/**',
+ '!test/hosted/hosted-astro-project/**',
+ // Tests are part of the analysis, but never of the production graph
+ '!test/**!',
+ '!e2e/**!',
+];
+
/** @type {import('knip').KnipConfig} */
export default {
- ignore: ['**/test/**/{fixtures,_temp-fixtures}/**', 'triage/**', '.github/scripts/**'],
tags: ['-lintignore'],
ignoreWorkspaces: [
'examples/**',
'**/{test,e2e}/**/{fixtures,_temp-fixtures}/**',
'benchmark/**',
'packages/language-tools/**/*',
+ // Standalone projects living inside packages
+ 'packages/astro/performance/**',
+ '**/test/hosted/hosted-astro-project/**',
],
workspaces: {
'.': {
@@ -24,14 +43,21 @@ export default {
// to be installed in the vscode package, but knip is expecting them to be in the root node_modules
ignoreBinaries: ['docgen', 'docgen:errors', 'playwright', 'vsce', 'ovsx'],
entry: ['.agents/evals/*.ts'],
+ // The root workspace ships nothing, so none of its files are part of the production graph
+ project: ['**/*', '!triage/**', '!.github/scripts/**'],
+ },
+ // Internal tooling package: it publishes nothing, so all of its commands are entry points
+ scripts: {
+ entry: ['*.js!', '{deps,smoke}/*.js!'],
},
'packages/*': {
entry: [srcEntry, dtsEntry, testEntry],
+ project,
},
'packages/astro': {
entry: [
// Can't be detected automatically since it's only in package.json#files
- 'templates/**/*',
+ 'templates/**/*!',
srcEntry,
dtsEntry,
testEntry,
@@ -42,11 +68,10 @@ export default {
'test/test-image-service.ts',
'test/test-remote-image-service.ts',
// Can't detect this file when using inside a vite plugin
- 'src/vite-plugin-app/createAstroServerApp.ts',
+ 'src/vite-plugin-app/createAstroServerApp.ts!',
],
+ project,
ignore: [
- '**/e2e/**/{fixtures,_temp-fixtures}/**',
- 'performance/**/*',
// This export is resolved dynamically in packages/astro/src/vite-plugin-app/index.ts
'src/vite-plugin-app/createExports.ts',
],
@@ -64,43 +89,56 @@ export default {
},
'packages/astro-prism': {
entry: [srcEntry, dtsEntry, testEntry],
+ project,
ignoreUnresolved: ['#prism-loadLanguages'],
},
'packages/integrations/*': {
entry: [srcEntry, dtsEntry, testEntry],
+ project,
},
'packages/integrations/cloudflare': {
entry: [srcEntry, dtsEntry, testEntry],
+ project,
// False positive because of cloudflare:workers
ignoreDependencies: ['cloudflare'],
},
'packages/integrations/netlify': {
entry: [srcEntry, dtsEntry, testEntry],
+ project,
+ // Runtime dependency of the Netlify Blobs session driver, which the adapter enables but
+ // never imports by name
+ ignoreDependencies: ['@netlify/blobs'],
},
'packages/integrations/solid': {
entry: [srcEntry, dtsEntry, testEntry],
+ project,
// It's an optional peer dep (triggers a warning) but it's fine in this case
ignoreDependencies: ['solid-devtools'],
},
'packages/integrations/svelte': {
entry: [srcEntry, dtsEntry, testEntry],
+ project,
// Used in testing-library compatibility tests but not directly imported
ignoreDependencies: ['@testing-library/svelte'],
},
'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'],
},
'packages/markdown/remark': {
entry: [srcEntry, dtsEntry, testEntry],
+ project,
},
'packages/markdown/satteri': {
entry: [srcEntry, dtsEntry, testEntry],
+ project,
},
'packages/upgrade': {
- entry: ['src/index.ts', testEntry],
+ entry: ['src/index.ts!', testEntry],
+ project,
},
},
};
diff --git a/package.json b/package.json
index bb55a066267e..bf730e90406d 100644
--- a/package.json
+++ b/package.json
@@ -44,11 +44,12 @@
"eval:skills:validate": "vitest list --config vitest.skills.config.ts",
"typecheck": "tsc -b",
"benchmark": "astro-benchmark",
- "lint": "biome lint && knip && eslint --cache --concurrency=auto",
- "lint:ai": "biome lint --reporter=concise && knip && eslint --cache --concurrency=auto",
- "lint:ci": "knip && pnpm run eslint:ci",
+ "lint": "biome lint && pnpm run knip && eslint --cache --concurrency=auto",
+ "lint:ai": "biome lint --reporter=concise && pnpm run knip && eslint --cache --concurrency=auto",
+ "lint:ci": "pnpm run knip && pnpm run eslint:ci",
"eslint:ci": "NODE_OPTIONS=\"--max-old-space-size=8192\" eslint --cache --concurrency=auto",
"lint:fix": "biome lint --write --unsafe",
+ "knip": "knip && knip --production",
"publint": "pnpm -r --filter=astro --filter=create-astro --filter=\"@astrojs/*\" --no-bail exec publint",
"version": "changeset version && node ./scripts/deps/update-example-versions.js && pnpm install --no-frozen-lockfile && pnpm run format"
},
@@ -62,9 +63,6 @@
"node": ">=22.12.0"
},
"packageManager": "pnpm@11.13.1",
- "dependencies": {
- "astro-benchmark": "workspace:*"
- },
"devDependencies": {
"@astrojs/check": "^0.9.5",
"@biomejs/biome": "2.5.3",
@@ -73,6 +71,7 @@
"@earendil-works/pi-ai": "^0.83.0",
"@flue/runtime": "^2.0.3",
"@types/node": "^22.10.6",
+ "astro-benchmark": "workspace:*",
"bgproc": "^0.2.0",
"eslint": "^10.4.0",
"eslint-plugin-regexp": "^3.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d0965924728b..b0784cfed33e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -211,10 +211,6 @@ overrides:
importers:
.:
- dependencies:
- astro-benchmark:
- specifier: workspace:*
- version: link:benchmark
devDependencies:
'@astrojs/check':
specifier: ^0.9.5
@@ -237,6 +233,9 @@ importers:
'@types/node':
specifier: ^22.19.0
version: 22.19.19
+ astro-benchmark:
+ specifier: workspace:*
+ version: link:benchmark
bgproc:
specifier: ^0.2.0
version: 0.2.0
From 5f419e25c570002a2ce0e10a973aa13336016b0c Mon Sep 17 00:00:00 2001
From: "astro-factory[bot]"
<316791938+astro-factory[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 13:26:28 +0100
Subject: [PATCH 09/21] Fall back to requestUrl.origin in
RemoteRuntimeFontFileUrlResolver when server address is null (#17758)
Co-authored-by: factory[bot]
Co-authored-by: ematipico
---
.changeset/better-zebras-notice.md | 5 +++
.../remote-runtime-font-file-url-resolver.ts | 36 +++++++++++++------
.../test/units/assets/fonts/infra.test.ts | 35 +++++++++++++++---
3 files changed, 61 insertions(+), 15 deletions(-)
create mode 100644 .changeset/better-zebras-notice.md
diff --git a/.changeset/better-zebras-notice.md b/.changeset/better-zebras-notice.md
new file mode 100644
index 000000000000..ff1c2e5799aa
--- /dev/null
+++ b/.changeset/better-zebras-notice.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes a bug where `experimental_getFontFileURL()` rejected valid font URLs when using the Cloudflare adapter
diff --git a/packages/astro/src/assets/fonts/infra/remote-runtime-font-file-url-resolver.ts b/packages/astro/src/assets/fonts/infra/remote-runtime-font-file-url-resolver.ts
index a397db7221eb..7bf2770020a4 100644
--- a/packages/astro/src/assets/fonts/infra/remote-runtime-font-file-url-resolver.ts
+++ b/packages/astro/src/assets/fonts/infra/remote-runtime-font-file-url-resolver.ts
@@ -6,9 +6,11 @@ import type { RuntimeFontFileUrlResolver } from '../definitions.js';
* During prerendering, a temporary Node HTTP server is started to
* serve font files.
*
- * We send request to the provided server address. `requestUrl` on
- * `fetch` is not implemented because we have the information from
- * within the Vite plugin already.
+ * When possible, the resolver uses a statically known server
+ * {@link address}. When the address is not yet available (e.g. the
+ * virtual module was evaluated before the HTTP server started
+ * listening — see #17722), the resolver falls back to deriving the
+ * origin from the caller-supplied {@link requestUrl}.
*/
export class RemoteRuntimeFontFileUrlResolver implements RuntimeFontFileUrlResolver {
#urls: Set;
@@ -25,19 +27,31 @@ export class RemoteRuntimeFontFileUrlResolver implements RuntimeFontFileUrlResol
this.#address = address;
}
- resolve(url: string): string | null {
+ resolve(url: string, requestUrl: URL | undefined): string | null {
if (!this.#urls.has(url)) {
return null;
}
- if (!this.#address) {
- throw new Error('Server address unavailable, this should not happen. Open an issue.');
- }
// assetsPrefix
if (!url.startsWith('/')) {
- url = new URL(url).pathname;
+ if (this.#address) {
+ url = new URL(url).pathname;
+ } else {
+ return url;
+ }
+ }
+ if (this.#address) {
+ const host =
+ this.#address.family === 'IPv6'
+ ? `[${this.#address.address}]`
+ : this.#address.address;
+ return `http://${host}:${this.#address.port}${url}`;
+ }
+ // Fallback when the server address was not available at module
+ // load time (e.g. an adapter's dep optimizer pre-bundled the
+ // font runtime before the HTTP server started listening, #17722).
+ if (requestUrl) {
+ return `${requestUrl.origin}${url}`;
}
- const host =
- this.#address.family === 'IPv6' ? `[${this.#address.address}]` : this.#address.address;
- return `http://${host}:${this.#address.port}${url}`;
+ throw new Error('Server address unavailable, this should not happen. Open an issue.');
}
}
diff --git a/packages/astro/test/units/assets/fonts/infra.test.ts b/packages/astro/test/units/assets/fonts/infra.test.ts
index f9c06b3e6ab1..8d4c3b252875 100644
--- a/packages/astro/test/units/assets/fonts/infra.test.ts
+++ b/packages/astro/test/units/assets/fonts/infra.test.ts
@@ -783,7 +783,7 @@ describe('fonts infra', () => {
address: { address: '127.0.0.1', family: 'IPv4', port: 3000 },
});
- assert.equal(resolver.resolve('/_astro/fonts/bar.woff2'), null);
+ assert.equal(resolver.resolve('/_astro/fonts/bar.woff2', undefined), null);
});
it('works with ipv4', () => {
@@ -793,7 +793,7 @@ describe('fonts infra', () => {
});
assert.equal(
- resolver.resolve('/test/_astro/fonts/foo.woff2'),
+ resolver.resolve('/test/_astro/fonts/foo.woff2', undefined),
'http://127.0.0.1:3000/test/_astro/fonts/foo.woff2',
);
});
@@ -805,7 +805,7 @@ describe('fonts infra', () => {
});
assert.equal(
- resolver.resolve('/_astro/fonts/foo.woff2'),
+ resolver.resolve('/_astro/fonts/foo.woff2', undefined),
'http://[::]:3000/_astro/fonts/foo.woff2',
);
});
@@ -817,10 +817,37 @@ describe('fonts infra', () => {
});
assert.equal(
- resolver.resolve('http://cdn.example.com/_astro/fonts/foo.woff2'),
+ resolver.resolve('http://cdn.example.com/_astro/fonts/foo.woff2', undefined),
'http://127.0.0.1:3000/_astro/fonts/foo.woff2',
);
});
+
+ it('falls back to requestUrl when address is null', () => {
+ const resolver = new RemoteRuntimeFontFileUrlResolver({
+ urls: new Set(['/_astro/fonts/foo.woff2']),
+ address: null,
+ });
+
+ assert.equal(
+ resolver.resolve(
+ '/_astro/fonts/foo.woff2',
+ new URL('http://localhost:4321/og.png'),
+ ),
+ 'http://localhost:4321/_astro/fonts/foo.woff2',
+ );
+ });
+
+ it('returns full url directly when address is null and url is absolute (assetsPrefix)', () => {
+ const resolver = new RemoteRuntimeFontFileUrlResolver({
+ urls: new Set(['http://cdn.example.com/_astro/fonts/foo.woff2']),
+ address: null,
+ });
+
+ assert.equal(
+ resolver.resolve('http://cdn.example.com/_astro/fonts/foo.woff2', undefined),
+ 'http://cdn.example.com/_astro/fonts/foo.woff2',
+ );
+ });
});
describe('SsrRuntimeFontFileUrlResolver', () => {
From c7811b8f15b29b28a2921c944b9aa83a78af0585 Mon Sep 17 00:00:00 2001
From: "astro-factory[bot]"
Date: Thu, 20 Aug 2026 12:27:44 +0000
Subject: [PATCH 10/21] [ci] format
---
.../fonts/infra/remote-runtime-font-file-url-resolver.ts | 4 +---
packages/astro/test/units/assets/fonts/infra.test.ts | 5 +----
2 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/packages/astro/src/assets/fonts/infra/remote-runtime-font-file-url-resolver.ts b/packages/astro/src/assets/fonts/infra/remote-runtime-font-file-url-resolver.ts
index 7bf2770020a4..43ea23a3217c 100644
--- a/packages/astro/src/assets/fonts/infra/remote-runtime-font-file-url-resolver.ts
+++ b/packages/astro/src/assets/fonts/infra/remote-runtime-font-file-url-resolver.ts
@@ -41,9 +41,7 @@ export class RemoteRuntimeFontFileUrlResolver implements RuntimeFontFileUrlResol
}
if (this.#address) {
const host =
- this.#address.family === 'IPv6'
- ? `[${this.#address.address}]`
- : this.#address.address;
+ this.#address.family === 'IPv6' ? `[${this.#address.address}]` : this.#address.address;
return `http://${host}:${this.#address.port}${url}`;
}
// Fallback when the server address was not available at module
diff --git a/packages/astro/test/units/assets/fonts/infra.test.ts b/packages/astro/test/units/assets/fonts/infra.test.ts
index 8d4c3b252875..cdf54e65f31c 100644
--- a/packages/astro/test/units/assets/fonts/infra.test.ts
+++ b/packages/astro/test/units/assets/fonts/infra.test.ts
@@ -829,10 +829,7 @@ describe('fonts infra', () => {
});
assert.equal(
- resolver.resolve(
- '/_astro/fonts/foo.woff2',
- new URL('http://localhost:4321/og.png'),
- ),
+ resolver.resolve('/_astro/fonts/foo.woff2', new URL('http://localhost:4321/og.png')),
'http://localhost:4321/_astro/fonts/foo.woff2',
);
});
From 3d50dfdd14e2eff09f28645b8e788aca36323ff1 Mon Sep 17 00:00:00 2001
From: "astro-factory[bot]"
<316791938+astro-factory[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 08:40:48 -0400
Subject: [PATCH 11/21] fix(dev): treat lock file as stale when PID matches
current process (#17744) (#17754)
In Docker containers, PID namespaces reset on restart, so the new
`astro dev` process often inherits the same PID the old one had. The
lock file from the previous run persists, and the process detects
itself as the "already running" server.
Add a self-PID guard to `isLockFileProcessAlive()`: if the lock file's
PID matches `process.pid`, treat it as stale immediately. The current
process cannot be the server recorded in the lock file because it
hasn't started one yet.
Co-authored-by: factory[bot]
---
.changeset/wicked-zebras-sip.md | 5 +++
packages/astro/src/core/dev/lockfile.ts | 8 ++++
.../astro/test/units/dev/lockfile.test.ts | 39 ++++++++++++++++---
pnpm-lock.yaml | 6 +++
4 files changed, 53 insertions(+), 5 deletions(-)
create mode 100644 .changeset/wicked-zebras-sip.md
diff --git a/.changeset/wicked-zebras-sip.md b/.changeset/wicked-zebras-sip.md
new file mode 100644
index 000000000000..0699a0ab6451
--- /dev/null
+++ b/.changeset/wicked-zebras-sip.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes the dev server refusing to start in Docker containers after a restart due to PID reuse in the lock file check
diff --git a/packages/astro/src/core/dev/lockfile.ts b/packages/astro/src/core/dev/lockfile.ts
index b27f9bcacd26..de67ddbc1103 100644
--- a/packages/astro/src/core/dev/lockfile.ts
+++ b/packages/astro/src/core/dev/lockfile.ts
@@ -124,6 +124,14 @@ export async function isLockFileProcessAlive(
data: LockFileData,
find: ProcessLookup = findProcess,
): Promise {
+ // The current process cannot be the server recorded in the lock file — it hasn't
+ // started one yet. In Docker containers the PID namespace resets on restart, so the
+ // new `astro dev` process often inherits the same PID the old one had. Without this
+ // guard, the process detects itself as the "already running" server. (#17744)
+ if (data.pid === process.pid) {
+ return false;
+ }
+
if (!isProcessAlive(data.pid)) {
return false;
}
diff --git a/packages/astro/test/units/dev/lockfile.test.ts b/packages/astro/test/units/dev/lockfile.test.ts
index 87bdfac92017..65cbbefcb149 100644
--- a/packages/astro/test/units/dev/lockfile.test.ts
+++ b/packages/astro/test/units/dev/lockfile.test.ts
@@ -220,7 +220,22 @@ describe('isAstroCommand', () => {
});
describe('isLockFileProcessAlive', () => {
- it('returns true when the recorded process command is Astro', async () => {
+ /** A long-lived child process whose PID is alive but is not the current process. */
+ let child: ReturnType;
+ let childPid: number;
+
+ before(() => {
+ child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 60_000)'], {
+ stdio: 'ignore',
+ });
+ childPid = child.pid!;
+ });
+
+ after(() => {
+ child.kill('SIGKILL');
+ });
+
+ it('returns false when the lock file PID matches the current process', async () => {
const data = { ...validData, pid: process.pid };
const findProcess = async () => [
{
@@ -231,15 +246,29 @@ describe('isLockFileProcessAlive', () => {
},
];
+ assert.equal(await isLockFileProcessAlive(data, findProcess), false);
+ });
+
+ it('returns true when the recorded process command is Astro', async () => {
+ const data = { ...validData, pid: childPid };
+ const findProcess = async () => [
+ {
+ pid: childPid,
+ ppid: process.pid,
+ name: 'node',
+ cmd: 'node /workspace/node_modules/astro/bin/astro.mjs dev',
+ },
+ ];
+
assert.equal(await isLockFileProcessAlive(data, findProcess), true);
});
it('returns false when the PID belongs to another command', async () => {
- const data = { ...validData, pid: process.pid };
+ const data = { ...validData, pid: childPid };
const findProcess = async () => [
{
- pid: process.pid,
- ppid: process.ppid,
+ pid: childPid,
+ ppid: process.pid,
name: 'node',
cmd: 'node /app/server.mjs',
},
@@ -249,7 +278,7 @@ describe('isLockFileProcessAlive', () => {
});
it('keeps the PID-only result when the command cannot be inspected', async () => {
- const data = { ...validData, pid: process.pid };
+ const data = { ...validData, pid: childPid };
assert.equal(await isLockFileProcessAlive(data, async () => []), true);
assert.equal(
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b0784cfed33e..46f138303994 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7169,6 +7169,12 @@ importers:
specifier: ^4.22.0
version: 4.22.3
+ triage/gh-17744:
+ dependencies:
+ astro:
+ specifier: ^7.2.4
+ version: link:../../packages/astro
+
packages:
'@anthropic-ai/sdk@0.91.1':
From aacf76fda4f600983de16a037b9d0808e49f3266 Mon Sep 17 00:00:00 2001
From: Matthew Phillips
Date: Thu, 20 Aug 2026 08:44:20 -0400
Subject: [PATCH 12/21] Make triage bot changesets non-optional (#17475)
* Require changesets in triage fix and PR-writer skills
* Update .agents/skills/astro-pr-writer/SKILL.md
* Update .agents/skills/astro-pr-writer/SKILL.md
* Update .agents/skills/triage/fix.md
---
.agents/skills/astro-pr-writer/SKILL.md | 4 ++--
.agents/skills/triage/fix.md | 23 ++++++++++++++++++-----
2 files changed, 20 insertions(+), 7 deletions(-)
diff --git a/.agents/skills/astro-pr-writer/SKILL.md b/.agents/skills/astro-pr-writer/SKILL.md
index a5a10933d402..7fb5076a09e9 100644
--- a/.agents/skills/astro-pr-writer/SKILL.md
+++ b/.agents/skills/astro-pr-writer/SKILL.md
@@ -120,7 +120,7 @@ Load the `changeset` skill to create the changeset file and write the message. I
When writing the PR body:
-- Always check that a changeset exists before posting the PR
+- Before posting, check whether a changeset exists. If the PR modifies a package and none exists, **create it now** using the `changeset` skill — do not post the PR without one.
- Do not mention "added changeset" in the `Changes` section — it is process noise, not a behavior change
## Self-Check Before Posting
@@ -129,4 +129,4 @@ When writing the PR body:
- `Changes` bullets describe behavior/implementation/impact
- `Testing` lists test code added/changed, not test run results
- `Docs` decision is explicit
-- Changeset file exists in `.changeset/` for any package-modifying PR
+- Changeset file exists in `.changeset/` for any package-modifying PR — if missing, create it before posting
diff --git a/.agents/skills/triage/fix.md b/.agents/skills/triage/fix.md
index 0784a20991af..010978e06c85 100644
--- a/.agents/skills/triage/fix.md
+++ b/.agents/skills/triage/fix.md
@@ -25,7 +25,7 @@ These variables are referenced throughout this skill. They may be passed as args
6. Write a unit test
7. Ensure no regressions
8. Generate git diff
-9. Create a changeset
+9. Create a changeset (required for any fix that modifies a package)
10. Append fix details to `report.md`
11. Clean up the working directory
@@ -141,9 +141,22 @@ This captures all your changes for the report.
## Step 9: Create a Changeset
-**Only do this if the fix was successful** (i.e., you are on the high-confidence path and the fix resolves the issue). If the fix failed or was skipped, skip this step entirely.
+A changeset is **required** for every successful fix that modifies a package under `packages/`. This is not optional and does not scale with the size of the fix: one-line fixes, type-only fixes, and comment-only behavior changes all need a changeset. The only fixes that skip this step are ones that failed, were skipped, or touch nothing under `packages/` (e.g. `examples/*`-only changes).
-Load the `changeset` skill and follow its instructions to create a changeset for the fix. Since this is a bug fix, the bump type will almost always be `patch`.
+Create the changeset now:
+
+1. Run `pnpm changeset --empty` from the repo root. This writes a randomly-named `.md` file to `.changeset/`.
+2. Edit that file to add the package bump and a user-facing message. The bump type for a bug fix is almost always `patch`:
+
+ ```md
+ ---
+ '': patch
+ ---
+
+
+ ```
+
+Load the `changeset` skill for the message conventions (present-tense verb, name the affected API, write for Astro users not reviewers) and the exact package name to use.
## Step 10: Write Output
@@ -158,7 +171,7 @@ The report must include all information needed for a final GitHub comment to be
- Whether the fix was successful or not
- Verification results (did the fix resolve the original error?)
- Unit test details: what test was added, where it lives, and what it verifies. If no test was added, explain why.
-- Changeset details: what changeset file was created and which packages it covers. If no changeset was created, explain why.
+- Changeset details: the name of the changeset file created in `.changeset/` and which packages it covers. A successful fix that modified a package must have a changeset — there is no "skipped because trivial" outcome. The only valid reason to have no changeset is that the fix failed/was skipped or touched nothing under `packages/`; state which.
- Any alternative approaches considered and their tradeoffs
- If the fix failed: what was tried and why it didn't work
@@ -170,7 +183,7 @@ The report must include all information needed for a final GitHub comment to be
- Changes outside `packages/` that were only needed for diagnosis/reproduction
- Build artifacts that shouldn't be committed
3. Use `git checkout -- ` to discard unwanted changes
-4. Confirm with a final `git status` that only the intended fix files remain
+4. Confirm with a final `git status` that only the intended fix files remain, and that they include a new `.changeset/*.md` file. If the fix modified a package but no changeset is present, go back and create it before finishing.
5. DO NOT commit or push anything yet! The user will handle that at a later step.
The `triage/` directory is already gitignored, so it won't appear in `git status`.
From f88c875c2c89af3b8cbb9f9eebdcddde6e0645c8 Mon Sep 17 00:00:00 2001
From: "astro-factory[bot]"
<316791938+astro-factory[bot]@users.noreply.github.com>
Date: Thu, 20 Aug 2026 08:53:42 -0400
Subject: [PATCH 13/21] fix(create-astro): approve esbuild install scripts for
npm v11+ compatibility (#17756)
npm v11+ warns about packages with unapproved install scripts, and
npm v12 will make this a hard failure. Astro depends on esbuild which
has a postinstall script that downloads platform-specific binaries.
Add `allowScripts` for esbuild to all example package.json files and
add `ensureNpmScriptsAllowed()` to create-astro to pre-approve esbuild
in package.json before running `npm install`.
Fixes #17745
Co-authored-by: factory[bot]
Co-authored-by: Matthew Phillips
---
.changeset/short-tables-thank.md | 5 ++++
examples/advanced-routing/package.json | 3 +++
examples/basics/package.json | 3 +++
examples/blog/package.json | 3 +++
examples/component/package.json | 3 +++
examples/container-with-vitest/package.json | 3 +++
examples/framework-alpine/package.json | 3 +++
examples/framework-multiple/package.json | 3 +++
examples/framework-preact/package.json | 3 +++
examples/framework-react/package.json | 3 +++
examples/framework-solid/package.json | 3 +++
examples/framework-svelte/package.json | 3 +++
examples/framework-vue/package.json | 3 +++
examples/hackernews/package.json | 3 +++
examples/integration/package.json | 3 +++
examples/minimal/package.json | 3 +++
examples/portfolio/package.json | 3 +++
examples/ssr/package.json | 3 +++
examples/starlog/package.json | 3 +++
examples/toolbar-app/package.json | 3 +++
examples/with-markdoc/package.json | 3 +++
examples/with-mdx/package.json | 3 +++
examples/with-nanostores/package.json | 3 +++
examples/with-tailwindcss/package.json | 3 +++
examples/with-vitest/package.json | 3 +++
.../create-astro/src/actions/dependencies.ts | 25 +++++++++++++++++++
26 files changed, 102 insertions(+)
create mode 100644 .changeset/short-tables-thank.md
diff --git a/.changeset/short-tables-thank.md b/.changeset/short-tables-thank.md
new file mode 100644
index 000000000000..85488172c250
--- /dev/null
+++ b/.changeset/short-tables-thank.md
@@ -0,0 +1,5 @@
+---
+'create-astro': patch
+---
+
+Fixes `npm install` warnings on npm v11+ about esbuild's install scripts not being covered by `allowScripts`. Adds `ensureNpmScriptsAllowed()` to pre-approve esbuild in `package.json` before running `npm install`, matching the existing pnpm v11 compatibility fix.
diff --git a/examples/advanced-routing/package.json b/examples/advanced-routing/package.json
index eedf3c865a21..1ddddb8b9ac7 100644
--- a/examples/advanced-routing/package.json
+++ b/examples/advanced-routing/package.json
@@ -16,5 +16,8 @@
"@astrojs/node": "^11.1.4",
"astro": "^7.2.4",
"hono": "^4.12.14"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/basics/package.json b/examples/basics/package.json
index deaf8ab28b7a..c48360da0a18 100644
--- a/examples/basics/package.json
+++ b/examples/basics/package.json
@@ -14,5 +14,8 @@
},
"dependencies": {
"astro": "^7.2.4"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/blog/package.json b/examples/blog/package.json
index c9db9ef3a99b..2f91beddffc0 100644
--- a/examples/blog/package.json
+++ b/examples/blog/package.json
@@ -18,5 +18,8 @@
"@astrojs/sitemap": "^3.7.3",
"astro": "^7.2.4",
"sharp": "^0.35.0"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/component/package.json b/examples/component/package.json
index 68741a682a52..a66e7451df75 100644
--- a/examples/component/package.json
+++ b/examples/component/package.json
@@ -22,5 +22,8 @@
},
"peerDependencies": {
"astro": "^5.0.0 || ^6.0.0"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/container-with-vitest/package.json b/examples/container-with-vitest/package.json
index 17680fc49da6..6ae7c7b2016a 100644
--- a/examples/container-with-vitest/package.json
+++ b/examples/container-with-vitest/package.json
@@ -23,5 +23,8 @@
"devDependencies": {
"@types/react": "^18.3.28",
"@types/react-dom": "^18.3.7"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/framework-alpine/package.json b/examples/framework-alpine/package.json
index 46f29528c737..1881b4140f66 100644
--- a/examples/framework-alpine/package.json
+++ b/examples/framework-alpine/package.json
@@ -17,5 +17,8 @@
"@types/alpinejs": "^3.13.11",
"alpinejs": "^3.15.8",
"astro": "^7.2.4"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/framework-multiple/package.json b/examples/framework-multiple/package.json
index f94b30c0a56a..4e47f06ca8b8 100644
--- a/examples/framework-multiple/package.json
+++ b/examples/framework-multiple/package.json
@@ -27,5 +27,8 @@
"solid-js": "^1.9.11",
"svelte": "^5.53.5",
"vue": "^3.5.29"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/framework-preact/package.json b/examples/framework-preact/package.json
index 25014ced0441..e7abe20da8de 100644
--- a/examples/framework-preact/package.json
+++ b/examples/framework-preact/package.json
@@ -17,5 +17,8 @@
"@preact/signals": "^2.8.1",
"astro": "^7.2.4",
"preact": "^10.28.4"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/framework-react/package.json b/examples/framework-react/package.json
index 1c2024d0c534..49cc0ca31c19 100644
--- a/examples/framework-react/package.json
+++ b/examples/framework-react/package.json
@@ -19,5 +19,8 @@
"astro": "^7.2.4",
"react": "^18.3.1",
"react-dom": "^18.3.1"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/framework-solid/package.json b/examples/framework-solid/package.json
index 8826ad707c7b..554f799d8807 100644
--- a/examples/framework-solid/package.json
+++ b/examples/framework-solid/package.json
@@ -16,5 +16,8 @@
"@astrojs/solid-js": "^7.0.2",
"astro": "^7.2.4",
"solid-js": "^1.9.11"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/framework-svelte/package.json b/examples/framework-svelte/package.json
index 86dac97c83f2..2c55b78d8eba 100644
--- a/examples/framework-svelte/package.json
+++ b/examples/framework-svelte/package.json
@@ -16,5 +16,8 @@
"@astrojs/svelte": "^9.0.1",
"astro": "^7.2.4",
"svelte": "^5.53.5"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/framework-vue/package.json b/examples/framework-vue/package.json
index 24fc3c122055..202202ee80de 100644
--- a/examples/framework-vue/package.json
+++ b/examples/framework-vue/package.json
@@ -16,5 +16,8 @@
"@astrojs/vue": "^7.0.2",
"astro": "^7.2.4",
"vue": "^3.5.29"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json
index b71a520e7a67..15b61c81d144 100644
--- a/examples/hackernews/package.json
+++ b/examples/hackernews/package.json
@@ -15,5 +15,8 @@
"dependencies": {
"@astrojs/node": "^11.1.4",
"astro": "^7.2.4"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/integration/package.json b/examples/integration/package.json
index a19b0a75a9dc..d87db02ef3a1 100644
--- a/examples/integration/package.json
+++ b/examples/integration/package.json
@@ -22,5 +22,8 @@
},
"peerDependencies": {
"astro": "^4.0.0"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/minimal/package.json b/examples/minimal/package.json
index 6edf5f8309ab..8ea8d9837e33 100644
--- a/examples/minimal/package.json
+++ b/examples/minimal/package.json
@@ -14,5 +14,8 @@
},
"dependencies": {
"astro": "^7.2.4"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/portfolio/package.json b/examples/portfolio/package.json
index dc45d5e2fed4..bac2f217ae56 100644
--- a/examples/portfolio/package.json
+++ b/examples/portfolio/package.json
@@ -14,5 +14,8 @@
},
"dependencies": {
"astro": "^7.2.4"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/ssr/package.json b/examples/ssr/package.json
index a5236ab64c87..f498797979d8 100644
--- a/examples/ssr/package.json
+++ b/examples/ssr/package.json
@@ -18,5 +18,8 @@
"@astrojs/svelte": "^9.0.1",
"astro": "^7.2.4",
"svelte": "^5.53.5"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/starlog/package.json b/examples/starlog/package.json
index 3b465f28f07a..12d44981e785 100644
--- a/examples/starlog/package.json
+++ b/examples/starlog/package.json
@@ -15,5 +15,8 @@
},
"engines": {
"node": ">=22.12.0"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/toolbar-app/package.json b/examples/toolbar-app/package.json
index 973a93eb5d8e..b561a8f063f7 100644
--- a/examples/toolbar-app/package.json
+++ b/examples/toolbar-app/package.json
@@ -20,5 +20,8 @@
},
"engines": {
"node": ">=22.12.0"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/with-markdoc/package.json b/examples/with-markdoc/package.json
index f46d24e068ca..7044bfd0b898 100644
--- a/examples/with-markdoc/package.json
+++ b/examples/with-markdoc/package.json
@@ -15,5 +15,8 @@
"dependencies": {
"@astrojs/markdoc": "^2.0.8",
"astro": "^7.2.4"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/with-mdx/package.json b/examples/with-mdx/package.json
index e8a4b9953690..db64f8e9962a 100644
--- a/examples/with-mdx/package.json
+++ b/examples/with-mdx/package.json
@@ -17,5 +17,8 @@
"@astrojs/preact": "^6.0.4",
"astro": "^7.2.4",
"preact": "^10.28.4"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/with-nanostores/package.json b/examples/with-nanostores/package.json
index a7b840f2a2de..e5471a8a3010 100644
--- a/examples/with-nanostores/package.json
+++ b/examples/with-nanostores/package.json
@@ -18,5 +18,8 @@
"astro": "^7.2.4",
"nanostores": "^1.1.1",
"preact": "^10.28.4"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/with-tailwindcss/package.json b/examples/with-tailwindcss/package.json
index b0dcc0e8d279..359b244b373a 100644
--- a/examples/with-tailwindcss/package.json
+++ b/examples/with-tailwindcss/package.json
@@ -20,5 +20,8 @@
"canvas-confetti": "^1.9.4",
"tailwindcss": "^4.2.1",
"vite": "^8.0.13"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/examples/with-vitest/package.json b/examples/with-vitest/package.json
index a03ed9530be8..f7bbd3021346 100644
--- a/examples/with-vitest/package.json
+++ b/examples/with-vitest/package.json
@@ -16,5 +16,8 @@
"dependencies": {
"astro": "^7.2.4",
"vitest": "^5.0.0-beta.2"
+ },
+ "allowScripts": {
+ "esbuild": true
}
}
diff --git a/packages/create-astro/src/actions/dependencies.ts b/packages/create-astro/src/actions/dependencies.ts
index 337c713a0341..c26d443f465b 100644
--- a/packages/create-astro/src/actions/dependencies.ts
+++ b/packages/create-astro/src/actions/dependencies.ts
@@ -123,6 +123,7 @@ async function astroAdd({
async function install({ packageManager, cwd }: { packageManager: string; cwd: string }) {
if (packageManager === 'yarn') await ensureYarnLock({ cwd });
if (packageManager === 'pnpm') await ensurePnpmBuildsAllowed({ cwd });
+ if (packageManager === 'npm') await ensureNpmScriptsAllowed({ cwd });
return shell(packageManager, ['install'], { cwd, timeout: 90_000, stdio: 'ignore' });
}
@@ -152,6 +153,30 @@ async function ensurePnpmBuildsAllowed({ cwd }: { cwd: string }) {
return fs.promises.writeFile(workspaceFile, content + allowBuildsBlock, 'utf-8');
}
+/**
+ * npm v11+ warns about packages with unapproved install scripts, and npm v12 will
+ * make this a hard failure. Astro depends on `esbuild` which has a postinstall script
+ * that downloads platform-specific binaries.
+ *
+ * This function ensures esbuild is pre-approved in `package.json` via the `allowScripts`
+ * field so that `npm install` succeeds without warnings or failures.
+ * See https://docs.npmjs.com/cli/v11/using-npm/config#allow-scripts
+ */
+async function ensureNpmScriptsAllowed({ cwd }: { cwd: string }) {
+ const pkgFile = path.join(cwd, 'package.json');
+ if (!fs.existsSync(pkgFile)) return;
+
+ const content = await fs.promises.readFile(pkgFile, 'utf-8');
+ const packageJson = JSON.parse(content);
+
+ // If allowScripts is already configured, don't touch it
+ if (packageJson.allowScripts) return;
+
+ const indent = /(^\s+)/m.exec(content)?.[1] ?? '\t';
+ packageJson.allowScripts = { esbuild: true };
+ return fs.promises.writeFile(pkgFile, JSON.stringify(packageJson, null, indent) + '\n', 'utf-8');
+}
+
/**
* Yarn Berry (PnP) versions will throw an error if there isn't an existing `yarn.lock` file
* If a `yarn.lock` file doesn't exist, this function writes an empty `yarn.lock` one.
From d035290a14afac8834885b727327a7f44d3a3a48 Mon Sep 17 00:00:00 2001
From: Waqas Ahmed
Date: Thu, 20 Aug 2026 18:07:21 +0500
Subject: [PATCH 14/21] fix: rebuild module imports after content entry
deletion (#17713)
* fix: rebuild module imports after content entry deletion (#17707)
* test: exercise debounced module-import trigger on delete/clear/clearAll (#17707)
Prior tests called writeModuleImports() explicitly right after delete/clear,
which forced the rebuild to run synchronously regardless of whether the new
#writeModulesImportsDebounced() calls inside delete()/clear()/clearAll() were
wired correctly. Reverting those three trigger lines still passed all
existing assertions.
Rewrite the delete/clear tests to rely on waitUntilSaveComplete() alone, add
a clearAll()-specific test, and add a rename test (delete(oldId) + set(newId))
matching the issue's actual reported scenario.
Verified: reverting the three trigger lines makes the delete/clear/clearAll
tests fail; restoring them makes all pass.
* fix: return early after writing an empty content-modules.mjs
The zero-size branch in writeModuleImports() fell through to the generator
below it, writing the file twice on every call where #moduleImports ends up
empty (e.g. after deleting the last deferred-render entry) -- first
'export default new Map();', then a second, differently-formatted empty-map
write. That path only became reachable once #moduleImports could shrink back
to zero, which this PR introduces. Add the missing return, matching the
pattern review comment.
Also note in the changeset that addModuleImport() callers without a backing
deferredRender entry no longer survive a write, now that #moduleImports is
fully derived state.
---
.../fix-content-modules-stale-import.md | 7 +
.../astro/src/content/mutable-data-store.ts | 26 ++++
.../mutable-data-store.test.ts | 142 ++++++++++++++++++
3 files changed, 175 insertions(+)
create mode 100644 .changeset/fix-content-modules-stale-import.md
diff --git a/.changeset/fix-content-modules-stale-import.md b/.changeset/fix-content-modules-stale-import.md
new file mode 100644
index 000000000000..52b5d4116e2f
--- /dev/null
+++ b/.changeset/fix-content-modules-stale-import.md
@@ -0,0 +1,7 @@
+---
+'astro': patch
+---
+
+Fixes `content-modules.mjs` not removing entries for deleted or renamed content files, which could cause Vite to attempt to resolve non-existent modules
+
+As part of this fix, `#moduleImports` is now fully rebuilt from `deferredRender` entries before every write, so a module import added only through the public `addModuleImport()` API without a corresponding `deferredRender` entry in the store will no longer be preserved across writes.
diff --git a/packages/astro/src/content/mutable-data-store.ts b/packages/astro/src/content/mutable-data-store.ts
index ee28d277768c..ef6c0878ec5d 100644
--- a/packages/astro/src/content/mutable-data-store.ts
+++ b/packages/astro/src/content/mutable-data-store.ts
@@ -59,6 +59,7 @@ export class MutableDataStore extends ImmutableDataStore {
collection.delete(String(key));
this.#saveToDiskDebounced();
this.#writeAssetsImportsDebounced();
+ this.#writeModulesImportsDebounced();
}
}
@@ -66,12 +67,14 @@ export class MutableDataStore extends ImmutableDataStore {
this._collections.delete(collectionName);
this.#saveToDiskDebounced();
this.#writeAssetsImportsDebounced();
+ this.#writeModulesImportsDebounced();
}
clearAll() {
this._collections.clear();
this.#saveToDiskDebounced();
this.#writeAssetsImportsDebounced();
+ this.#writeModulesImportsDebounced();
}
addAssetImport(assetImport: string, filePath?: string) {
@@ -123,6 +126,27 @@ export class MutableDataStore extends ImmutableDataStore {
}
}
+ /**
+ * Rebuilds #moduleImports from the current entries in _collections.
+ * This ensures stale module entries are removed when content files are
+ * deleted or renamed, preventing Vite from attempting to resolve
+ * non-existent files listed in content-modules.mjs.
+ */
+ #rebuildModuleImports() {
+ this.#moduleImports.clear();
+ for (const collection of this._collections.values()) {
+ for (const entry of collection.values()) {
+ const typedEntry = entry as DataEntry;
+ if (typedEntry.deferredRender && typedEntry.filePath) {
+ const id = contentModuleToId(typedEntry.filePath);
+ if (id) {
+ this.#moduleImports.set(typedEntry.filePath, id);
+ }
+ }
+ }
+ }
+ }
+
async writeAssetImports(filePath: PathLike) {
this.#assetsFile = filePath;
this.#rebuildAssetImports();
@@ -164,6 +188,7 @@ export default new Map([${exports.join(', ')}]);
async writeModuleImports(filePath: PathLike) {
this.#modulesFile = filePath;
+ this.#rebuildModuleImports();
if (this.#moduleImports.size === 0) {
try {
@@ -171,6 +196,7 @@ export default new Map([${exports.join(', ')}]);
} catch (err) {
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
}
+ return;
}
if (!this.#modulesDirty && existsSync(filePath)) {
diff --git a/packages/astro/test/units/content-collections/mutable-data-store.test.ts b/packages/astro/test/units/content-collections/mutable-data-store.test.ts
index eef3efe004ca..821c270369af 100644
--- a/packages/astro/test/units/content-collections/mutable-data-store.test.ts
+++ b/packages/astro/test/units/content-collections/mutable-data-store.test.ts
@@ -128,6 +128,148 @@ describe('MutableDataStore', () => {
);
});
+ it('removes stale module imports when an entry is deleted (via debounced write trigger)', async () => {
+ const modulesFilePath = path.join(tmpDir, 'content-modules-delete.mjs');
+ const store = new MutableDataStore();
+ const scoped = store.scopedStore('docs');
+
+ scoped.set({
+ id: 'page-a',
+ data: {},
+ filePath: 'src/content/docs/page-a.mdx',
+ deferredRender: true,
+ });
+
+ scoped.set({
+ id: 'page-b',
+ data: {},
+ filePath: 'src/content/docs/page-b.mdx',
+ deferredRender: true,
+ });
+
+ await store.writeModuleImports(modulesFilePath);
+ const contentBefore = await fs.readFile(modulesFilePath, 'utf-8');
+ assert.ok(contentBefore.includes('page-a.mdx'), 'should contain page-a before deletion');
+ assert.ok(contentBefore.includes('page-b.mdx'), 'should contain page-b before deletion');
+
+ // Do NOT call writeModuleImports() again here: that would rebuild and write
+ // synchronously regardless of whether delete() actually schedules a rewrite.
+ // Rely solely on the debounced trigger that delete() is supposed to schedule,
+ // flushed via waitUntilSaveComplete(), so this test exercises the real
+ // dev-server code path (a filesystem delete triggers a rewrite on its own).
+ scoped.delete('page-a');
+ await store.waitUntilSaveComplete();
+
+ const contentAfter = await fs.readFile(modulesFilePath, 'utf-8');
+ assert.ok(
+ !contentAfter.includes('page-a.mdx'),
+ 'should NOT contain page-a after the entry is deleted',
+ );
+ assert.ok(contentAfter.includes('page-b.mdx'), 'should still contain page-b');
+ });
+
+ it('removes stale module imports when a collection is cleared (via debounced write trigger)', async () => {
+ const modulesFilePath = path.join(tmpDir, 'content-modules-clear.mjs');
+ const store = new MutableDataStore();
+ const scoped = store.scopedStore('docs');
+
+ scoped.set({
+ id: 'page-1',
+ data: {},
+ filePath: 'src/content/docs/page-1.mdx',
+ deferredRender: true,
+ });
+
+ await store.writeModuleImports(modulesFilePath);
+ const contentBefore = await fs.readFile(modulesFilePath, 'utf-8');
+ assert.ok(contentBefore.includes('page-1.mdx'), 'should contain page-1 before clear');
+
+ scoped.clear();
+ await store.waitUntilSaveComplete();
+
+ const contentAfter = await fs.readFile(modulesFilePath, 'utf-8');
+ assert.ok(
+ !contentAfter.includes('page-1.mdx'),
+ 'should NOT contain page-1 after the collection is cleared',
+ );
+ });
+
+ it('removes stale module imports when the entire store is cleared via clearAll (via debounced write trigger)', async () => {
+ const modulesFilePath = path.join(tmpDir, 'content-modules-clear-all.mjs');
+ const store = new MutableDataStore();
+ const docsScoped = store.scopedStore('docs');
+ const blogScoped = store.scopedStore('blog');
+
+ docsScoped.set({
+ id: 'page-1',
+ data: {},
+ filePath: 'src/content/docs/page-1.mdx',
+ deferredRender: true,
+ });
+ blogScoped.set({
+ id: 'post-1',
+ data: {},
+ filePath: 'src/content/blog/post-1.mdx',
+ deferredRender: true,
+ });
+
+ await store.writeModuleImports(modulesFilePath);
+ const contentBefore = await fs.readFile(modulesFilePath, 'utf-8');
+ assert.ok(contentBefore.includes('page-1.mdx'), 'should contain page-1 before clearAll');
+ assert.ok(contentBefore.includes('post-1.mdx'), 'should contain post-1 before clearAll');
+
+ store.clearAll();
+ await store.waitUntilSaveComplete();
+
+ const contentAfter = await fs.readFile(modulesFilePath, 'utf-8');
+ assert.ok(
+ !contentAfter.includes('page-1.mdx'),
+ 'should NOT contain page-1 after clearAll',
+ );
+ assert.ok(
+ !contentAfter.includes('post-1.mdx'),
+ 'should NOT contain post-1 after clearAll',
+ );
+ });
+
+ it('removes the old module import and adds the new one when an entry is renamed (issue #17707)', async () => {
+ // A rename is how glob.ts actually models it: delete the old id, then set the new one.
+ const modulesFilePath = path.join(tmpDir, 'content-modules-rename.mjs');
+ const store = new MutableDataStore();
+ const scoped = store.scopedStore('docs');
+
+ scoped.set({
+ id: 'otp',
+ data: {},
+ filePath: 'src/content/docs/otp.mdx',
+ deferredRender: true,
+ });
+
+ await store.writeModuleImports(modulesFilePath);
+ const contentBefore = await fs.readFile(modulesFilePath, 'utf-8');
+ assert.ok(contentBefore.includes('otp.mdx'), 'should contain the original file before rename');
+
+ // Rename: delete the old entry, then set the new one under a new id/filePath.
+ scoped.delete('otp');
+ scoped.set({
+ id: 'the-otp',
+ data: {},
+ filePath: 'src/content/docs/the-otp.mdx',
+ deferredRender: true,
+ });
+ await store.waitUntilSaveComplete();
+
+ const contentAfter = await fs.readFile(modulesFilePath, 'utf-8');
+ assert.ok(
+ !contentAfter.includes('"src/content/docs/otp.mdx"'),
+ 'should NOT reference the old (renamed-away) file path',
+ );
+ assert.ok(
+ contentAfter.includes('the-otp.mdx'),
+ 'should reference the new (renamed-to) file path',
+ );
+ });
+
it('reproduces race condition: concurrent writeToDisk() calls lose data', async () => {
const filePath = pathToFileURL(path.join(tmpDir, 'data-store.json'));
const store = await MutableDataStore.fromFile(filePath);
From 1b41d099ca1920dd1b3302c1fabbd0c10fd7fdcd Mon Sep 17 00:00:00 2001
From: Waqas Ahmed
Date: Thu, 20 Aug 2026 13:08:38 +0000
Subject: [PATCH 15/21] [ci] format
---
.../content-collections/mutable-data-store.test.ts | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/packages/astro/test/units/content-collections/mutable-data-store.test.ts b/packages/astro/test/units/content-collections/mutable-data-store.test.ts
index 821c270369af..f48b7f150f83 100644
--- a/packages/astro/test/units/content-collections/mutable-data-store.test.ts
+++ b/packages/astro/test/units/content-collections/mutable-data-store.test.ts
@@ -222,14 +222,8 @@ describe('MutableDataStore', () => {
await store.waitUntilSaveComplete();
const contentAfter = await fs.readFile(modulesFilePath, 'utf-8');
- assert.ok(
- !contentAfter.includes('page-1.mdx'),
- 'should NOT contain page-1 after clearAll',
- );
- assert.ok(
- !contentAfter.includes('post-1.mdx'),
- 'should NOT contain post-1 after clearAll',
- );
+ assert.ok(!contentAfter.includes('page-1.mdx'), 'should NOT contain page-1 after clearAll');
+ assert.ok(!contentAfter.includes('post-1.mdx'), 'should NOT contain post-1 after clearAll');
});
it('removes the old module import and adds the new one when an entry is renamed (issue #17707)', async () => {
From dd0e3aca0b898307431120a5bc1541681190178d Mon Sep 17 00:00:00 2001
From: Kosta <47947996+dobrodob@users.noreply.github.com>
Date: Thu, 20 Aug 2026 15:28:16 +0200
Subject: [PATCH 16/21] fix(transitions): do not re-create media inside
persisted subtrees (#17750)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* fix(transitions): do not re-create media inside persisted subtrees
reifyMediaElements() (#17603) runs after transition:persist elements from the old document have been moved into the new body, so it also replaced the live