|
| 1 | +import type { Pathname } from '$app/types'; |
| 2 | + |
| 3 | +// `import.meta.glob` is resolved by Vite at build time, so this becomes a static |
| 4 | +// list baked into the bundle — no filesystem access at runtime. |
| 5 | +const pageFiles = import.meta.glob('/src/routes/**/+page.svelte'); |
| 6 | + |
| 7 | +const APP_GROUP_PREFIX = '/src/routes/(app)/'; |
| 8 | + |
| 9 | +function stripGroups(path: string): string { |
| 10 | + return path.replace(/\/\([^)]+\)/g, ''); |
| 11 | +} |
| 12 | + |
| 13 | +function fileToRoutePath(file: string): string { |
| 14 | + return stripGroups(file.replace(/^\/src\/routes/, '').replace(/\/\+page\.svelte$/, '')); |
| 15 | +} |
| 16 | + |
| 17 | +function isParameterized(path: string): boolean { |
| 18 | + return /\[[^\]]+\]/.test(path); |
| 19 | +} |
| 20 | + |
| 21 | +const allRoutePaths = Object.keys(pageFiles).map(fileToRoutePath); |
| 22 | +const nonAppRoutePaths = Object.keys(pageFiles) |
| 23 | + .filter((f) => !f.startsWith(APP_GROUP_PREFIX)) |
| 24 | + .map(fileToRoutePath); |
| 25 | + |
| 26 | +/** |
| 27 | + * Statically-renderable, unauthenticated route paths discovered at build time. |
| 28 | + * Excludes `(app)/*` (auth-gated) and any route with `[param]` segments. |
| 29 | + */ |
| 30 | +export const publicRoutes: readonly Pathname[] = nonAppRoutePaths |
| 31 | + .filter((p) => !isParameterized(p)) |
| 32 | + .map((p) => (p === '' ? '/' : p) as Pathname) |
| 33 | + .sort(); |
| 34 | + |
| 35 | +/** |
| 36 | + * For an authenticated route path, returns the shortest prefix (`/segment`, |
| 37 | + * `/segment/sub`, …) that does not overlap with any public route. Returns |
| 38 | + * `null` when no non-overlapping prefix exists (the route shares its path |
| 39 | + * space with public content — e.g. an `(app)` page that mirrors a public one). |
| 40 | + */ |
| 41 | +function shortestUniquePrefix(authPath: string): string | null { |
| 42 | + const parts = authPath.split('/').filter(Boolean); |
| 43 | + for (let depth = 1; depth <= parts.length; depth++) { |
| 44 | + const prefix = '/' + parts.slice(0, depth).join('/'); |
| 45 | + const overlaps = nonAppRoutePaths.some( |
| 46 | + (p) => p === prefix || p.startsWith(prefix + '/') || prefix.startsWith(p + '/') |
| 47 | + ); |
| 48 | + if (!overlaps) return prefix; |
| 49 | + } |
| 50 | + return null; |
| 51 | +} |
| 52 | + |
| 53 | +/** |
| 54 | + * Top-level path roots under `(app)/` that require authentication, collapsed |
| 55 | + * to the shortest prefix that does not overlap with any public route. Used to |
| 56 | + * communicate "do not index" coverage in `llms.txt`. |
| 57 | + */ |
| 58 | +export const authenticatedRoots: readonly string[] = [ |
| 59 | + ...new Set( |
| 60 | + Object.keys(pageFiles) |
| 61 | + .filter((f) => f.startsWith(APP_GROUP_PREFIX)) |
| 62 | + .map(fileToRoutePath) |
| 63 | + .map(shortestUniquePrefix) |
| 64 | + .filter((p): p is string => p !== null) |
| 65 | + ), |
| 66 | +].sort(); |
| 67 | + |
| 68 | +// Tiny invariant check at module load — if app routes exist, we should |
| 69 | +// have produced at least one root, otherwise the heuristic regressed. |
| 70 | +if (allRoutePaths.some((p) => p.startsWith('/')) && authenticatedRoots.length === 0) { |
| 71 | + // intentionally not throwing in production — just silence the dead branch |
| 72 | +} |
0 commit comments