diff --git a/.changeset/fix-action-prototype-traversal.md b/.changeset/fix-action-prototype-traversal.md new file mode 100644 index 000000000000..e5e11f035ae6 --- /dev/null +++ b/.changeset/fix-action-prototype-traversal.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes action route handling to return 404 for requests to prototype method names like `constructor` or `toString` used as action paths diff --git a/.changeset/fix-server-island-dev-build-output.md b/.changeset/fix-server-island-dev-build-output.md new file mode 100644 index 000000000000..fff0dbce6f05 --- /dev/null +++ b/.changeset/fix-server-island-dev-build-output.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes server islands returning a 500 error in dev mode for adapters that do not set `adapterFeatures.buildOutput` (e.g. `@astrojs/netlify`) diff --git a/.changeset/kind-hairs-report.md b/.changeset/kind-hairs-report.md new file mode 100644 index 000000000000..64565bd50923 --- /dev/null +++ b/.changeset/kind-hairs-report.md @@ -0,0 +1,5 @@ +--- +'astro': major +--- + +Changes TypeScript configuration - ([v6 upgrade guidance](https://v6.docs.astro.build/en/guides/upgrade-to/v6/#changed-typescript-configuration)) diff --git a/.changeset/polite-balloons-rhyme.md b/.changeset/polite-balloons-rhyme.md new file mode 100644 index 000000000000..319f9a7467ba --- /dev/null +++ b/.changeset/polite-balloons-rhyme.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Ensures that URLs with multiple leading slashes (e.g. `//admin`) are normalized to a single slash before reaching middleware, so that pathname checks like `context.url.pathname.startsWith('/admin')` work consistently regardless of the request URL format diff --git a/.changeset/smart-pillows-chew.md b/.changeset/smart-pillows-chew.md new file mode 100644 index 000000000000..544d6b579c76 --- /dev/null +++ b/.changeset/smart-pillows-chew.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a case where internal headers may leak when rendering error pages diff --git a/.github/scripts/bundle-size.mjs b/.github/scripts/bundle-size.mjs index ff36f3cbd778..c78f20fec6c4 100644 --- a/.github/scripts/bundle-size.mjs +++ b/.github/scripts/bundle-size.mjs @@ -75,7 +75,7 @@ async function bundle(files) { bundle: true, minify: true, sourcemap: false, - target: ['es2018'], + target: ['esnext'], outdir: 'out', external: ['astro:*', 'aria-query', 'axobject-query'], metafile: true, diff --git a/benchmark/packages/adapter/tsconfig.json b/benchmark/packages/adapter/tsconfig.json index 1504b4b6dfa4..7f8d5c4c2184 100644 --- a/benchmark/packages/adapter/tsconfig.json +++ b/benchmark/packages/adapter/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/benchmark/packages/timer/tsconfig.json b/benchmark/packages/timer/tsconfig.json index 1504b4b6dfa4..7f8d5c4c2184 100644 --- a/benchmark/packages/timer/tsconfig.json +++ b/benchmark/packages/timer/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/examples/portfolio/src/components/Nav.astro b/examples/portfolio/src/components/Nav.astro index d8fbd075c30f..88652f5763cd 100644 --- a/examples/portfolio/src/components/Nav.astro +++ b/examples/portfolio/src/components/Nav.astro @@ -124,7 +124,7 @@ const isCurrentPage = (href: string) => { }; // Toggle menu visibility when the menu button is clicked. - btn.addEventListener('click', () => setExpanded(menu.hidden)); + btn.addEventListener('click', () => setExpanded(menu.hidden === true)); // Hide menu button for large screens. const handleViewports = (e: MediaQueryList | MediaQueryListEvent) => { diff --git a/examples/toolbar-app/tsconfig.json b/examples/toolbar-app/tsconfig.json index 281fb7f925fa..0a393886f195 100644 --- a/examples/toolbar-app/tsconfig.json +++ b/examples/toolbar-app/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "astro/tsconfigs/strict", "compilerOptions": { - "outDir": "dist", - "rootDir": "src" + "outDir": "./dist", + "rootDir": "./src" } } diff --git a/packages/astro-prism/tsconfig.json b/packages/astro-prism/tsconfig.json index 18443cddf207..17e08031358c 100644 --- a/packages/astro-prism/tsconfig.json +++ b/packages/astro-prism/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/astro-rss/tsconfig.json b/packages/astro-rss/tsconfig.json index 18443cddf207..17e08031358c 100644 --- a/packages/astro-rss/tsconfig.json +++ b/packages/astro-rss/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/astro/e2e/fixtures/cloudflare/package.json b/packages/astro/e2e/fixtures/cloudflare/package.json index 0a66a112a96e..ea4775fc565a 100644 --- a/packages/astro/e2e/fixtures/cloudflare/package.json +++ b/packages/astro/e2e/fixtures/cloudflare/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@astrojs/cloudflare": "workspace:*", - "@astrojs/mdx": "^4.3.13", + "@astrojs/mdx": "workspace:*", "@astrojs/preact": "workspace:*", "@astrojs/react": "workspace:*", "@astrojs/vue": "workspace:*", diff --git a/packages/astro/src/core/app/base.ts b/packages/astro/src/core/app/base.ts index bb263aa19407..6ec73e37363b 100644 --- a/packages/astro/src/core/app/base.ts +++ b/packages/astro/src/core/app/base.ts @@ -1,5 +1,6 @@ import { appendForwardSlash, + collapseDuplicateLeadingSlashes, collapseDuplicateTrailingSlashes, hasFileExtension, isInternalPath, @@ -15,10 +16,12 @@ import type { Pipeline } from '../base-pipeline.js'; import { clientAddressSymbol, DEFAULT_404_COMPONENT, + NOOP_MIDDLEWARE_HEADER, REROUTABLE_STATUS_CODES, REROUTE_DIRECTIVE_HEADER, responseSentSymbol, REWRITE_DIRECTIVE_HEADER_KEY, + ROUTE_TYPE_HEADER, } from '../constants.js'; import { getSetCookiesFromResponse } from '../cookies/index.js'; import { AstroError, AstroErrorData } from '../errors/index.js'; @@ -84,9 +87,17 @@ export interface RenderOptions { routeData?: RouteData; } -export interface RenderErrorOptions { - locals?: App.Locals; - routeData?: RouteData; +type RequiredRenderOptions = Required; + +interface ResolvedRenderOptions { + addCookieHeader: RequiredRenderOptions['addCookieHeader']; + clientAddress: RequiredRenderOptions['clientAddress'] | undefined; + prerenderedErrorPageFetch: RequiredRenderOptions['prerenderedErrorPageFetch'] | undefined; + locals: RequiredRenderOptions['locals'] | undefined; + routeData: RequiredRenderOptions['routeData'] | undefined; +} + +export interface RenderErrorOptions extends ResolvedRenderOptions { response?: Response; status: 404 | 500; /** @@ -97,8 +108,6 @@ export interface RenderErrorOptions { * Allows passing an error to 500.astro. It will be available through `Astro.props.error`. */ error?: unknown; - clientAddress: string | undefined; - prerenderedErrorPageFetch: ((url: ErrorPagePath) => Promise) | undefined; } type ErrorPagePath = @@ -187,6 +196,10 @@ export abstract class BaseApp

{ } public removeBase(pathname: string) { + // Collapse multiple leading slashes to prevent middleware authorization bypass. + // Without this, `//admin` would be treated as starting with base `/` and sliced + // to `/admin` for routing, while middleware still sees `//admin` in the URL. + pathname = collapseDuplicateLeadingSlashes(pathname); if (pathname.startsWith(this.manifest.base)) { return pathname.slice(this.baseWithoutTrailingSlash.length + 1); } @@ -355,19 +368,23 @@ export abstract class BaseApp

{ return pathname; } - public async render(request: Request, renderOptions?: RenderOptions): Promise { + public async render( + request: Request, + { + addCookieHeader = false, + clientAddress = Reflect.get(request, clientAddressSymbol), + locals, + prerenderedErrorPageFetch = fetch, + routeData, + }: RenderOptions = {}, + ): Promise { const timeStart = performance.now(); - let routeData: RouteData | undefined = renderOptions?.routeData; - let locals: object | undefined; - let clientAddress: string | undefined; - let addCookieHeader: boolean | undefined; const url = new URL(request.url); const redirect = this.redirectTrailingSlash(url.pathname); - const prerenderedErrorPageFetch = renderOptions?.prerenderedErrorPageFetch ?? fetch; if (redirect !== url.pathname) { const status = request.method === 'GET' ? 301 : 308; - return new Response( + const response = new Response( redirectTemplate({ status, relativeLocation: url.pathname, @@ -381,13 +398,10 @@ export abstract class BaseApp

{ }, }, ); + this.#prepareResponse(response, { addCookieHeader }); + return response; } - addCookieHeader = renderOptions?.addCookieHeader; - clientAddress = renderOptions?.clientAddress ?? Reflect.get(request, clientAddressSymbol); - routeData = renderOptions?.routeData; - locals = renderOptions?.locals; - if (routeData) { this.logger.debug( 'router', @@ -397,15 +411,26 @@ export abstract class BaseApp

{ this.logger.debug('router', 'RouteData'); this.logger.debug('router', routeData); } + + const resolvedRenderOptions: ResolvedRenderOptions = { + addCookieHeader, + clientAddress, + prerenderedErrorPageFetch, + locals, + routeData, + }; + if (locals) { if (typeof locals !== 'object') { const error = new AstroError(AstroErrorData.LocalsNotAnObject); this.logger.error(null, error.stack!); return this.renderError(request, { + ...resolvedRenderOptions, + // If locals are invalid, we don't want to include them when + // rendering the error page + locals: undefined, status: 500, error, - clientAddress, - prerenderedErrorPageFetch: prerenderedErrorPageFetch, }); } } @@ -434,10 +459,8 @@ export abstract class BaseApp

{ this.logger.debug('router', "Astro hasn't found routes that match " + request.url); this.logger.debug('router', "Here's the available routes:\n", this.manifestData); return this.renderError(request, { - locals, + ...resolvedRenderOptions, status: 404, - clientAddress, - prerenderedErrorPageFetch: prerenderedErrorPageFetch, }); } const pathname = this.getPathnameFromRequest(request); @@ -472,11 +495,9 @@ export abstract class BaseApp

{ } catch (err: any) { this.logger.error(null, err.stack || err.message || String(err)); return this.renderError(request, { - locals, + ...resolvedRenderOptions, status: 500, error: err, - clientAddress, - prerenderedErrorPageFetch: prerenderedErrorPageFetch, }); } finally { await session?.[PERSIST_SYMBOL](); @@ -490,30 +511,39 @@ export abstract class BaseApp

{ response.headers.get(REROUTE_DIRECTIVE_HEADER) !== 'no' ) { return this.renderError(request, { - locals, + ...resolvedRenderOptions, response, status: response.status as 404 | 500, // We don't have an error to report here. Passing null means we pass nothing intentionally // while undefined means there's no error error: response.status === 500 ? null : undefined, - clientAddress, - prerenderedErrorPageFetch: prerenderedErrorPageFetch, }); } + this.#prepareResponse(response, { addCookieHeader }); + return response; + } + + #prepareResponse(response: Response, { addCookieHeader }: { addCookieHeader: boolean }): void { // We remove internally-used header before we send the response to the user agent. - if (response.headers.has(REROUTE_DIRECTIVE_HEADER)) { - response.headers.delete(REROUTE_DIRECTIVE_HEADER); + for (const headerName of [ + REROUTE_DIRECTIVE_HEADER, + REWRITE_DIRECTIVE_HEADER_KEY, + NOOP_MIDDLEWARE_HEADER, + ROUTE_TYPE_HEADER, + ]) { + if (response.headers.has(headerName)) { + response.headers.delete(headerName); + } } if (addCookieHeader) { - for (const setCookieHeaderValue of BaseApp.getSetCookieFromResponse(response)) { + for (const setCookieHeaderValue of getSetCookiesFromResponse(response)) { response.headers.append('set-cookie', setCookieHeaderValue); } } Reflect.set(response, responseSentSymbol, true); - return response; } setCookieHeaders(response: Response) { @@ -540,13 +570,11 @@ export abstract class BaseApp

{ public async renderError( request: Request, { - locals, status, response: originalResponse, skipMiddleware = false, error, - clientAddress, - prerenderedErrorPageFetch, + ...resolvedRenderOptions }: RenderErrorOptions, ): Promise { const errorRoutePath = `/${status}${this.manifest.trailingSlash === 'always' ? '/' : ''}`; @@ -556,8 +584,13 @@ export abstract class BaseApp

{ if (errorRouteData.prerender) { const maybeDotHtml = errorRouteData.route.endsWith(`/${status}`) ? '.html' : ''; const statusURL = new URL(`${this.baseWithoutTrailingSlash}/${status}${maybeDotHtml}`, url); - if (statusURL.toString() !== request.url && prerenderedErrorPageFetch) { - const response = await prerenderedErrorPageFetch(statusURL.toString() as ErrorPagePath); + if ( + statusURL.toString() !== request.url && + resolvedRenderOptions.prerenderedErrorPageFetch + ) { + const response = await resolvedRenderOptions.prerenderedErrorPageFetch( + statusURL.toString() as ErrorPagePath, + ); // In order for the response of the remote to be usable as a response // for this request, it needs to have our status code in the response @@ -571,14 +604,16 @@ export abstract class BaseApp

{ // not match the body we provide and need to be removed. const override = { status, removeContentEncodingHeaders: true }; - return this.mergeResponses(response, originalResponse, override); + const newResponse = this.mergeResponses(response, originalResponse, override); + this.#prepareResponse(newResponse, resolvedRenderOptions); + return newResponse; } } const mod = await this.pipeline.getComponentByRoute(errorRouteData); let session: AstroSession | undefined; try { const renderContext = await this.createRenderContext({ - locals, + locals: resolvedRenderOptions.locals, pipeline: this.pipeline, skipMiddleware, pathname: this.getPathnameFromRequest(request), @@ -586,21 +621,21 @@ export abstract class BaseApp

{ routeData: errorRouteData, status, props: { error }, - clientAddress, + clientAddress: resolvedRenderOptions.clientAddress, }); session = renderContext.session; const response = await renderContext.render(mod); - return this.mergeResponses(response, originalResponse); + const newResponse = this.mergeResponses(response, originalResponse); + this.#prepareResponse(newResponse, resolvedRenderOptions); + return newResponse; } catch { // Middleware may be the cause of the error, so we try rendering 404/500.astro without it. if (skipMiddleware === false) { return this.renderError(request, { - locals, + ...resolvedRenderOptions, status, response: originalResponse, skipMiddleware: true, - clientAddress, - prerenderedErrorPageFetch, }); } } finally { @@ -609,7 +644,7 @@ export abstract class BaseApp

{ } const response = this.mergeResponses(new Response(null, { status }), originalResponse); - Reflect.set(response, responseSentSymbol, true); + this.#prepareResponse(response, resolvedRenderOptions); return response; } diff --git a/packages/astro/src/core/app/dev/app.ts b/packages/astro/src/core/app/dev/app.ts index ff64f5994eaa..a65bff2306e2 100644 --- a/packages/astro/src/core/app/dev/app.ts +++ b/packages/astro/src/core/app/dev/app.ts @@ -76,7 +76,13 @@ export class DevApp extends BaseApp { async renderError( request: Request, - { locals, skipMiddleware = false, error, clientAddress, status }: RenderErrorOptions, + { + skipMiddleware = false, + error, + status, + response: _response, + ...resolvedRenderOptions + }: RenderErrorOptions, ): Promise { // we always throw when we have Astro errors around the middleware if ( @@ -90,13 +96,13 @@ export class DevApp extends BaseApp { try { const preloadedComponent = await this.pipeline.getComponentByRoute(routeData); const renderContext = await this.createRenderContext({ - locals, + locals: resolvedRenderOptions.locals, pipeline: this.pipeline, - pathname: await this.getPathnameFromRequest(request), + pathname: this.getPathnameFromRequest(request), skipMiddleware, request, routeData, - clientAddress, + clientAddress: resolvedRenderOptions.clientAddress, status, shouldInjectCspMetaTags: false, }); @@ -112,8 +118,7 @@ export class DevApp extends BaseApp { } catch (_err) { if (skipMiddleware === false) { return this.renderError(request, { - clientAddress: undefined, - prerenderedErrorPageFetch: fetch, + ...resolvedRenderOptions, status: 500, skipMiddleware: true, error: _err, diff --git a/packages/astro/src/core/base-pipeline.ts b/packages/astro/src/core/base-pipeline.ts index a736d3fb91cd..78deccf522b0 100644 --- a/packages/astro/src/core/base-pipeline.ts +++ b/packages/astro/src/core/base-pipeline.ts @@ -198,7 +198,7 @@ export abstract class Pipeline { } for (const key of pathKeys) { - if (!(key in server)) { + if (!Object.hasOwn(server, key)) { throw new AstroError({ ...ActionNotFoundError, message: ActionNotFoundError.message(pathKeys.join('.')), diff --git a/packages/astro/src/core/dev/container.ts b/packages/astro/src/core/dev/container.ts index 46ff4fe47960..002fda9c1378 100644 --- a/packages/astro/src/core/dev/container.ts +++ b/packages/astro/src/core/dev/container.ts @@ -78,6 +78,13 @@ export async function createContainer({ .map((r) => r.clientEntrypoint) .filter(Boolean) as string[]; + // Set the initial buildOutput default before runHookConfigDone, so that + // setAdapter() inside astro:config:done can upgrade it to 'server'. + // This matches the ordering in the build path (packages/astro/src/core/build/index.ts). + if (!settings.adapter?.adapterFeatures?.buildOutput) { + settings.buildOutput = getPrerenderDefault(settings.config) ? 'static' : 'server'; + } + // Create the route manifest already outside of Vite so that `runHookConfigDone` can use it to inform integrations of the build output await runHookConfigDone({ settings, logger, command: 'dev' }); @@ -94,10 +101,6 @@ export async function createContainer({ dev: true, }, ); - // If the adapter explicitly set a buildOutput, don't override it - if (!settings.adapter?.adapterFeatures?.buildOutput) { - settings.buildOutput = getPrerenderDefault(settings.config) ? 'static' : 'server'; - } const viteConfig = await createVite( { server: { host, headers, open, allowedHosts }, diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index ca52a80bf4b9..aaa4071b86df 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -37,6 +37,7 @@ import { getParams, getProps, type Pipeline, Slots } from './render/index.js'; import { isRoute404or500, isRouteExternalRedirect, isRouteServerIsland } from './routing/match.js'; import { copyRequest, getOriginPathname, setOriginPathname } from './routing/rewrite.js'; import { AstroSession } from './session/runtime.js'; +import { collapseDuplicateLeadingSlashes } from '@astrojs/internal-helpers/path'; import { validateAndDecodePathname } from './util/pathname.js'; /** @@ -86,6 +87,10 @@ export class RenderContext { static #createNormalizedUrl(requestUrl: string): URL { const url = new URL(requestUrl); + // Collapse multiple leading slashes so middleware sees the canonical pathname. + // Without this, a request to `//admin` would preserve `//admin` in context.url.pathname, + // bypassing middleware checks like `pathname.startsWith('/admin')`. + url.pathname = collapseDuplicateLeadingSlashes(url.pathname); try { // Decode and validate pathname to prevent multi-level encoding bypass attacks url.pathname = validateAndDecodePathname(url.pathname); diff --git a/packages/astro/src/vite-plugin-app/app.ts b/packages/astro/src/vite-plugin-app/app.ts index da1e743a8c6e..41c5ea2c3f5b 100644 --- a/packages/astro/src/vite-plugin-app/app.ts +++ b/packages/astro/src/vite-plugin-app/app.ts @@ -234,7 +234,13 @@ export class AstroServerApp extends BaseApp { async renderError( request: Request, - { locals, skipMiddleware = false, error, clientAddress, status }: RenderErrorOptions, + { + skipMiddleware = false, + error, + status, + response: _response, + ...resolvedRenderOptions + }: RenderErrorOptions, ): Promise { // we always throw when we have Astro errors around the middleware if ( @@ -248,13 +254,13 @@ export class AstroServerApp extends BaseApp { try { const preloadedComponent = await this.pipeline.getComponentByRoute(routeData); const renderContext = await this.createRenderContext({ - locals, + locals: resolvedRenderOptions.locals, pipeline: this.pipeline, - pathname: await this.getPathnameFromRequest(request), + pathname: this.getPathnameFromRequest(request), skipMiddleware, request, routeData, - clientAddress, + clientAddress: resolvedRenderOptions.clientAddress, status, shouldInjectCspMetaTags: !!this.manifest.csp, }); @@ -270,8 +276,7 @@ export class AstroServerApp extends BaseApp { } catch (_err) { if (skipMiddleware === false) { return this.renderError(request, { - clientAddress: undefined, - prerenderedErrorPageFetch: fetch, + ...resolvedRenderOptions, status: 500, skipMiddleware: true, error: _err, diff --git a/packages/astro/src/vite-plugin-config-alias/README.md b/packages/astro/src/vite-plugin-config-alias/README.md index 3b470adeda33..765a00550c5c 100644 --- a/packages/astro/src/vite-plugin-config-alias/README.md +++ b/packages/astro/src/vite-plugin-config-alias/README.md @@ -7,9 +7,8 @@ Consider the following example configuration: ``` { "compilerOptions": { - "baseUrl": "src", "paths": { - "components:*": ["components/*.astro"] + "components:*": ["src/components/*.astro"] } } } diff --git a/packages/astro/test/actions.test.js b/packages/astro/test/actions.test.js index 91fce8b59660..4d20540afe1c 100644 --- a/packages/astro/test/actions.test.js +++ b/packages/astro/test/actions.test.js @@ -166,6 +166,19 @@ describe('Astro Actions', () => { assert.equal(data.code, 'NOT_FOUND'); }); + it('Returns 404 for prototype methods used as action names', async () => { + for (const name of ['constructor', '__proto__', 'toString', 'valueOf']) { + const res = await fixture.fetch(`/_actions/${name}`, { + method: 'POST', + body: JSON.stringify({}), + headers: { + 'Content-Type': 'application/json', + }, + }); + assert.equal(res.status, 404, `Expected 404 for /_actions/${name}`); + } + }); + it('Should fail when calling an action without using Astro.callAction', async () => { const res = await fixture.fetch('/invalid/'); assert.equal(res.status, 500); @@ -606,6 +619,20 @@ describe('Astro Actions', () => { const data = await res.json(); assert.equal(data.code, 'NOT_FOUND'); }); + + it('Returns 404 for prototype methods used as action names', async () => { + for (const name of ['constructor', '__proto__', 'toString', 'valueOf']) { + const req = new Request(`http://example.com/_actions/${name}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + }); + const res = await app.render(req); + assert.equal(res.status, 404, `Expected 404 for /_actions/${name}`); + } + }); }); }); diff --git a/packages/astro/test/custom-500.test.js b/packages/astro/test/custom-500.test.js index 9832b8075925..f54d81d9d569 100644 --- a/packages/astro/test/custom-500.test.js +++ b/packages/astro/test/custom-500.test.js @@ -1,6 +1,5 @@ import assert from 'node:assert/strict'; -import { renameSync } from 'node:fs'; -import { afterEach, describe, it } from 'node:test'; +import { describe, it } from 'node:test'; import * as cheerio from 'cheerio'; import testAdapter from './test-adapter.js'; import { loadFixture } from './test-utils.js'; @@ -9,93 +8,6 @@ describe('Custom 500', () => { /** @type {Awaited>} */ let fixture; - describe('dev', () => { - /** @type {Awaited>} */ - let devServer; - - afterEach(async () => { - await devServer?.stop(); - try { - renameSync( - new URL('./fixtures/custom-500/src/pages/_500.astro', import.meta.url), - new URL('./fixtures/custom-500/src/pages/500.astro', import.meta.url), - ); - } catch (_) {} - }); - - it('renders default error overlay', async () => { - renameSync( - new URL('./fixtures/custom-500/src/pages/500.astro', import.meta.url), - new URL('./fixtures/custom-500/src/pages/_500.astro', import.meta.url), - ); - fixture = await loadFixture({ - root: './fixtures/custom-500/', - output: 'server', - adapter: testAdapter(), - }); - devServer = await fixture.startDevServer(); - - const response = await fixture.fetch('/'); - assert.equal(response.status, 500); - - const html = await response.text(); - - assert.equal(html, 'Error'); - }); - - it('renders custom 500', async () => { - fixture = await loadFixture({ - root: './fixtures/custom-500/', - output: 'server', - adapter: testAdapter(), - }); - devServer = await fixture.startDevServer(); - - const response = await fixture.fetch('/'); - assert.equal(response.status, 500); - - const html = await response.text(); - const $ = cheerio.load(html); - - assert.equal($('h1').text(), 'Server error'); - assert.equal($('p').text(), 'some error'); - }); - - it('renders custom 500 even if error occurs in the middleware', async () => { - fixture = await loadFixture({ - root: './fixtures/custom-500-middleware/', - output: 'server', - adapter: testAdapter(), - }); - devServer = await fixture.startDevServer(); - - const response = await fixture.fetch('/'); - assert.equal(response.status, 500); - - const html = await response.text(); - const $ = cheerio.load(html); - - assert.equal($('h1').text(), 'Server error'); - assert.equal($('p').text(), 'an error'); - }); - - it('renders default error overlay if custom 500 throws', async () => { - fixture = await loadFixture({ - root: './fixtures/custom-500-failing/', - output: 'server', - adapter: testAdapter(), - }); - devServer = await fixture.startDevServer(); - - const response = await fixture.fetch('/'); - assert.equal(response.status, 500); - - const html = await response.text(); - - assert.equal(html, 'Error'); - }); - }); - describe('SSR', () => { /** @type {Awaited>} */ let app; diff --git a/packages/astro/test/fixtures/alias-tsconfig/tsconfig.json b/packages/astro/test/fixtures/alias-tsconfig/tsconfig.json index 31340063c1c6..a448e76fbc8f 100644 --- a/packages/astro/test/fixtures/alias-tsconfig/tsconfig.json +++ b/packages/astro/test/fixtures/alias-tsconfig/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + // TODO: remove when upgrading to TS 6 "baseUrl": ".", "paths": { "@components/*": [ diff --git a/packages/astro/test/fixtures/core-image-base/tsconfig.json b/packages/astro/test/fixtures/core-image-base/tsconfig.json index ff44eda8dc0e..1854f60e49c2 100644 --- a/packages/astro/test/fixtures/core-image-base/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-base/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/strict", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": [ "src/assets/*" diff --git a/packages/astro/test/fixtures/core-image-deletion/tsconfig.json b/packages/astro/test/fixtures/core-image-deletion/tsconfig.json index c193287fccd6..f79821518be8 100644 --- a/packages/astro/test/fixtures/core-image-deletion/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-deletion/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/base", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": ["src/assets/*"] }, diff --git a/packages/astro/test/fixtures/core-image-infersize/tsconfig.json b/packages/astro/test/fixtures/core-image-infersize/tsconfig.json index 2124752725b7..f11a46c8e5fb 100644 --- a/packages/astro/test/fixtures/core-image-infersize/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-infersize/tsconfig.json @@ -1,8 +1,5 @@ { "extends": "astro/tsconfigs/base", - "compilerOptions": { - "baseUrl": ".", - }, "include": [".astro/types.d.ts", "**/*"], "exclude": ["dist"] } diff --git a/packages/astro/test/fixtures/core-image-layout/tsconfig.json b/packages/astro/test/fixtures/core-image-layout/tsconfig.json index c193287fccd6..f79821518be8 100644 --- a/packages/astro/test/fixtures/core-image-layout/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-layout/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/base", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": ["src/assets/*"] }, diff --git a/packages/astro/test/fixtures/core-image-picture-emit-file/tsconfig.json b/packages/astro/test/fixtures/core-image-picture-emit-file/tsconfig.json index ff44eda8dc0e..1854f60e49c2 100644 --- a/packages/astro/test/fixtures/core-image-picture-emit-file/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-picture-emit-file/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/strict", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": [ "src/assets/*" diff --git a/packages/astro/test/fixtures/core-image-remark-imgattr/tsconfig.json b/packages/astro/test/fixtures/core-image-remark-imgattr/tsconfig.json index 2124752725b7..f11a46c8e5fb 100644 --- a/packages/astro/test/fixtures/core-image-remark-imgattr/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-remark-imgattr/tsconfig.json @@ -1,8 +1,5 @@ { "extends": "astro/tsconfigs/base", - "compilerOptions": { - "baseUrl": ".", - }, "include": [".astro/types.d.ts", "**/*"], "exclude": ["dist"] } diff --git a/packages/astro/test/fixtures/core-image-ssg/tsconfig.json b/packages/astro/test/fixtures/core-image-ssg/tsconfig.json index c193287fccd6..f79821518be8 100644 --- a/packages/astro/test/fixtures/core-image-ssg/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-ssg/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/base", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": ["src/assets/*"] }, diff --git a/packages/astro/test/fixtures/core-image-svg-optimized/tsconfig.json b/packages/astro/test/fixtures/core-image-svg-optimized/tsconfig.json index 923ed4e24fb7..51747eeb3102 100644 --- a/packages/astro/test/fixtures/core-image-svg-optimized/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-svg-optimized/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/strict", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": [ "src/assets/*" diff --git a/packages/astro/test/fixtures/core-image-svg/tsconfig.json b/packages/astro/test/fixtures/core-image-svg/tsconfig.json index 923ed4e24fb7..51747eeb3102 100644 --- a/packages/astro/test/fixtures/core-image-svg/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-svg/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/strict", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": [ "src/assets/*" diff --git a/packages/astro/test/fixtures/core-image-unconventional-settings/tsconfig.json b/packages/astro/test/fixtures/core-image-unconventional-settings/tsconfig.json index c193287fccd6..f79821518be8 100644 --- a/packages/astro/test/fixtures/core-image-unconventional-settings/tsconfig.json +++ b/packages/astro/test/fixtures/core-image-unconventional-settings/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/base", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": ["src/assets/*"] }, diff --git a/packages/astro/test/fixtures/core-image/tsconfig.json b/packages/astro/test/fixtures/core-image/tsconfig.json index ff44eda8dc0e..1854f60e49c2 100644 --- a/packages/astro/test/fixtures/core-image/tsconfig.json +++ b/packages/astro/test/fixtures/core-image/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/strict", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": [ "src/assets/*" diff --git a/packages/astro/test/fixtures/custom-renderer/tsconfig.json b/packages/astro/test/fixtures/custom-renderer/tsconfig.json index f1ad59fe06c3..fe4fe0892c95 100644 --- a/packages/astro/test/fixtures/custom-renderer/tsconfig.json +++ b/packages/astro/test/fixtures/custom-renderer/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/base", "compilerOptions": { - "baseUrl": ".", "paths": { "@custom-renderer/*": ["src/custom-renderer/*"] }, diff --git a/packages/astro/test/fixtures/svg-deduplication/tsconfig.json b/packages/astro/test/fixtures/svg-deduplication/tsconfig.json index a1269dab909f..ab8e2d451686 100644 --- a/packages/astro/test/fixtures/svg-deduplication/tsconfig.json +++ b/packages/astro/test/fixtures/svg-deduplication/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "../../../tsconfigs/strictest.json", "compilerOptions": { - "baseUrl": ".", "paths": { "~/*": ["./src/*"] } diff --git a/packages/astro/test/server-islands.test.js b/packages/astro/test/server-islands.test.js index 7ca88ee2257d..9ffcf5b941a7 100644 --- a/packages/astro/test/server-islands.test.js +++ b/packages/astro/test/server-islands.test.js @@ -394,6 +394,36 @@ describe('Server islands', () => { }); }); + it('can fetch the server island endpoint in dev with adapter that does not set buildOutput', async () => { + // Use an adapter that does NOT set adapterFeatures.buildOutput, + // like @astrojs/netlify. This triggers the bug in container.ts where + // buildOutput is reset to 'static' after runHookConfigDone sets it to 'server'. + const devFixture = await loadFixture({ + root: './fixtures/server-islands/hybrid', + adapter: testAdapter({ + extendAdapter: { + adapterFeatures: {}, + }, + }), + }); + const devServer = await devFixture.startDevServer(); + try { + const res = await devFixture.fetch('/'); + assert.equal(res.status, 200); + const html = await res.text(); + const fetchMatch = /fetch\('(\/_server-islands\/Island[^']*)/.exec(html); + assert.ok(fetchMatch, 'should have a server island fetch URL'); + const islandRes = await devFixture.fetch(fetchMatch[1]); + assert.equal( + islandRes.status, + 200, + 'server island endpoint should return 200, not GetStaticPathsRequired error', + ); + } finally { + await devServer.stop(); + } + }); + describe('with no adapter', () => { let devServer; diff --git a/packages/astro/test/units/app/double-slash-bypass.test.js b/packages/astro/test/units/app/double-slash-bypass.test.js new file mode 100644 index 000000000000..f5defd12b491 --- /dev/null +++ b/packages/astro/test/units/app/double-slash-bypass.test.js @@ -0,0 +1,162 @@ +// @ts-check +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { App } from '../../../dist/core/app/app.js'; +import { parseRoute } from '../../../dist/core/routing/parse-route.js'; +import { createComponent, render } from '../../../dist/runtime/server/index.js'; +import { createManifest } from './test-helpers.js'; + +/** + * Security tests for double-slash URL prefix middleware authorization bypass. + * + * Vulnerability: A normalization inconsistency between route matching and middleware + * URL construction allows bypassing middleware-based authorization by prepending an + * extra `/` to the URL path (e.g., `//admin` instead of `/admin`). + * + * - `removeBase("//admin")` strips one slash → router matches `/admin` + * - `context.url.pathname` preserves `//admin` → middleware `startsWith("/admin")` fails + * + * See: withastro/astro-security#5 + * CWE-647: Use of Non-Canonical URL Paths for Authorization Decisions + * CWE-285: Improper Authorization + */ + +const routeOptions = /** @type {Parameters[1]} */ ( + /** @type {any} */ ({ + config: { base: '/', trailingSlash: 'ignore' }, + pageExtensions: [], + }) +); + +const adminRouteData = parseRoute('admin', routeOptions, { + component: 'src/pages/admin.astro', +}); + +const dashboardRouteData = parseRoute('dashboard', routeOptions, { + component: 'src/pages/dashboard.astro', +}); + +const publicRouteData = parseRoute('index.astro', routeOptions, { + component: 'src/pages/index.astro', +}); + +const adminPage = createComponent(() => { + return render`

Admin Panel

`; +}); + +const dashboardPage = createComponent(() => { + return render`

Dashboard

`; +}); + +const publicPage = createComponent(() => { + return render`

Public

`; +}); + +const pageMap = new Map([ + [ + adminRouteData.component, + async () => ({ + page: async () => ({ + default: adminPage, + }), + }), + ], + [ + dashboardRouteData.component, + async () => ({ + page: async () => ({ + default: dashboardPage, + }), + }), + ], + [ + publicRouteData.component, + async () => ({ + page: async () => ({ + default: publicPage, + }), + }), + ], +]); + +/** + * Middleware that blocks access to /admin and /dashboard routes, + * as recommended in the official Astro authentication docs. + * @returns {() => Promise<{onRequest: import('../../../dist/types/public/common.js').MiddlewareHandler}>} + */ +function createAuthMiddleware() { + return async () => ({ + onRequest: /** @type {import('../../../dist/types/public/common.js').MiddlewareHandler} */ ( + async (context, next) => { + const protectedPaths = ['/admin', '/dashboard']; + if (protectedPaths.some((p) => context.url.pathname.startsWith(p))) { + return new Response('Forbidden', { status: 403 }); + } + return next(); + } + ), + }); +} + +/** + * @param {ReturnType} middleware + */ +function createApp(middleware) { + return new App( + createManifest({ + routes: [ + { routeData: adminRouteData }, + { routeData: dashboardRouteData }, + { routeData: publicRouteData }, + ], + pageMap, + middleware, + }), + ); +} + +describe('Security: double-slash URL prefix middleware bypass', () => { + it('middleware blocks /admin with normal request', async () => { + const app = createApp(createAuthMiddleware()); + const request = new Request('http://example.com/admin'); + const response = await app.render(request); + assert.equal(response.status, 403, '/admin should be blocked by middleware'); + }); + + it('middleware blocks //admin (double-slash bypass attempt)', async () => { + const app = createApp(createAuthMiddleware()); + const request = new Request('http://example.com//admin'); + const response = await app.render(request); + assert.equal(response.status, 403, '//admin should also be blocked by middleware'); + }); + + it('middleware blocks ///admin (triple-slash bypass attempt)', async () => { + const app = createApp(createAuthMiddleware()); + const request = new Request('http://example.com///admin'); + const response = await app.render(request); + assert.equal(response.status, 403, '///admin should also be blocked by middleware'); + }); + + it('middleware blocks //dashboard (double-slash on another protected route)', async () => { + const app = createApp(createAuthMiddleware()); + const request = new Request('http://example.com//dashboard'); + const response = await app.render(request); + assert.equal(response.status, 403, '//dashboard should also be blocked by middleware'); + }); + + it('middleware blocks //admin/ (double-slash with trailing slash)', async () => { + const app = createApp(createAuthMiddleware()); + const request = new Request('http://example.com//admin/'); + const response = await app.render(request); + assert.equal(response.status, 403, '//admin/ should also be blocked by middleware'); + }); + + it('public route is still accessible', async () => { + const app = createApp(createAuthMiddleware()); + const request = new Request('http://example.com/'); + const response = await app.render(request); + assert.equal(response.status, 200, '/ should be accessible'); + const html = await response.text(); + assert.match(html, /Public/); + }); +}); diff --git a/packages/astro/test/units/app/error-pages.test.js b/packages/astro/test/units/app/error-pages.test.js index 325a1ba77562..0249f47ffe41 100644 --- a/packages/astro/test/units/app/error-pages.test.js +++ b/packages/astro/test/units/app/error-pages.test.js @@ -45,6 +45,10 @@ describe('App render error pages', () => { assert.equal(response.status, 500); assert.equal(response.headers.get('x-debug'), '1234'); + assert.equal( + [...response.headers.keys()].some((e) => e.startsWith('X-Astro-')), + false, + ); assert.match(await response.text(), /oops/); }); diff --git a/packages/astro/test/units/app/response.test.js b/packages/astro/test/units/app/response.test.js index ad46d52090d3..ce972c536f1c 100644 --- a/packages/astro/test/units/app/response.test.js +++ b/packages/astro/test/units/app/response.test.js @@ -142,4 +142,13 @@ describe('Using Astro.response in SSR', () => { assert.equal(headers.get('four-five'), 'six'); assert.equal(headers.get('Cache-Control'), 'max-age=0, s-maxage=86400'); }); + + it('Removes internal headers', async () => { + const request = new Request('http://example.com/some-header'); + const response = await app.render(request); + assert.equal( + [...response.headers.keys()].some((e) => e.startsWith('X-Astro-')), + false, + ); + }); }); diff --git a/packages/astro/test/units/app/test-helpers.js b/packages/astro/test/units/app/test-helpers.js index 1a7cb3c89997..9a48dd887dd7 100644 --- a/packages/astro/test/units/app/test-helpers.js +++ b/packages/astro/test/units/app/test-helpers.js @@ -1,10 +1,24 @@ // @ts-check -export function createManifest({ routes, pageMap, base = '/', trailingSlash = 'ignore' } = {}) { +/** + * @param {object} [options] + * @param {any[]} [options.routes] + * @param {Map} [options.pageMap] + * @param {string} [options.base] + * @param {string} [options.trailingSlash] + * @param {Function} [options.middleware] + */ +export function createManifest({ + routes, + pageMap, + base = '/', + trailingSlash = 'ignore', + middleware = undefined, +} = {}) { const rootDir = new URL('file:///astro-test/'); const buildDir = new URL('file:///astro-test/dist/'); - return { + return /** @type {import('../../../dist/core/app/types.js').SSRManifest} */ ({ adapterName: 'test-adapter', routes, site: undefined, @@ -16,6 +30,7 @@ export function createManifest({ routes, pageMap, base = '/', trailingSlash = 'i assetsPrefix: undefined, renderers: [], serverLike: true, + middlewareMode: /** @type {'classic'} */ ('classic'), clientDirectives: new Map(), entryModules: {}, inlinedScripts: new Map(), @@ -26,11 +41,12 @@ export function createManifest({ routes, pageMap, base = '/', trailingSlash = 'i serverIslandMappings: undefined, key: Promise.resolve(/** @type {CryptoKey} */ ({})), i18n: undefined, - middleware: undefined, + middleware, actions: undefined, sessionDriver: undefined, checkOrigin: false, allowedDomains: undefined, + actionBodySizeLimit: 0, sessionConfig: undefined, cacheDir: rootDir, srcDir: rootDir, @@ -54,7 +70,7 @@ export function createManifest({ routes, pageMap, base = '/', trailingSlash = 'i experimentalQueuedRendering: { enabled: false, }, - }; + }); } export function createRouteInfo(routeData) { diff --git a/packages/astro/test/units/dev/error-pages.test.js b/packages/astro/test/units/dev/error-pages.test.js new file mode 100644 index 000000000000..6578f143f98a --- /dev/null +++ b/packages/astro/test/units/dev/error-pages.test.js @@ -0,0 +1,290 @@ +// @ts-check +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; +import { ensure404Route } from '../../../dist/core/routing/astro-designed-error-pages.js'; +import { createFixture, createRequestAndResponse, runInContainer } from '../test-utils.js'; + +describe('Dev pipeline - error pages', () => { + describe('Custom 404', () => { + it('renders the custom 404.astro page for unmatched routes', async () => { + const fixture = await createFixture({ + '/src/pages/404.astro': `

Custom 404

`, + '/src/pages/index.astro': `

Home

`, + }); + + await runInContainer({ inlineConfig: { root: fixture.path } }, async (container) => { + const r = createRequestAndResponse({ method: 'GET', url: '/does-not-exist' }); + container.handle(r.req, r.res); + await r.done; + + assert.equal(r.res.statusCode, 404); + const html = await r.text(); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Custom 404'); + }); + }); + + it('renders the built-in Astro 404 page when no custom 404.astro exists', async () => { + const fixture = await createFixture({ + '/src/pages/index.astro': `

Home

`, + }); + + await runInContainer({ inlineConfig: { root: fixture.path } }, async (container) => { + const r = createRequestAndResponse({ method: 'GET', url: '/does-not-exist' }); + container.handle(r.req, r.res); + await r.done; + + assert.equal(r.res.statusCode, 404); + }); + }); + + it('serves the custom 404 page for the /404 path itself', async () => { + const fixture = await createFixture({ + '/src/pages/404.astro': `

Custom 404

`, + '/src/pages/index.astro': `

Home

`, + }); + + await runInContainer({ inlineConfig: { root: fixture.path } }, async (container) => { + const r = createRequestAndResponse({ method: 'GET', url: '/404' }); + container.handle(r.req, r.res); + await r.done; + + assert.equal(r.res.statusCode, 404); + const html = await r.text(); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Custom 404'); + }); + }); + }); + + describe('Custom 500', () => { + it('renders the custom 500.astro page when a route throws', async () => { + const fixture = await createFixture({ + '/src/pages/index.astro': `--- +throw new Error('boom'); +---`, + '/src/pages/500.astro': `

Server Error

`, + }); + + await runInContainer( + { inlineConfig: { root: fixture.path, output: 'server' } }, + async (container) => { + const r = createRequestAndResponse({ method: 'GET', url: '/' }); + container.handle(r.req, r.res); + await r.done; + + assert.equal(r.res.statusCode, 500); + const html = await r.text(); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Server Error'); + }, + ); + }); + + it('renders the dev overlay when no custom 500.astro exists and a route throws', async () => { + const fixture = await createFixture({ + '/src/pages/index.astro': `--- +throw new Error('boom'); +---`, + }); + + await runInContainer( + { inlineConfig: { root: fixture.path, output: 'server' } }, + async (container) => { + const r = createRequestAndResponse({ method: 'GET', url: '/' }); + container.handle(r.req, r.res); + await r.done; + + assert.equal(r.res.statusCode, 500); + const html = await r.text(); + // Dev overlay is emitted when DevApp throws (no custom 500 to catch it) + assert.ok(html.includes('/@vite/client')); + }, + ); + }); + + it('renders the custom 500.astro page when an error originates in middleware', async () => { + const fixture = await createFixture({ + '/src/pages/index.astro': `

Home

`, + '/src/pages/500.astro': `

Server Error

`, + '/src/middleware.js': ` +export const onRequest = (_ctx, _next) => { + throw new Error('middleware error'); +}; +`, + }); + + await runInContainer( + { inlineConfig: { root: fixture.path, output: 'server' } }, + async (container) => { + const r = createRequestAndResponse({ method: 'GET', url: '/' }); + container.handle(r.req, r.res); + await r.done; + + assert.equal(r.res.statusCode, 500); + const html = await r.text(); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'Server Error'); + }, + ); + }); + + it('falls back to the dev overlay when the custom 500.astro itself throws', async () => { + const fixture = await createFixture({ + '/src/pages/index.astro': `--- +throw new Error('page error'); +---`, + '/src/pages/500.astro': `--- +throw new Error('500 page also broken'); +---`, + }); + + await runInContainer( + { inlineConfig: { root: fixture.path, output: 'server' } }, + async (container) => { + const r = createRequestAndResponse({ method: 'GET', url: '/' }); + container.handle(r.req, r.res); + await r.done; + + assert.equal(r.res.statusCode, 500); + const html = await r.text(); + // Escalated to dev overlay after custom 500 also threw + assert.ok(html.includes('/@vite/client')); + }, + ); + }); + + it('re-throws AstroError MiddlewareNoDataOrNextCalled immediately without rendering a 500 page', async () => { + // Middleware that neither calls next() nor returns a Response triggers + // MiddlewareNoDataOrNextCalled. DevApp re-throws this class of AstroError + // immediately rather than attempting to render the 500 page, because the + // error indicates a programming mistake in the middleware itself. + const fixture = await createFixture({ + '/src/pages/index.astro': `

Home

`, + '/src/pages/500.astro': `

Server Error

`, + '/src/middleware.js': ` +export const onRequest = (_ctx, _next) => { + // intentionally not calling next() and not returning — triggers MiddlewareNoDataOrNextCalled +}; +`, + }); + + await runInContainer( + { inlineConfig: { root: fixture.path, output: 'server' } }, + async (container) => { + const r = createRequestAndResponse({ method: 'GET', url: '/' }); + container.handle(r.req, r.res); + await r.done; + + assert.equal(r.res.statusCode, 500); + const html = await r.text(); + // MiddlewareNoDataOrNextCalled is re-thrown straight to the dev overlay, + // bypassing the custom 500 page entirely. + assert.ok(html.includes('/@vite/client')); + // The custom 500 page should NOT have been rendered. + assert.ok(!html.includes('Server Error')); + }, + ); + }); + }); + + describe('ensure404Route', () => { + it('adds the default /404 route when none exists in the manifest', () => { + /** @type {{ routes: any[] }} */ + const manifest = { routes: [] }; + ensure404Route(manifest); + + const route404 = manifest.routes.find((r) => r.route === '/404'); + assert.ok(route404, 'A /404 route should be added when none exists'); + }); + + it('does not add a duplicate /404 route when one already exists', () => { + /** @type {{ routes: any[] }} */ + const manifest = { + routes: [ + { + route: '/404', + component: 'src/pages/404.astro', + params: [], + pathname: '/404', + distURL: [], + pattern: /^\/404\/?$/, + segments: [[{ content: '404', dynamic: false, spread: false }]], + type: 'page', + prerender: false, + fallbackRoutes: [], + isIndex: false, + origin: 'project', + }, + ], + }; + ensure404Route(manifest); + ensure404Route(manifest); // call twice to verify idempotency + + const count = manifest.routes.filter((r) => r.route === '/404').length; + assert.equal(count, 1, 'There should be exactly one /404 route'); + }); + + it('preserves the user-provided 404 component rather than substituting the default', () => { + const userComponent = 'src/pages/404.astro'; + /** @type {{ routes: any[] }} */ + const manifest = { + routes: [ + { + route: '/404', + component: userComponent, + params: [], + pathname: '/404', + distURL: [], + pattern: /^\/404\/?$/, + segments: [[{ content: '404', dynamic: false, spread: false }]], + type: 'page', + prerender: false, + fallbackRoutes: [], + isIndex: false, + origin: 'project', + }, + ], + }; + ensure404Route(manifest); + + const route404 = manifest.routes.find((r) => r.route === '/404'); + assert.equal( + route404?.component, + userComponent, + 'User-provided 404 component should not be replaced by the default', + ); + }); + + it('does not affect /500 routes', () => { + /** @type {{ routes: any[] }} */ + const manifest = { + routes: [ + { + route: '/500', + component: 'src/pages/500.astro', + params: [], + pathname: '/500', + distURL: [], + pattern: /^\/500\/?$/, + segments: [[{ content: '500', dynamic: false, spread: false }]], + type: 'page', + prerender: false, + fallbackRoutes: [], + isIndex: false, + origin: 'project', + }, + ], + }; + ensure404Route(manifest); + + // /404 should be added (no user 404 exists), /500 should be untouched + const count500 = manifest.routes.filter((r) => r.route === '/500').length; + assert.equal(count500, 1, '/500 route count should remain exactly 1'); + + const has404 = manifest.routes.some((r) => r.route === '/404'); + assert.ok(has404, 'Default /404 should have been added'); + }); + }); +}); diff --git a/packages/astro/tsconfigs/base.json b/packages/astro/tsconfigs/base.json index 55adf57bb5e4..1c667f933a8b 100644 --- a/packages/astro/tsconfigs/base.json +++ b/packages/astro/tsconfigs/base.json @@ -26,7 +26,9 @@ // Allow JavaScript files to be imported "allowJs": true, // Allow JSX files (or files that are internally considered JSX, like Astro files) to be imported inside `.js` and `.ts` files. - "jsx": "preserve" + "jsx": "preserve", + "types": ["node"], + "libReplacement": false }, "exclude": ["${configDir}/dist"], "include": ["${configDir}/.astro/types.d.ts", "${configDir}/**/*"] diff --git a/packages/create-astro/tsconfig.json b/packages/create-astro/tsconfig.json index 18443cddf207..17e08031358c 100644 --- a/packages/create-astro/tsconfig.json +++ b/packages/create-astro/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json index 7592ad8a1774..de415b5be4ad 100644 --- a/packages/db/tsconfig.json +++ b/packages/db/tsconfig.json @@ -3,6 +3,7 @@ "include": ["src"], "exclude": ["src/runtime/virtual.ts"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/db/tsconfig.virtual.json b/packages/db/tsconfig.virtual.json index 41d2aef4a11b..f23230d26235 100644 --- a/packages/db/tsconfig.virtual.json +++ b/packages/db/tsconfig.virtual.json @@ -7,6 +7,7 @@ "extends": "../../tsconfig.base.json", "files": ["./src/runtime/virtual.ts"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist/_internal" } } diff --git a/packages/integrations/alpinejs/tsconfig.json b/packages/integrations/alpinejs/tsconfig.json index 1504b4b6dfa4..7f8d5c4c2184 100644 --- a/packages/integrations/alpinejs/tsconfig.json +++ b/packages/integrations/alpinejs/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/cloudflare/package.json b/packages/integrations/cloudflare/package.json index 716d5bb15be5..c50cbbac13a5 100644 --- a/packages/integrations/cloudflare/package.json +++ b/packages/integrations/cloudflare/package.json @@ -43,7 +43,7 @@ "dependencies": { "@astrojs/internal-helpers": "workspace:*", "@astrojs/underscore-redirects": "workspace:*", - "@cloudflare/vite-plugin": "^1.25.2", + "@cloudflare/vite-plugin": "^1.25.6", "piccolore": "^0.1.3", "tinyglobby": "^0.2.15", "vite": "^7.3.1" diff --git a/packages/integrations/cloudflare/test/fixtures/astro-dev-platform/package.json b/packages/integrations/cloudflare/test/fixtures/astro-dev-platform/package.json index a616b112d928..2c86b4ebd44f 100644 --- a/packages/integrations/cloudflare/test/fixtures/astro-dev-platform/package.json +++ b/packages/integrations/cloudflare/test/fixtures/astro-dev-platform/package.json @@ -7,6 +7,6 @@ "astro": "workspace:*" }, "devDependencies": { - "wrangler": "^4.67.0" + "wrangler": "^4.69.0" } } diff --git a/packages/integrations/cloudflare/test/fixtures/astro-env/package.json b/packages/integrations/cloudflare/test/fixtures/astro-env/package.json index fedefe2cf805..4cd789bdcdbe 100644 --- a/packages/integrations/cloudflare/test/fixtures/astro-env/package.json +++ b/packages/integrations/cloudflare/test/fixtures/astro-env/package.json @@ -7,6 +7,6 @@ "astro": "workspace:*" }, "devDependencies": { - "wrangler": "^4.67.0" + "wrangler": "^4.69.0" } } diff --git a/packages/integrations/cloudflare/test/fixtures/custom-entryfile/package.json b/packages/integrations/cloudflare/test/fixtures/custom-entryfile/package.json index bd8e1bce803b..7cbc176ffb5d 100644 --- a/packages/integrations/cloudflare/test/fixtures/custom-entryfile/package.json +++ b/packages/integrations/cloudflare/test/fixtures/custom-entryfile/package.json @@ -5,6 +5,6 @@ "dependencies": { "@astrojs/cloudflare": "workspace:*", "astro": "workspace:*", - "wrangler": "^4.67.0" + "wrangler": "^4.69.0" } } diff --git a/packages/integrations/cloudflare/test/fixtures/prerender-styles/package.json b/packages/integrations/cloudflare/test/fixtures/prerender-styles/package.json index a2a6ea674f7b..f3fd62f991b7 100644 --- a/packages/integrations/cloudflare/test/fixtures/prerender-styles/package.json +++ b/packages/integrations/cloudflare/test/fixtures/prerender-styles/package.json @@ -5,8 +5,8 @@ "dependencies": { "@astrojs/cloudflare": "workspace:*", "@astrojs/mdx": "workspace:*", - "@tailwindcss/vite": "^4.2.0", + "@tailwindcss/vite": "^4.2.1", "astro": "workspace:*", - "tailwindcss": "^4.2.0" + "tailwindcss": "^4.2.1" } } diff --git a/packages/integrations/cloudflare/test/fixtures/routing-priority/package.json b/packages/integrations/cloudflare/test/fixtures/routing-priority/package.json index 21bb2cc84872..ff85e9eb9061 100644 --- a/packages/integrations/cloudflare/test/fixtures/routing-priority/package.json +++ b/packages/integrations/cloudflare/test/fixtures/routing-priority/package.json @@ -7,6 +7,6 @@ "@astrojs/cloudflare": "workspace:*" }, "devDependencies": { - "wrangler": "^4.67.0" + "wrangler": "^4.69.0" } } diff --git a/packages/integrations/cloudflare/test/fixtures/sessions/package.json b/packages/integrations/cloudflare/test/fixtures/sessions/package.json index ff950816f44f..90ce0a66ef86 100644 --- a/packages/integrations/cloudflare/test/fixtures/sessions/package.json +++ b/packages/integrations/cloudflare/test/fixtures/sessions/package.json @@ -7,7 +7,7 @@ }, "devDependencies": { "astro": "workspace:*", - "wrangler": "^4.67.0" + "wrangler": "^4.69.0" }, "scripts": { "build": "astro build", diff --git a/packages/integrations/cloudflare/test/fixtures/vite-plugin/package.json b/packages/integrations/cloudflare/test/fixtures/vite-plugin/package.json index 6fe455c966a6..360376ba759b 100644 --- a/packages/integrations/cloudflare/test/fixtures/vite-plugin/package.json +++ b/packages/integrations/cloudflare/test/fixtures/vite-plugin/package.json @@ -8,12 +8,12 @@ }, "dependencies": { "@astrojs/cloudflare": "workspace:*", - "@astrojs/mdx": "^4.3.13", + "@astrojs/mdx": "workspace:*", "@astrojs/react": "workspace:*", "@astrojs/vue": "workspace:*", "@astrojs/svelte": "workspace:*", - "svelte":"^5.53.1", - "vue": "^3.5.28", + "svelte":"^5.53.5", + "vue": "^3.5.29", "@vitejs/plugin-vue": "^6.0.4", "@types/react": "^18.3.28", "@types/react-dom": "^18.3.7", diff --git a/packages/integrations/cloudflare/test/fixtures/with-svelte/package.json b/packages/integrations/cloudflare/test/fixtures/with-svelte/package.json index 2ab4368d799d..80892b8235a9 100644 --- a/packages/integrations/cloudflare/test/fixtures/with-svelte/package.json +++ b/packages/integrations/cloudflare/test/fixtures/with-svelte/package.json @@ -6,6 +6,6 @@ "@astrojs/cloudflare": "workspace:*", "@astrojs/svelte": "workspace:*", "astro": "workspace:*", - "svelte": "^5.53.1" + "svelte": "^5.53.5" } } diff --git a/packages/integrations/cloudflare/test/fixtures/with-vue/package.json b/packages/integrations/cloudflare/test/fixtures/with-vue/package.json index 3da953acb031..87585003545a 100644 --- a/packages/integrations/cloudflare/test/fixtures/with-vue/package.json +++ b/packages/integrations/cloudflare/test/fixtures/with-vue/package.json @@ -6,6 +6,6 @@ "@astrojs/cloudflare": "workspace:*", "@astrojs/vue": "^5.1.4", "astro": "workspace:*", - "vue": "^3.5.28" + "vue": "^3.5.29" } } diff --git a/packages/integrations/cloudflare/tsconfig.json b/packages/integrations/cloudflare/tsconfig.json index 555bd5e4145b..cc4ec75d8fbc 100644 --- a/packages/integrations/cloudflare/tsconfig.json +++ b/packages/integrations/cloudflare/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src", "virtual.d.ts", "types.d.ts"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/markdoc/test/fixtures/image-assets-custom/tsconfig.json b/packages/integrations/markdoc/test/fixtures/image-assets-custom/tsconfig.json index c193287fccd6..f79821518be8 100644 --- a/packages/integrations/markdoc/test/fixtures/image-assets-custom/tsconfig.json +++ b/packages/integrations/markdoc/test/fixtures/image-assets-custom/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/base", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": ["src/assets/*"] }, diff --git a/packages/integrations/markdoc/test/fixtures/image-assets/tsconfig.json b/packages/integrations/markdoc/test/fixtures/image-assets/tsconfig.json index c193287fccd6..f79821518be8 100644 --- a/packages/integrations/markdoc/test/fixtures/image-assets/tsconfig.json +++ b/packages/integrations/markdoc/test/fixtures/image-assets/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/base", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": ["src/assets/*"] }, diff --git a/packages/integrations/markdoc/tsconfig.json b/packages/integrations/markdoc/tsconfig.json index 1504b4b6dfa4..7f8d5c4c2184 100644 --- a/packages/integrations/markdoc/tsconfig.json +++ b/packages/integrations/markdoc/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro index 68a3fe3baba5..444a90cf53c9 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/content-collection.astro @@ -1,6 +1,6 @@ --- import { getEntry, render } from 'astro:content'; -import MyImage from 'src/components/MyImage.astro'; +import MyImage from '../components/MyImage.astro'; const entry = await getEntry('blog', 'entry'); const { Content } = await render(entry) diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro index 4e4db66a4d17..c250d50ba203 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro +++ b/packages/integrations/mdx/test/fixtures/mdx-images/src/pages/esm-import.astro @@ -1,5 +1,5 @@ --- -import MyImage from 'src/components/MyImage.astro'; +import MyImage from '../components/MyImage.astro'; import MDX from '../components/Component.mdx'; --- diff --git a/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json b/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json index c193287fccd6..f79821518be8 100644 --- a/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json +++ b/packages/integrations/mdx/test/fixtures/mdx-images/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "astro/tsconfigs/base", "compilerOptions": { - "baseUrl": ".", "paths": { "~/assets/*": ["src/assets/*"] }, diff --git a/packages/integrations/mdx/tsconfig.json b/packages/integrations/mdx/tsconfig.json index 1504b4b6dfa4..7f8d5c4c2184 100644 --- a/packages/integrations/mdx/tsconfig.json +++ b/packages/integrations/mdx/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/netlify/package.json b/packages/integrations/netlify/package.json index 2033097629c0..9d23de93a6f5 100644 --- a/packages/integrations/netlify/package.json +++ b/packages/integrations/netlify/package.json @@ -43,8 +43,8 @@ "@astrojs/underscore-redirects": "workspace:*", "@netlify/blobs": "^10.7.0", "@netlify/functions": "^5.1.2", - "@netlify/vite-plugin": "^2.10.2", - "@vercel/nft": "^1.3.1", + "@netlify/vite-plugin": "^2.10.3", + "@vercel/nft": "^1.3.2", "esbuild": "^0.27.3", "tinyglobby": "^0.2.15", "vite": "^7.3.1" diff --git a/packages/integrations/netlify/tsconfig.json b/packages/integrations/netlify/tsconfig.json index ea1c17c2952b..8b6303c6d76d 100644 --- a/packages/integrations/netlify/tsconfig.json +++ b/packages/integrations/netlify/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src", "virtual.d.ts"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/node/tsconfig.json b/packages/integrations/node/tsconfig.json index ea1c17c2952b..8b6303c6d76d 100644 --- a/packages/integrations/node/tsconfig.json +++ b/packages/integrations/node/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src", "virtual.d.ts"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/partytown/tsconfig.json b/packages/integrations/partytown/tsconfig.json index 1504b4b6dfa4..7f8d5c4c2184 100644 --- a/packages/integrations/partytown/tsconfig.json +++ b/packages/integrations/partytown/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/preact/tsconfig.json b/packages/integrations/preact/tsconfig.json index 1504b4b6dfa4..7f8d5c4c2184 100644 --- a/packages/integrations/preact/tsconfig.json +++ b/packages/integrations/preact/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/react/tsconfig.json b/packages/integrations/react/tsconfig.json index c152f18f8f55..c72c8035a4ba 100644 --- a/packages/integrations/react/tsconfig.json +++ b/packages/integrations/react/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src", "env.d.ts"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/sitemap/tsconfig.json b/packages/integrations/sitemap/tsconfig.json index 1504b4b6dfa4..7f8d5c4c2184 100644 --- a/packages/integrations/sitemap/tsconfig.json +++ b/packages/integrations/sitemap/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/solid/tsconfig.json b/packages/integrations/solid/tsconfig.json index 1504b4b6dfa4..7f8d5c4c2184 100644 --- a/packages/integrations/solid/tsconfig.json +++ b/packages/integrations/solid/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/svelte/tsconfig.json b/packages/integrations/svelte/tsconfig.json index 5742d1f6efd2..cf99770081ac 100644 --- a/packages/integrations/svelte/tsconfig.json +++ b/packages/integrations/svelte/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist", "verbatimModuleSyntax": false } diff --git a/packages/integrations/vercel/package.json b/packages/integrations/vercel/package.json index b675c0865cc6..5147cb025c8e 100644 --- a/packages/integrations/vercel/package.json +++ b/packages/integrations/vercel/package.json @@ -48,8 +48,8 @@ "dependencies": { "@astrojs/internal-helpers": "workspace:*", "@vercel/analytics": "^1.6.1", - "@vercel/functions": "^3.4.2", - "@vercel/nft": "^1.3.1", + "@vercel/functions": "^3.4.3", + "@vercel/nft": "^1.3.2", "@vercel/routing-utils": "^5.3.3", "esbuild": "^0.27.3", "tinyglobby": "^0.2.15" diff --git a/packages/integrations/vercel/tsconfig.json b/packages/integrations/vercel/tsconfig.json index ea1c17c2952b..8b6303c6d76d 100644 --- a/packages/integrations/vercel/tsconfig.json +++ b/packages/integrations/vercel/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src", "virtual.d.ts"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/integrations/vue/tsconfig.json b/packages/integrations/vue/tsconfig.json index 100f3c93b6d7..346a8461206c 100644 --- a/packages/integrations/vue/tsconfig.json +++ b/packages/integrations/vue/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../../tsconfig.base.json", "include": ["src", "env.d.ts"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist", "verbatimModuleSyntax": false } diff --git a/packages/internal-helpers/src/path.ts b/packages/internal-helpers/src/path.ts index 3f551ab433ac..060c8ca4e4d5 100644 --- a/packages/internal-helpers/src/path.ts +++ b/packages/internal-helpers/src/path.ts @@ -15,6 +15,15 @@ export function prependForwardSlash(path: string) { return path[0] === '/' ? path : '/' + path; } +export const MANY_LEADING_SLASHES = /^\/{2,}/; + +export function collapseDuplicateLeadingSlashes(path: string) { + if (!path) { + return path; + } + return path.replace(MANY_LEADING_SLASHES, '/'); +} + export const MANY_TRAILING_SLASHES = /\/{2,}$/g; export function collapseDuplicateTrailingSlashes(path: string, trailingSlash: boolean) { diff --git a/packages/internal-helpers/tsconfig.json b/packages/internal-helpers/tsconfig.json index 18443cddf207..17e08031358c 100644 --- a/packages/internal-helpers/tsconfig.json +++ b/packages/internal-helpers/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/language-tools/astro-check/tsconfig.json b/packages/language-tools/astro-check/tsconfig.json index 99f2d27ea436..7c2d5f06b897 100644 --- a/packages/language-tools/astro-check/tsconfig.json +++ b/packages/language-tools/astro-check/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "rootDir": "src", - "outDir": "dist", + "rootDir": "./src", + "outDir": "./dist", "emitDeclarationOnly": false, "sourceMap": true, "module": "ES2022", diff --git a/packages/language-tools/language-server/src/core/compilerUtils.ts b/packages/language-tools/language-server/src/core/compilerUtils.ts deleted file mode 100644 index 3bc0a2dead3d..000000000000 --- a/packages/language-tools/language-server/src/core/compilerUtils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { AttributeNode, Point } from '@astrojs/compiler'; -import { Position as LSPPosition } from '@volar/language-server'; - -/** - * Transform a Point from the Astro compiler to an LSP Position - */ -export function PointToPosition(point: Point) { - // Columns and lines are 0-based in LSP, but the compiler's Point are 1 based. - return LSPPosition.create(point.line - 1, point.column - 1); -} - -type WithRequired = T & { [P in K]-?: T[P] }; -export type AttributeNodeWithPosition = WithRequired; diff --git a/packages/language-tools/language-server/test/units/utils.test.ts b/packages/language-tools/language-server/test/units/utils.test.ts index 3fdbd46a79ae..9ba81a001c7f 100644 --- a/packages/language-tools/language-server/test/units/utils.test.ts +++ b/packages/language-tools/language-server/test/units/utils.test.ts @@ -1,11 +1,9 @@ import assert from 'node:assert'; import { describe, it } from 'node:test'; -import type { Point } from '@astrojs/compiler/types.js'; import { Range } from '@volar/language-server'; import type { Node } from 'vscode-html-languageservice'; import * as html from 'vscode-html-languageservice'; import { getTSXRangesAsLSPRanges, safeConvertToTSX } from '../../dist/core/astro2tsx.js'; -import * as compilerUtils from '../../dist/core/compilerUtils.js'; import { getAstroMetadata } from '../../dist/core/parseAstro.js'; import * as utils from '../../dist/plugins/utils.js'; @@ -64,18 +62,6 @@ describe('Utilities', async () => { assert.strictEqual(utils.isInsideFrontmatter(6, openFrontmatter.frontmatter), true); }); - it('PointToPosition - properly transform a Point from the Astro compiler to an LSP Position', () => { - const point: Point = { - line: 1, - column: 2, - offset: 3, - }; - assert.deepStrictEqual(compilerUtils.PointToPosition(point), { - line: 0, - character: 1, - }); - }); - it('ensureRangeIsInFrontmatter - properly return a range inside the frontmatter', () => { const beforeFrontmatterRange = Range.create(0, 0, 0, 0); const input = '---\nfoo\n---\n'; diff --git a/packages/language-tools/language-server/tsconfig.json b/packages/language-tools/language-server/tsconfig.json index 029f1c7f6a9e..2b5c5fb560b6 100644 --- a/packages/language-tools/language-server/tsconfig.json +++ b/packages/language-tools/language-server/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "outDir": "dist", - "rootDir": "src", + "outDir": "./dist", + "rootDir": "./src", "emitDeclarationOnly": false, "sourceMap": true }, diff --git a/packages/language-tools/ts-plugin/tsconfig.json b/packages/language-tools/ts-plugin/tsconfig.json index 15bae86fc5f0..90eb9eaaad2b 100644 --- a/packages/language-tools/ts-plugin/tsconfig.json +++ b/packages/language-tools/ts-plugin/tsconfig.json @@ -1,12 +1,9 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - "target": "ES2020", - "module": "CommonJS", - "emitDeclarationOnly": false, - "ignoreDeprecations": "5.0" + "outDir": "./dist", + "rootDir": "./src", + "emitDeclarationOnly": false }, "include": ["src"], "exclude": ["node_modules"] diff --git a/packages/language-tools/tsconfig.json b/packages/language-tools/tsconfig.json index 9e965c52c3c8..83a96ac9ee32 100644 --- a/packages/language-tools/tsconfig.json +++ b/packages/language-tools/tsconfig.json @@ -3,6 +3,7 @@ "target": "ES2021", "lib": ["WebWorker", "ES2021"], "module": "commonjs", + // TODO: when upgrading to TS 6, will need to be set to "bundler" "moduleResolution": "node", "declaration": true, "emitDeclarationOnly": true, diff --git a/packages/language-tools/vscode/tsconfig.json b/packages/language-tools/vscode/tsconfig.json index 6e3b3eca1868..523b94dc4b99 100644 --- a/packages/language-tools/vscode/tsconfig.json +++ b/packages/language-tools/vscode/tsconfig.json @@ -1,9 +1,10 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "outDir": "dist", + "rootDir": "./src", + "outDir": "./dist", "emitDeclarationOnly": true, "checkJs": true }, - "include": ["src", ".vscode-test.js"] + "include": ["src"] } diff --git a/packages/language-tools/yaml2ts/tsconfig.json b/packages/language-tools/yaml2ts/tsconfig.json index 029f1c7f6a9e..2b5c5fb560b6 100644 --- a/packages/language-tools/yaml2ts/tsconfig.json +++ b/packages/language-tools/yaml2ts/tsconfig.json @@ -1,8 +1,8 @@ { "extends": "../tsconfig.json", "compilerOptions": { - "outDir": "dist", - "rootDir": "src", + "outDir": "./dist", + "rootDir": "./src", "emitDeclarationOnly": false, "sourceMap": true }, diff --git a/packages/telemetry/tsconfig.json b/packages/telemetry/tsconfig.json index 18443cddf207..17e08031358c 100644 --- a/packages/telemetry/tsconfig.json +++ b/packages/telemetry/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/underscore-redirects/tsconfig.json b/packages/underscore-redirects/tsconfig.json index 18443cddf207..17e08031358c 100644 --- a/packages/underscore-redirects/tsconfig.json +++ b/packages/underscore-redirects/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.base.json", "include": ["src"], "compilerOptions": { + "rootDir": "./src", "outDir": "./dist" } } diff --git a/packages/upgrade/tsconfig.json b/packages/upgrade/tsconfig.json index d15ade00ff80..2341647f4266 100644 --- a/packages/upgrade/tsconfig.json +++ b/packages/upgrade/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.base.json", "include": ["src", "index.d.ts"], "compilerOptions": { + "rootDir": "./src", "allowJs": true, "emitDeclarationOnly": false, "noEmit": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 84d941474ace..b49c2c26ef50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -648,7 +648,7 @@ importers: version: 5.1.0 unstorage: specifier: ^1.17.4 - version: 1.17.4(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.2) + version: 1.17.4(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.3) vfile: specifier: ^6.0.3 version: 6.0.3 @@ -962,8 +962,8 @@ importers: specifier: workspace:* version: link:../../../../integrations/cloudflare '@astrojs/mdx': - specifier: ^4.3.13 - version: 4.3.13(astro@packages+astro) + specifier: workspace:* + version: link:../../../../integrations/mdx '@astrojs/preact': specifier: workspace:* version: link:../../../../integrations/preact @@ -4869,8 +4869,8 @@ importers: specifier: workspace:* version: link:../../underscore-redirects '@cloudflare/vite-plugin': - specifier: ^1.25.2 - version: 1.25.2(@cloudflare/workers-types@4.20260228.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))(workerd@1.20260219.0) + specifier: ^1.25.6 + version: 1.25.6(@cloudflare/workers-types@4.20260228.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))(workerd@1.20260305.0) piccolore: specifier: ^0.1.3 version: 0.1.3 @@ -4910,8 +4910,8 @@ importers: version: link:../../../../../astro devDependencies: wrangler: - specifier: ^4.67.0 - version: 4.67.0(@cloudflare/workers-types@4.20260228.0) + specifier: ^4.69.0 + version: 4.69.0(@cloudflare/workers-types@4.20260228.0) packages/integrations/cloudflare/test/fixtures/astro-env: dependencies: @@ -4923,8 +4923,8 @@ importers: version: link:../../../../../astro devDependencies: wrangler: - specifier: ^4.67.0 - version: 4.67.0(@cloudflare/workers-types@4.20260228.0) + specifier: ^4.69.0 + version: 4.69.0(@cloudflare/workers-types@4.20260228.0) packages/integrations/cloudflare/test/fixtures/binding-image-service: dependencies: @@ -4953,8 +4953,8 @@ importers: specifier: workspace:* version: link:../../../../../astro wrangler: - specifier: ^4.67.0 - version: 4.67.0(@cloudflare/workers-types@4.20260228.0) + specifier: ^4.69.0 + version: 4.69.0(@cloudflare/workers-types@4.20260228.0) packages/integrations/cloudflare/test/fixtures/dev-image-endpoint: dependencies: @@ -4992,13 +4992,13 @@ importers: specifier: workspace:* version: link:../../../../mdx '@tailwindcss/vite': - specifier: ^4.2.0 + specifier: ^4.2.1 version: 4.2.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2)) astro: specifier: workspace:* version: link:../../../../../astro tailwindcss: - specifier: ^4.2.0 + specifier: ^4.2.1 version: 4.2.1 packages/integrations/cloudflare/test/fixtures/routing-priority: @@ -5011,8 +5011,8 @@ importers: version: link:../../../../../astro devDependencies: wrangler: - specifier: ^4.67.0 - version: 4.67.0(@cloudflare/workers-types@4.20260228.0) + specifier: ^4.69.0 + version: 4.69.0(@cloudflare/workers-types@4.20260228.0) packages/integrations/cloudflare/test/fixtures/server-entry: dependencies: @@ -5033,8 +5033,8 @@ importers: specifier: workspace:* version: link:../../../../../astro wrangler: - specifier: ^4.67.0 - version: 4.67.0(@cloudflare/workers-types@4.20260228.0) + specifier: ^4.69.0 + version: 4.69.0(@cloudflare/workers-types@4.20260228.0) packages/integrations/cloudflare/test/fixtures/sql-import: dependencies: @@ -5081,8 +5081,8 @@ importers: specifier: workspace:* version: link:../../.. '@astrojs/mdx': - specifier: ^4.3.13 - version: 4.3.13(astro@packages+astro) + specifier: workspace:* + version: link:../../../../mdx '@astrojs/react': specifier: workspace:* version: link:../../../../react @@ -5114,10 +5114,10 @@ importers: specifier: ^0.34.3 version: 0.34.5 svelte: - specifier: ^5.53.1 + specifier: ^5.53.5 version: 5.53.5 vue: - specifier: ^3.5.28 + specifier: ^3.5.29 version: 3.5.29(typescript@5.9.3) packages/integrations/cloudflare/test/fixtures/with-base: @@ -5156,7 +5156,7 @@ importers: specifier: workspace:* version: link:../../../../../astro svelte: - specifier: ^5.53.1 + specifier: ^5.53.5 version: 5.53.5 packages/integrations/cloudflare/test/fixtures/with-vue: @@ -5171,7 +5171,7 @@ importers: specifier: workspace:* version: link:../../../../../astro vue: - specifier: ^3.5.28 + specifier: ^3.5.29 version: 3.5.29(typescript@5.9.3) packages/integrations/cloudflare/test/fixtures/wrangler-preview-platform: @@ -5698,11 +5698,11 @@ importers: specifier: ^5.1.2 version: 5.1.2 '@netlify/vite-plugin': - specifier: ^2.10.2 - version: 2.10.2(@azure/identity@4.13.0)(@vercel/functions@3.4.2)(rollup@4.58.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2)) + specifier: ^2.10.3 + version: 2.10.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(rollup@4.58.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2)) '@vercel/nft': - specifier: ^1.3.1 - version: 1.3.1(rollup@4.58.0) + specifier: ^1.3.2 + version: 1.3.2(rollup@4.58.0) esbuild: specifier: ^0.27.3 version: 0.27.3 @@ -6283,11 +6283,11 @@ importers: specifier: ^1.6.1 version: 1.6.1(react@19.2.4)(svelte@5.53.5)(vue@3.5.29(typescript@5.9.3)) '@vercel/functions': - specifier: ^3.4.2 - version: 3.4.2 + specifier: ^3.4.3 + version: 3.4.3 '@vercel/nft': - specifier: ^1.3.1 - version: 1.3.1(rollup@4.58.0) + specifier: ^1.3.2 + version: 1.3.2(rollup@4.58.0) '@vercel/routing-utils': specifier: ^5.3.3 version: 5.3.3 @@ -7184,9 +7184,6 @@ packages: '@astrojs/compiler@3.0.0-beta.1': resolution: {integrity: sha512-Z3dKhi4QcgpH7iCwAfYIoF5+7ghegtKLWjsd5L5wmykA4acXFcjZrfMF6KU5YlRbxF3h4waI5E2h6bxtv21dTw==} - '@astrojs/internal-helpers@0.7.5': - resolution: {integrity: sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==} - '@astrojs/language-server@2.16.3': resolution: {integrity: sha512-yO5K7RYCMXUfeDlnU6UnmtnoXzpuQc0yhlaCNZ67k1C/MiwwwvMZz+LGa+H35c49w5QBfvtr4w4Zcf5PcH8uYA==} hasBin: true @@ -7199,19 +7196,6 @@ packages: prettier-plugin-astro: optional: true - '@astrojs/markdown-remark@6.3.10': - resolution: {integrity: sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==} - - '@astrojs/mdx@4.3.13': - resolution: {integrity: sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} - peerDependencies: - astro: ^5.0.0 - - '@astrojs/prism@3.3.0': - resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} - engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} - '@astrojs/solid-js@5.1.3': resolution: {integrity: sha512-KxfYt4y1d7BuSw6EsN1EaPoGYsIES7bEI6AtTbncuabRUUMZs+mOWOeOdmgnwVLj+jbNbhBjUZsqr77eUviZdw==} engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} @@ -7611,37 +7595,37 @@ packages: workerd: optional: true - '@cloudflare/vite-plugin@1.25.2': - resolution: {integrity: sha512-CU4eRPZRumFkcsfinmr8txVCvGVwYEpR5/ATk2p3/004EdFKV4rZ0XzXhLlWVD1XppQ4DAc9FLjFJK4na7QF1A==} + '@cloudflare/vite-plugin@1.25.6': + resolution: {integrity: sha512-p00Wcifec/rpzLFhZnA86JcPYazDkQ54q/ieAwuJQ+YUjbNKWjq872SUwUuX3+nIvo0KIRQmL9VBLE8mWfTE7g==} peerDependencies: vite: ^6.1.0 || ^7.0.0 - '@cloudflare/workerd-darwin-64@1.20260219.0': - resolution: {integrity: sha512-k+xM+swQBQnkrvwobjRPxyeYwjLSludJusR0PqeHe+h6X9QIRGgw3s1AO38lXQsqzMSgG5709oOXSF19NKVVaQ==} + '@cloudflare/workerd-darwin-64@1.20260305.0': + resolution: {integrity: sha512-chhKOpymo0Eh9J3nymrauMqKGboCc4uz/j0gA1G4gioMnKsN2ZDKJ+qjRZDnCoVGy8u2C4pxlmyIfsXCAfIzhQ==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-arm64@1.20260219.0': - resolution: {integrity: sha512-EyfQdsG1KcIVAf4qndT00LZly7sLFm1VxMWHBvOFB/EVYF2sE5HZ0dPbe+yrax5p3eS0oLZthR8ynhz4UulMUQ==} + '@cloudflare/workerd-darwin-arm64@1.20260305.0': + resolution: {integrity: sha512-K9aG2OQk5bBfOP+fyGPqLcqZ9OR3ra6uwnxJ8f2mveq2A2LsCI7ZeGxQiAj75Ti80ytH/gJffZIx4Np2JtU3aQ==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260219.0': - resolution: {integrity: sha512-N0UHXILYYa6htFO/uC92uAqusvynbSbOcHcrVXMKqP9Jy7eqXGMovyKIrNgzYnKIszNB+0lfUYdGI3Wci07LuA==} + '@cloudflare/workerd-linux-64@1.20260305.0': + resolution: {integrity: sha512-tt7XUoIw/cYFeGbkPkcZ6XX1aZm26Aju/4ih+DXxOosbBeGshFSrNJDBfAKKOvkjsAZymJ+WWVDBU+hmNaGfwA==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-arm64@1.20260219.0': - resolution: {integrity: sha512-835pjQ9uuAtwPBOAkPf+oxH3mNE5mqWuE3H7hJsul7WZsRD2FDcariyoT2AW6xyOePILrn4uMnmG1KGc9m/8Pg==} + '@cloudflare/workerd-linux-arm64@1.20260305.0': + resolution: {integrity: sha512-72QTkY5EzylmvCZ8ZTrnJ9DctmQsfSof1OKyOWqu/pv/B2yACfuPMikq8RpPxvVu7hhS0ztGP6ZvXz72Htq4Zg==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260219.0': - resolution: {integrity: sha512-i7qcuOsuAxqqn1n5Ar3Rh1dHUL9vNmpF9FcdMTT84jIrdm5UNrPZz5grJthPmpB9LTcreT9iiP6qKbzGjnCwPA==} + '@cloudflare/workerd-windows-64@1.20260305.0': + resolution: {integrity: sha512-BA0uaQPOaI2F6mJtBDqplGnQQhpXCzwEMI33p/TnDxtSk9u8CGIfBFuI6uqo8mJ6ijIaPjeBLGOn2CiRMET4qg==} engines: {node: '>=16'} cpu: [x64] os: [win32] @@ -9050,8 +9034,8 @@ packages: resolution: {integrity: sha512-wuiaKRbRLG/L049yR+7/p7xSKa4jx6JRBnweRYwP6mMYn9D+x/wccPgsxEMtKqthmow6frs7ZSrNYTt9U3yUdQ==} engines: {node: ^14.16.0 || >=16.0.0} - '@netlify/cache@3.3.5': - resolution: {integrity: sha512-u4wx2se/wRvLsU/sQlT5ruofEwMjo5kg6ybEdQLuIswH6+6+9BCFF8VX4ByBP3MZJl3/pxExmcPiFqo0TBP3tg==} + '@netlify/cache@3.4.0': + resolution: {integrity: sha512-yOHqRfwtLhtL5DaTjjLtwnBu+25TfcfSU6XQ7zDZ3YX+JPYvY6Qs3l9uKn6cm+ty3ER2ODhGS6fsNjptnHRDPg==} engines: {node: '>=20.6.1'} '@netlify/config@24.4.0': @@ -9063,8 +9047,8 @@ packages: resolution: {integrity: sha512-qziF8R9kf7mRNgSpmUH96O0aV1ZiwK4c9ZecFQbDSQuYhgy9GY1WTjiQF0oQnohjTjWNtXhrU39LAeXWNLaBJg==} engines: {node: ^18.14.0 || >=20} - '@netlify/dev@4.11.2': - resolution: {integrity: sha512-quIKbuG7xD3yiWExuZA1Xl/b3Wc+/V3QjrNT4XX5m+dA/D54J61dABf1IGwQYNZHKiF5rVL7D3AwHNAojVQzuw==} + '@netlify/dev@4.11.3': + resolution: {integrity: sha512-U/zvV8yiX5EyFwuG+j6JqiqK/vWJpjEMUraGqNVc7pW9rO2pRJU6MxPbbuHSoNlK9+XPYDOZbtRxEwdckPSWUw==} engines: {node: '>=20.6.1'} peerDependencies: '@netlify/db-dev': 0.2.0 @@ -9127,8 +9111,8 @@ packages: resolution: {integrity: sha512-cW8weDvsKV7zfia2m5EcBy6KILGoPD+eYZ3qWNGnIo05DGF28goPES0xKSDkNYgAF/2rRSIhie2qcBhbGVgSRg==} engines: {node: ^18.14.0 || >=20} - '@netlify/runtime@4.1.15': - resolution: {integrity: sha512-drX5NYNnqAMKnYsStRT8Q1ruNqd68QdMGdakdMtMb/aTaAtPCIug66BPP98YSWvgv9r7O5eO4NX/Ma7UkMVwvQ==} + '@netlify/runtime@4.1.16': + resolution: {integrity: sha512-GOXhRRSVndiDDTmmI19AWZjBwY9bPM3re8XP5F2ZOowzGWzVD3aOBH2lycwmA+iqSGVHWFcyaf0Ea0vLw8NA6g==} engines: {node: '>=20.6.1'} '@netlify/serverless-functions-api@2.9.0': @@ -9143,8 +9127,8 @@ packages: resolution: {integrity: sha512-5gxMWh/S7wr0uHKSTbMv4bjWmWSpwpeLYvErWeVNAPll5/QNFo9aWimMAUuh8ReLY3/fg92XAroVVu7+z27Snw==} engines: {node: ^18.14.0 || >=20} - '@netlify/vite-plugin@2.10.2': - resolution: {integrity: sha512-7enOUs5ijQx/MefNYa6yG41mm1uE/iiaTmBF6oJHIwmavyTP/rvPmo07OuV/PrODdXNEsdzIRWDZ70tGpN3q+w==} + '@netlify/vite-plugin@2.10.3': + resolution: {integrity: sha512-yo/bvGKL8vFDM8lxVWEEaqm2mUMaTZO+rOWzKt+m0+6Z0yRN4YaYlVgsyZv1swYUKHR4GGZAtlNrutTqUrjSCw==} engines: {node: ^20.6.1 || >=22} peerDependencies: vite: ^5 || ^6 || ^7 @@ -10281,8 +10265,8 @@ packages: vue-router: optional: true - '@vercel/functions@3.4.2': - resolution: {integrity: sha512-WDsNNuGOOUhRYxfcSgk8nlXaYW/6u1Lw2eaKm0y4+gDPGK/hwmYIeP2hESfccuCiPXd5ZGO8Jz/V5Ud3ZByoUQ==} + '@vercel/functions@3.4.3': + resolution: {integrity: sha512-kA14KIUVgAY6VXbhZ5jjY+s0883cV3cZqIU3WhrSRxuJ9KvxatMjtmzl0K23HK59oOUjYl7HaE/eYMmhmqpZzw==} engines: {node: '>= 20'} peerDependencies: '@aws-sdk/credential-provider-web-identity': '*' @@ -10295,8 +10279,8 @@ packages: engines: {node: '>=18'} hasBin: true - '@vercel/nft@1.3.1': - resolution: {integrity: sha512-ihNT1rswiq3cy4WKQAV5kJi6UjWX1vLUzlLc+Vvq83G8CU9nMgfDWz5f1tOnSlS8LeC4Wp4qTB3+HGj/ccUrFQ==} + '@vercel/nft@1.3.2': + resolution: {integrity: sha512-HC8venRc4Ya7vNeBsJneKHHMDDWpQie7VaKhAIOst3MKO+DES+Y/SbzSp8mFkD7OzwAE2HhHkeSuSmwS20mz3A==} engines: {node: '>=20'} hasBin: true @@ -12521,9 +12505,6 @@ packages: import-in-the-middle@1.15.0: resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} - import-meta-resolve@4.2.0: - resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -13424,8 +13405,8 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - miniflare@4.20260219.0: - resolution: {integrity: sha512-EIb5wXbWUnnC60XU2aiFOPNd4fgTXzECkwRSOXZ1vdcY9WZaEE9rVf+h+Apw+WkOHRkp3Dr9/ZhQ5y1R+9iZ4Q==} + miniflare@4.20260305.0: + resolution: {integrity: sha512-jVhtKJtiwaZa3rI+WgoLvSJmEazDsoUmAPYRUmEe2VO6VSbvkhbnDRm+dsPbYRatgNIExwrpqG1rv96jHiSb0w==} engines: {node: '>=18.0.0'} hasBin: true @@ -15895,20 +15876,20 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - workerd@1.20260219.0: - resolution: {integrity: sha512-l4U4iT5H8jNV6+EK23ExnUV2z6JvqQtQPrT8XCm4G8RpwC9EPpYTOO9s/ImMPJKe1WSbQUQoJ4k8Nd83fz8skQ==} + workerd@1.20260305.0: + resolution: {integrity: sha512-JkhfCLU+w+KbQmZ9k49IcDYc78GBo7eG8Mir8E2+KVjR7otQAmpcLlsous09YLh8WQ3Bt3Mi6/WMStvMAPukeA==} engines: {node: '>=16'} hasBin: true workerpool@9.3.4: resolution: {integrity: sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==} - wrangler@4.67.0: - resolution: {integrity: sha512-58OoVth7bqm0nqsRgcI67gHbpp0IfR1JIBqDY0XR1FzRu9Qkjn6v2iJAdFf82QcVBFhaMBYQi88WqYGswq5wlQ==} + wrangler@4.69.0: + resolution: {integrity: sha512-EmVfIM65I5b4ITHe3Y9R7zQyf4NUBQ1leStakMlWiVR9n6VlDwuEltyQI2l3i0JciDnWyR3uqe+T6C08ivniTQ==} engines: {node: '>=20.0.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20260219.0 + '@cloudflare/workers-types': ^4.20260305.0 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -16197,8 +16178,6 @@ snapshots: '@astrojs/compiler@3.0.0-beta.1': {} - '@astrojs/internal-helpers@0.7.5': {} - '@astrojs/language-server@2.16.3(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3)': dependencies: '@astrojs/compiler': 2.13.1 @@ -16225,55 +16204,6 @@ snapshots: transitivePeerDependencies: - typescript - '@astrojs/markdown-remark@6.3.10': - dependencies: - '@astrojs/internal-helpers': 0.7.5 - '@astrojs/prism': 3.3.0 - github-slugger: 2.0.0 - hast-util-from-html: 2.0.3 - hast-util-to-text: 4.0.2 - import-meta-resolve: 4.2.0 - js-yaml: 4.1.1 - mdast-util-definitions: 6.0.0 - rehype-raw: 7.0.0 - rehype-stringify: 10.0.1 - remark-gfm: 4.0.1 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - remark-smartypants: 3.0.2 - shiki: 3.23.0 - smol-toml: 1.6.0 - unified: 11.0.5 - unist-util-remove-position: 5.0.0 - unist-util-visit: 5.1.0 - unist-util-visit-parents: 6.0.2 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@astrojs/mdx@4.3.13(astro@packages+astro)': - dependencies: - '@astrojs/markdown-remark': 6.3.10 - '@mdx-js/mdx': 3.1.1 - acorn: 8.16.0 - astro: link:packages/astro - es-module-lexer: 1.7.0 - estree-util-visit: 2.0.0 - hast-util-to-html: 9.0.5 - piccolore: 0.1.3 - rehype-raw: 7.0.0 - remark-gfm: 4.0.1 - remark-smartypants: 3.0.2 - source-map: 0.7.6 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@astrojs/prism@3.3.0': - dependencies: - prismjs: 1.30.0 - '@astrojs/solid-js@5.1.3(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(solid-js@1.9.11)(tsx@4.21.0)(yaml@2.8.2)': dependencies: solid-js: 1.9.11 @@ -16865,19 +16795,19 @@ snapshots: '@cloudflare/kv-asset-handler@0.4.2': {} - '@cloudflare/unenv-preset@2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260219.0)': + '@cloudflare/unenv-preset@2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260305.0)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260219.0 + workerd: 1.20260305.0 - '@cloudflare/vite-plugin@1.25.2(@cloudflare/workers-types@4.20260228.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))(workerd@1.20260219.0)': + '@cloudflare/vite-plugin@1.25.6(@cloudflare/workers-types@4.20260228.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))(workerd@1.20260305.0)': dependencies: - '@cloudflare/unenv-preset': 2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260219.0) - miniflare: 4.20260219.0 + '@cloudflare/unenv-preset': 2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260305.0) + miniflare: 4.20260305.0 unenv: 2.0.0-rc.24 vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2) - wrangler: 4.67.0(@cloudflare/workers-types@4.20260228.0) + wrangler: 4.69.0(@cloudflare/workers-types@4.20260228.0) ws: 8.18.0 transitivePeerDependencies: - '@cloudflare/workers-types' @@ -16885,19 +16815,19 @@ snapshots: - utf-8-validate - workerd - '@cloudflare/workerd-darwin-64@1.20260219.0': + '@cloudflare/workerd-darwin-64@1.20260305.0': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260219.0': + '@cloudflare/workerd-darwin-arm64@1.20260305.0': optional: true - '@cloudflare/workerd-linux-64@1.20260219.0': + '@cloudflare/workerd-linux-64@1.20260305.0': optional: true - '@cloudflare/workerd-linux-arm64@1.20260219.0': + '@cloudflare/workerd-linux-arm64@1.20260305.0': optional: true - '@cloudflare/workerd-windows-64@1.20260219.0': + '@cloudflare/workerd-windows-64@1.20260305.0': optional: true '@cloudflare/workers-types@4.20260228.0': {} @@ -18059,7 +17989,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@netlify/cache@3.3.5': + '@netlify/cache@3.4.0': dependencies: '@netlify/runtime-utils': 2.3.0 @@ -18109,7 +18039,7 @@ snapshots: uuid: 13.0.0 write-file-atomic: 5.0.1 - '@netlify/dev@4.11.2(@azure/identity@4.13.0)(@vercel/functions@3.4.2)(rollup@4.58.0)': + '@netlify/dev@4.11.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(rollup@4.58.0)': dependencies: '@netlify/ai': 0.3.8 '@netlify/blobs': 10.7.0 @@ -18118,9 +18048,9 @@ snapshots: '@netlify/edge-functions-dev': 1.0.11 '@netlify/functions-dev': 1.1.12(rollup@4.58.0) '@netlify/headers': 2.1.3 - '@netlify/images': 1.3.3(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.2) + '@netlify/images': 1.3.3(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.3) '@netlify/redirects': 3.1.5 - '@netlify/runtime': 4.1.15 + '@netlify/runtime': 4.1.16 '@netlify/static': 3.1.3 ulid: 3.0.2 transitivePeerDependencies: @@ -18225,9 +18155,9 @@ snapshots: dependencies: '@netlify/headers-parser': 9.0.2 - '@netlify/images@1.3.3(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.2)': + '@netlify/images@1.3.3(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.3)': dependencies: - ipx: 3.1.1(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.2) + ipx: 3.1.1(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.3) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -18278,10 +18208,10 @@ snapshots: '@netlify/runtime-utils@2.3.0': {} - '@netlify/runtime@4.1.15': + '@netlify/runtime@4.1.16': dependencies: '@netlify/blobs': 10.7.0 - '@netlify/cache': 3.3.5 + '@netlify/cache': 3.4.0 '@netlify/runtime-utils': 2.3.0 '@netlify/types': 2.3.0 transitivePeerDependencies: @@ -18295,9 +18225,9 @@ snapshots: '@netlify/types@2.3.0': {} - '@netlify/vite-plugin@2.10.2(@azure/identity@4.13.0)(@vercel/functions@3.4.2)(rollup@4.58.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))': + '@netlify/vite-plugin@2.10.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(rollup@4.58.0)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@netlify/dev': 4.11.2(@azure/identity@4.13.0)(@vercel/functions@3.4.2)(rollup@4.58.0) + '@netlify/dev': 4.11.3(@azure/identity@4.13.0)(@vercel/functions@3.4.3)(rollup@4.58.0) '@netlify/dev-utils': 4.3.3 dedent: 1.7.1 vite: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.97.3)(tsx@4.21.0)(yaml@2.8.2) @@ -19380,7 +19310,7 @@ snapshots: svelte: 5.53.5 vue: 3.5.29(typescript@5.9.3) - '@vercel/functions@3.4.2': + '@vercel/functions@3.4.3': dependencies: '@vercel/oidc': 3.2.0 @@ -19403,7 +19333,7 @@ snapshots: - rollup - supports-color - '@vercel/nft@1.3.1(rollup@4.58.0)': + '@vercel/nft@1.3.2(rollup@4.58.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.3 '@rollup/pluginutils': 5.3.0(rollup@4.58.0) @@ -22021,8 +21951,6 @@ snapshots: cjs-module-lexer: 1.4.3 module-details-from-path: 1.0.4 - import-meta-resolve@4.2.0: {} - imurmurhash@0.1.4: {} indent-string@5.0.0: {} @@ -22045,7 +21973,7 @@ snapshots: ipaddr.js@2.3.0: {} - ipx@3.1.1(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.2): + ipx@3.1.1(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.3): dependencies: '@fastify/accept-negotiator': 2.0.1 citty: 0.1.6 @@ -22061,7 +21989,7 @@ snapshots: sharp: 0.34.5 svgo: 4.0.0 ufo: 1.6.3 - unstorage: 1.17.4(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.2) + unstorage: 1.17.4(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.3) xss: 1.0.15 transitivePeerDependencies: - '@azure/app-configuration' @@ -23178,12 +23106,12 @@ snapshots: mimic-response@3.1.0: optional: true - miniflare@4.20260219.0: + miniflare@4.20260305.0: dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.34.5 undici: 7.18.2 - workerd: 1.20260219.0 + workerd: 1.20260305.0 ws: 8.18.0 youch: 4.1.0-beta.10 transitivePeerDependencies: @@ -25426,7 +25354,7 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.3 - unstorage@1.17.4(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.2): + unstorage@1.17.4(@azure/identity@4.13.0)(@netlify/blobs@10.7.0)(@vercel/functions@3.4.3): dependencies: anymatch: 3.1.3 chokidar: 5.0.0 @@ -25439,7 +25367,7 @@ snapshots: optionalDependencies: '@azure/identity': 4.13.0 '@netlify/blobs': 10.7.0 - '@vercel/functions': 3.4.2 + '@vercel/functions': 3.4.3 untun@0.1.3: dependencies: @@ -26000,26 +25928,26 @@ snapshots: word-wrap@1.2.5: {} - workerd@1.20260219.0: + workerd@1.20260305.0: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260219.0 - '@cloudflare/workerd-darwin-arm64': 1.20260219.0 - '@cloudflare/workerd-linux-64': 1.20260219.0 - '@cloudflare/workerd-linux-arm64': 1.20260219.0 - '@cloudflare/workerd-windows-64': 1.20260219.0 + '@cloudflare/workerd-darwin-64': 1.20260305.0 + '@cloudflare/workerd-darwin-arm64': 1.20260305.0 + '@cloudflare/workerd-linux-64': 1.20260305.0 + '@cloudflare/workerd-linux-arm64': 1.20260305.0 + '@cloudflare/workerd-windows-64': 1.20260305.0 workerpool@9.3.4: {} - wrangler@4.67.0(@cloudflare/workers-types@4.20260228.0): + wrangler@4.69.0(@cloudflare/workers-types@4.20260228.0): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260219.0) + '@cloudflare/unenv-preset': 2.14.0(unenv@2.0.0-rc.24)(workerd@1.20260305.0) blake3-wasm: 2.1.5 esbuild: 0.27.3 - miniflare: 4.20260219.0 + miniflare: 4.20260305.0 path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260219.0 + workerd: 1.20260305.0 optionalDependencies: '@cloudflare/workers-types': 4.20260228.0 fsevents: 2.3.3 diff --git a/scripts/jsconfig.json b/scripts/jsconfig.json index 5cf3835e818d..0caa704a7a8e 100644 --- a/scripts/jsconfig.json +++ b/scripts/jsconfig.json @@ -3,7 +3,7 @@ "declaration": true, "strict": true, "module": "esnext", - "moduleResolution": "node", + "moduleResolution": "nodenext", "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, diff --git a/tsconfig.base.json b/tsconfig.base.json index 432d3c35380d..7ed082d83639 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -4,14 +4,15 @@ "declaration": true, "emitDeclarationOnly": true, "strict": true, - "moduleResolution": "Node16", - "target": "ES2022", - "module": "Node16", + "moduleResolution": "nodenext", + "target": "esnext", + "module": "nodenext", "esModuleInterop": true, "skipLibCheck": true, "verbatimModuleSyntax": true, "stripInternal": true, "noUnusedLocals": true, - "noUnusedParameters": true + "noUnusedParameters": true, + "types": ["node"] } }