diff --git a/packages/astro/src/integration/index.ts b/packages/astro/src/integration/index.ts index 69564fcc6535..28092cad84be 100644 --- a/packages/astro/src/integration/index.ts +++ b/packages/astro/src/integration/index.ts @@ -1,18 +1,14 @@ -import { readFileSync, writeFileSync } from 'node:fs'; -import { consoleSandbox, debug } from '@sentry/core'; +import { consoleSandbox } from '@sentry/core'; import { sentryVitePlugin } from '@sentry/vite-plugin'; -import type { AstroConfig, AstroIntegration, RoutePart } from 'astro'; +import type { AstroConfig, AstroIntegration } from 'astro'; import * as fs from 'fs'; import * as path from 'path'; import { buildClientSnippet, buildSdkInitFileImportSnippet, buildServerSnippet } from './snippets'; -import type { IntegrationResolvedRoute, SentryOptions } from './types'; +import type { SentryOptions } from './types'; const PKG_NAME = '@sentry/astro'; export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { - let sentryServerInitPath: string | undefined; - let didSaveRouteData = false; - return { name: PKG_NAME, hooks: { @@ -138,8 +134,6 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { injectScript('page-ssr', buildServerSnippet(options || {})); } - sentryServerInitPath = pathToServerInit; - // Prevent Sentry from being externalized for SSR. // Cloudflare like environments have Node.js APIs are available under `node:` prefix. // Ref: https://developers.cloudflare.com/workers/runtime-apis/nodejs/ @@ -171,36 +165,6 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { }); } }, - - // @ts-expect-error - This hook is available in Astro 5+ - 'astro:routes:resolved': ({ routes }: { routes: IntegrationResolvedRoute[] }) => { - if (!sentryServerInitPath || didSaveRouteData) { - return; - } - - try { - const serverInitContent = readFileSync(sentryServerInitPath, 'utf8'); - - const updatedServerInitContent = `${serverInitContent}\nglobalThis["__sentryRouteInfo"] = ${JSON.stringify( - routes.map(route => { - return { - ...route, - patternCaseSensitive: joinRouteSegments(route.segments), // Store parametrized routes with correct casing on `globalThis` to be able to use them on the server during runtime - patternRegex: route.patternRegex.source, // using `source` to be able to serialize the regex - }; - }), - null, - 2, - )};`; - - writeFileSync(sentryServerInitPath, updatedServerInitContent, 'utf8'); - - didSaveRouteData = true; // Prevents writing the file multiple times during runtime - debug.log('Successfully added route pattern information to Sentry config file:', sentryServerInitPath); - } catch (error) { - debug.warn(`Failed to write to Sentry config file at ${sentryServerInitPath}:`, error); - } - }, }, }; }; @@ -307,18 +271,3 @@ export function getUpdatedSourceMapSettings( return { previousUserSourceMapSetting, updatedSourceMapSetting }; } - -/** - * Join Astro route segments into a case-sensitive single path string. - * - * Astro lowercases the parametrized route. Joining segments manually is recommended to get the correct casing of the routes. - * Recommendation in comment: https://github.com/withastro/astro/issues/13885#issuecomment-2934203029 - * Function Reference: https://github.com/joanrieu/astro-typed-links/blob/b3dc12c6fe8d672a2bc2ae2ccc57c8071bbd09fa/package/src/integration.ts#L16 - */ -function joinRouteSegments(segments: RoutePart[][]): string { - const parthArray = segments.map(segment => - segment.map(routePart => (routePart.dynamic ? `[${routePart.content}]` : routePart.content)).join(''), - ); - - return `/${parthArray.join('/')}`; -} diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 9f04d5427fcf..19aa11af0767 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -22,8 +22,7 @@ import { startSpan, withIsolationScope, } from '@sentry/node'; -import type { APIContext, MiddlewareResponseHandler } from 'astro'; -import type { ResolvedRouteWithCasedPattern } from '../integration/types'; +import type { APIContext, MiddlewareResponseHandler, RoutePart } from 'astro'; type MiddlewareOptions = { /** @@ -96,9 +95,6 @@ async function instrumentRequest( addNonEnumerableProperty(locals, '__sentry_wrapped__', true); } - const storedBuildTimeRoutes = (globalThis as unknown as { __sentryRouteInfo?: ResolvedRouteWithCasedPattern[] }) - ?.__sentryRouteInfo; - const isDynamicPageRequest = checkIsDynamicPageRequest(ctx); const request = ctx.request; @@ -135,10 +131,21 @@ async function instrumentRequest( // `routePattern` is available after Astro 5 const contextWithRoutePattern = ctx as Parameters[0] & { routePattern?: string }; const rawRoutePattern = contextWithRoutePattern.routePattern; - const foundRoute = storedBuildTimeRoutes?.find(route => route.pattern === rawRoutePattern); + + // @ts-expect-error Implicit any on Symbol.for (This is available in Astro 5) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const routesFromManifest = ctx?.[Symbol.for('context.routes')]?.manifest?.routes; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const matchedRouteSegmentsFromManifest = routesFromManifest?.find( + (route: { routeData?: { route?: string } }) => route?.routeData?.route === rawRoutePattern, + )?.routeData?.segments; const parametrizedRoute = - foundRoute?.patternCaseSensitive || interpolateRouteFromUrlAndParams(ctx.url.pathname, ctx.params); + // Astro v5 - Joining the segments to get the correct casing of the parametrized route + (matchedRouteSegmentsFromManifest && joinRouteSegments(matchedRouteSegmentsFromManifest)) || + // Fallback (Astro v4 and earlier) + interpolateRouteFromUrlAndParams(ctx.url.pathname, ctx.params); const source = parametrizedRoute ? 'route' : 'url'; // storing res in a variable instead of directly returning is necessary to @@ -365,3 +372,18 @@ function checkIsDynamicPageRequest(context: Parameters + segment.map(routePart => (routePart.dynamic ? `[${routePart.content}]` : routePart.content)).join(''), + ); + + return `/${parthArray.join('/')}`; +}