import * as fs from 'fs'; import * as path from 'path'; export type RouteAccess = 'public' | 'auth' | 'admin' | 'sponsor'; export type RouteParams = Record; export type WebsiteRouteDefinition = { pathTemplate: string; params?: RouteParams; access: RouteAccess; expectedPathTemplate?: string; allowNotFound?: boolean; }; function walkDir(rootDir: string): string[] { const entries = fs.readdirSync(rootDir, { withFileTypes: true }); const results: string[] = []; for (const entry of entries) { if (entry.name.startsWith('.')) continue; const fullPath = path.join(rootDir, entry.name); if (entry.isDirectory()) { results.push(...walkDir(fullPath)); continue; } results.push(fullPath); } return results; } function toPathTemplate(appDir: string, pageFilePath: string): string { const rel = path.relative(appDir, pageFilePath); const segments = rel.split(path.sep); // drop trailing "page.tsx" segments.pop(); // root page.tsx if (segments.length === 0) return '/'; return `/${segments.join('/')}`; } export function listNextAppPageTemplates(appDir?: string): string[] { const resolvedAppDir = appDir ?? path.join(process.cwd(), 'apps', 'website', 'app'); const files = walkDir(resolvedAppDir); const pages = files.filter((f) => path.basename(f) === 'page.tsx'); return pages.map((pagePath) => toPathTemplate(resolvedAppDir, pagePath)); } export function resolvePathTemplate(pathTemplate: string, params: RouteParams = {}): string { return pathTemplate.replace(/\[([^\]]+)\]/g, (_match, key: string) => { const replacement = params[key]; if (!replacement) { throw new Error(`Missing route param "${key}" for template "${pathTemplate}"`); } return replacement; }); } // Default IDs used to resolve dynamic routes in smoke tests. // These values must be supported by the docker mock API in docker-compose.test.yml. const LEAGUE_ID = 'league-1'; const DRIVER_ID = 'driver-1'; const TEAM_ID = 'team-1'; const RACE_ID = 'race-1'; const PROTEST_ID = 'protest-1'; const ROUTE_META: Record> = { '/': { access: 'public' }, '/404': { access: 'public' }, '/500': { access: 'public' }, '/admin': { access: 'admin' }, '/admin/users': { access: 'admin' }, '/auth/forgot-password': { access: 'public' }, '/auth/login': { access: 'public' }, '/auth/reset-password': { access: 'public' }, '/auth/signup': { access: 'public' }, '/dashboard': { access: 'auth' }, '/drivers': { access: 'public' }, '/drivers/[id]': { access: 'public', params: { id: DRIVER_ID } }, '/leaderboards': { access: 'public' }, '/leaderboards/drivers': { access: 'public' }, '/leagues': { access: 'public' }, '/leagues/create': { access: 'auth' }, '/leagues/[id]': { access: 'public', params: { id: LEAGUE_ID } }, '/leagues/[id]/roster/admin': { access: 'admin', params: { id: LEAGUE_ID } }, '/leagues/[id]/rulebook': { access: 'public', params: { id: LEAGUE_ID } }, '/leagues/[id]/schedule': { access: 'public', params: { id: LEAGUE_ID } }, '/leagues/[id]/schedule/admin': { access: 'admin', params: { id: LEAGUE_ID } }, '/leagues/[id]/settings': { access: 'admin', params: { id: LEAGUE_ID } }, '/leagues/[id]/sponsorships': { access: 'admin', params: { id: LEAGUE_ID } }, '/leagues/[id]/standings': { access: 'public', params: { id: LEAGUE_ID } }, '/leagues/[id]/stewarding': { access: 'admin', params: { id: LEAGUE_ID } }, '/leagues/[id]/stewarding/protests/[protestId]': { access: 'admin', params: { id: LEAGUE_ID, protestId: PROTEST_ID }, }, '/leagues/[id]/wallet': { access: 'admin', params: { id: LEAGUE_ID } }, '/onboarding': { access: 'auth' }, '/profile': { access: 'auth' }, '/profile/leagues': { access: 'auth' }, '/profile/liveries': { access: 'auth' }, '/profile/liveries/upload': { access: 'auth' }, '/profile/settings': { access: 'auth' }, '/profile/sponsorship-requests': { access: 'auth' }, '/races': { access: 'public' }, '/races/all': { access: 'public' }, '/races/[id]': { access: 'public', params: { id: RACE_ID } }, '/races/[id]/results': { access: 'public', params: { id: RACE_ID } }, '/races/[id]/stewarding': { access: 'admin', params: { id: RACE_ID } }, '/sponsor': { access: 'sponsor', expectedPathTemplate: '/sponsor/dashboard' }, '/sponsor/billing': { access: 'sponsor' }, '/sponsor/campaigns': { access: 'sponsor' }, '/sponsor/dashboard': { access: 'sponsor' }, '/sponsor/leagues': { access: 'sponsor' }, '/sponsor/leagues/[id]': { access: 'sponsor', params: { id: LEAGUE_ID } }, '/sponsor/settings': { access: 'sponsor' }, '/sponsor/signup': { access: 'public' }, '/teams': { access: 'public' }, '/teams/leaderboard': { access: 'public' }, '/teams/[id]': { access: 'public', params: { id: TEAM_ID } }, }; export function getWebsiteRouteInventory(): WebsiteRouteDefinition[] { const discovered = listNextAppPageTemplates(); const missingMeta = discovered.filter((template) => !ROUTE_META[template]); if (missingMeta.length > 0) { throw new Error( `Missing ROUTE_META entries for discovered pages:\n${missingMeta .slice() .sort() .map((t) => `- ${t}`) .join('\n')}`, ); } const extraMeta = Object.keys(ROUTE_META).filter((template) => !discovered.includes(template)); if (extraMeta.length > 0) { throw new Error( `ROUTE_META contains templates that are not present as page.tsx routes:\n${extraMeta .slice() .sort() .map((t) => `- ${t}`) .join('\n')}`, ); } return discovered .slice() .sort() .map((pathTemplate) => ({ pathTemplate, ...ROUTE_META[pathTemplate] })); } export function getWebsiteParamEdgeCases(): WebsiteRouteDefinition[] { return [ { pathTemplate: '/races/[id]', params: { id: 'does-not-exist' }, access: 'public', allowNotFound: true }, { pathTemplate: '/leagues/[id]', params: { id: 'does-not-exist' }, access: 'public', allowNotFound: true }, ]; } export function getWebsiteFaultInjectionRoutes(): WebsiteRouteDefinition[] { return [ { pathTemplate: '/leagues/[id]', params: { id: LEAGUE_ID }, access: 'public' }, { pathTemplate: '/leagues/[id]/schedule/admin', params: { id: LEAGUE_ID }, access: 'admin' }, { pathTemplate: '/sponsor/dashboard', access: 'sponsor' }, { pathTemplate: '/races/[id]', params: { id: RACE_ID }, access: 'public' }, ]; } export function getWebsiteAuthDriftRoutes(): WebsiteRouteDefinition[] { return [{ pathTemplate: '/sponsor/dashboard', access: 'sponsor' }]; }