Files
klz-cables.com/.pnpm-store/v10/files/62/9a3a1291b7e5f57377ae7981843d70e8a7e9473280202c542f5aef357ecd77611a9664f1e88b8c212ad7420e48ca79942a18bd168dc099761362ec4a69f7c0
Marc Mintel 5397309103
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
fix(products): fix breadcrumbs and product filtering (backport from main)
2026-02-24 16:04:21 +01:00

202 lines
6.2 KiB
Plaintext

import { GLOBAL_OBJ, debug } from '@sentry/core';
import { DEBUG_BUILD } from '../../common/debug-build.js';
const globalWithInjectedManifest = GLOBAL_OBJ
;
// Some performance caches
let cachedManifest = null;
let cachedManifestString = undefined;
const compiledRegexCache = new Map();
const routeResultCache = new Map();
/**
* Calculate the specificity score for a route path.
* Lower scores indicate more specific routes.
*/
function getRouteSpecificity(routePath) {
const segments = routePath.split('/').filter(Boolean);
let score = 0;
for (const segment of segments) {
if (segment.startsWith(':')) {
const paramName = segment.substring(1);
if (paramName.endsWith('*?')) {
// Optional catch-all: [[...param]]
score += 1000;
} else if (paramName.endsWith('*')) {
// Required catch-all: [...param]
score += 100;
} else {
// Regular dynamic segment: [param]
score += 10;
}
}
// Static segments add 0 to score as they are most specific
}
if (segments.length > 0) {
// Add a small penalty based on inverse of segment count
// This ensures that routes with more segments are preferred
// e.g., '/:locale/foo' is more specific than '/:locale'
// We use a small value (1 / segments.length) so it doesn't override the main scoring
// but breaks ties between routes with the same number of dynamic segments
const segmentCountPenalty = 1 / segments.length;
score += segmentCountPenalty;
}
return score;
}
/**
* Get compiled regex from cache or create and cache it.
*/
function getCompiledRegex(regexString) {
if (compiledRegexCache.has(regexString)) {
return compiledRegexCache.get(regexString) ?? null;
}
try {
// eslint-disable-next-line @sentry-internal/sdk/no-regexp-constructor -- regex patterns are from build-time route manifest, not user input
const regex = new RegExp(regexString);
compiledRegexCache.set(regexString, regex);
return regex;
} catch (error) {
DEBUG_BUILD && debug.warn('Could not compile regex', { regexString, error });
// Cache the failure to avoid repeated attempts by storing undefined
return null;
}
}
/**
* Get and cache the route manifest from the global object.
* @returns The parsed route manifest or null if not available/invalid.
*/
function getManifest() {
if (
!globalWithInjectedManifest?._sentryRouteManifest ||
typeof globalWithInjectedManifest._sentryRouteManifest !== 'string'
) {
return null;
}
const currentManifestString = globalWithInjectedManifest._sentryRouteManifest;
// Return cached manifest if the string hasn't changed
if (cachedManifest && cachedManifestString === currentManifestString) {
return cachedManifest;
}
// Clear caches when manifest changes
compiledRegexCache.clear();
routeResultCache.clear();
let manifest = {
staticRoutes: [],
dynamicRoutes: [],
isrRoutes: [],
};
// Shallow check if the manifest is actually what we expect it to be
try {
manifest = JSON.parse(currentManifestString);
if (!Array.isArray(manifest.staticRoutes) || !Array.isArray(manifest.dynamicRoutes)) {
return null;
}
// Cache the successfully parsed manifest
cachedManifest = manifest;
cachedManifestString = currentManifestString;
return manifest;
} catch {
// Something went wrong while parsing the manifest, so we'll fallback to no parameterization
DEBUG_BUILD && debug.warn('Could not extract route manifest');
return null;
}
}
/**
* Find matching routes from static and dynamic route collections.
* @param route - The route to match against.
* @param staticRoutes - Array of static route objects.
* @param dynamicRoutes - Array of dynamic route objects.
* @returns Array of matching route paths.
*/
function findMatchingRoutes(
route,
staticRoutes,
dynamicRoutes,
) {
const matches = [];
// Static path: no parameterization needed, return empty array
if (staticRoutes.some(r => r.path === route)) {
return matches;
}
// Dynamic path: find the route pattern that matches the concrete route
for (const dynamicRoute of dynamicRoutes) {
if (dynamicRoute.regex) {
const regex = getCompiledRegex(dynamicRoute.regex);
if (regex?.test(route)) {
matches.push(dynamicRoute.path);
}
}
}
// Try matching with optional prefix segments (for i18n routing patterns)
// This handles cases like '/foo' matching '/:locale/foo' when using next-intl with localePrefix: "as-needed"
// We do this regardless of whether we found direct matches, as we want the most specific match
if (!route.startsWith('/:')) {
for (const dynamicRoute of dynamicRoutes) {
if (dynamicRoute.hasOptionalPrefix && dynamicRoute.regex) {
// Prepend a placeholder segment to simulate the optional prefix
// e.g., '/foo' becomes '/PLACEHOLDER/foo' to match '/:locale/foo'
// Special case: '/' becomes '/PLACEHOLDER' (not '/PLACEHOLDER/') to match '/:locale' pattern
const routeWithPrefix = route === '/' ? '/SENTRY_OPTIONAL_PREFIX' : `/SENTRY_OPTIONAL_PREFIX${route}`;
const regex = getCompiledRegex(dynamicRoute.regex);
if (regex?.test(routeWithPrefix)) {
matches.push(dynamicRoute.path);
}
}
}
}
return matches;
}
/**
* Parameterize a route using the route manifest.
*
* @param route - The route to parameterize.
* @returns The parameterized route or undefined if no parameterization is needed.
*/
const maybeParameterizeRoute = (route) => {
const manifest = getManifest();
if (!manifest) {
return undefined;
}
// Check route result cache after manifest validation
if (routeResultCache.has(route)) {
return routeResultCache.get(route);
}
const { staticRoutes, dynamicRoutes } = manifest;
if (!Array.isArray(staticRoutes) || !Array.isArray(dynamicRoutes)) {
return undefined;
}
const matches = findMatchingRoutes(route, staticRoutes, dynamicRoutes);
// We can always do the `sort()` call, it will short-circuit when it has one array item
const result = matches.sort((a, b) => getRouteSpecificity(a) - getRouteSpecificity(b))[0];
routeResultCache.set(route, result);
return result;
};
export { getManifest, maybeParameterizeRoute };
//# sourceMappingURL=parameterization.js.map