From 51eeb2fafb5bd3d91bee66616618a8015cd65d85 Mon Sep 17 00:00:00 2001 From: Reldo Varrock Date: Sun, 1 Feb 2026 23:33:21 +1000 Subject: [PATCH] feat: add routes.json generator for programmatic route access --- package.json | 1 + routes.json | 650 ++++++++++++++++++++++++++++++++ scripts/generate-routes-json.js | 355 +++++++++++++++++ 3 files changed, 1006 insertions(+) create mode 100644 routes.json create mode 100644 scripts/generate-routes-json.js diff --git a/package.json b/package.json index 8b0018acb..c848e670e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "format": "prettier --write \"src/**/*.{ts,tsx}\"", "clean": "rm -rf dist node_modules", "generate:api": "openapi-ts", + "generate:routes": "node scripts/generate-routes-json.js", "storybook": "storybook dev -p 6006 --no-open", "build-storybook": "storybook build", "build-storybook:gh-pages": "STORYBOOK_BASE_PATH=/lab storybook build" diff --git a/routes.json b/routes.json new file mode 100644 index 000000000..0e10506f7 --- /dev/null +++ b/routes.json @@ -0,0 +1,650 @@ +{ + "generatedAt": "2026-02-01T13:33:07.132Z", + "version": "1.0.0", + "routes": [ + { + "path": "/", + "fullPath": "/", + "name": "Home", + "description": "Platform for exploring Ethereum data and network statistics.", + "parameters": [], + "parent": null, + "isIndex": true, + "id": "/", + "filePath": "index.tsx", + "hasChildren": true, + "category": "root" + }, + { + "path": "/beacon/block-production/live", + "fullPath": "/beacon/block-production/live", + "name": "Live", + "description": "", + "parameters": [], + "parent": "/beacon/block-production", + "isIndex": false, + "id": "/beacon/block-production/live", + "filePath": "beacon/block-production/live.tsx", + "hasChildren": false, + "category": "beacon" + }, + { + "path": "/beacon/locally-built-blocks", + "fullPath": "/beacon/locally-built-blocks", + "name": "Locally Built Blocks", + "description": "", + "parameters": [], + "parent": "/beacon", + "isIndex": false, + "id": "/beacon/locally-built-blocks", + "filePath": "beacon/locally-built-blocks.tsx", + "hasChildren": false, + "category": "beacon" + }, + { + "path": "/beacon/slot/live", + "fullPath": "/beacon/slot/live", + "name": "Live", + "description": "", + "parameters": [], + "parent": "/beacon/slot", + "isIndex": false, + "id": "/beacon/slot/live", + "filePath": "beacon/slot/live.tsx", + "hasChildren": false, + "category": "beacon" + }, + { + "path": "/ethereum", + "fullPath": "/ethereum", + "name": "Ethereum", + "description": "", + "parameters": [], + "parent": "/", + "isIndex": false, + "id": "/ethereum", + "filePath": "ethereum.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/contracts", + "fullPath": "/ethereum/contracts", + "name": "Contracts", + "description": "Top 100 Ethereum contracts by storage size with state expiry savings analysis.", + "parameters": [], + "parent": "/ethereum", + "isIndex": false, + "id": "/ethereum/contracts", + "filePath": "ethereum/contracts/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/", + "fullPath": "/ethereum/contracts/", + "name": "Ethereum", + "description": "Top 100 Ethereum contracts by storage size with state expiry savings analysis.", + "parameters": [], + "parent": "/ethereum", + "isIndex": true, + "id": "/ethereum/contracts/", + "filePath": "ethereum/contracts/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/$address", + "fullPath": "/ethereum/contracts/$address", + "name": "Contracts Detail", + "description": "Storage analysis for Ethereum contract", + "parameters": [ + "address" + ], + "parent": "/ethereum/contracts", + "isIndex": false, + "id": "/ethereum/contracts/$address", + "filePath": "ethereum/contracts/$address.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/data-availability", + "fullPath": "/ethereum/data-availability", + "name": "Data Availability", + "description": "", + "parameters": [], + "parent": "/ethereum", + "isIndex": false, + "id": "/ethereum/data-availability", + "filePath": "ethereum/data-availability.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/custody", + "fullPath": "/ethereum/data-availability/custody", + "name": "Custody", + "description": "PeerDAS data availability visualization showing column custody across validators", + "parameters": [], + "parent": "/ethereum/data-availability", + "isIndex": false, + "id": "/ethereum/data-availability/custody", + "filePath": "ethereum/data-availability/custody/index.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/", + "fullPath": "/ethereum/data-availability/custody/", + "name": "Data Availability", + "description": "PeerDAS data availability visualization showing column custody across validators", + "parameters": [], + "parent": "/ethereum/data-availability", + "isIndex": true, + "id": "/ethereum/data-availability/custody/", + "filePath": "ethereum/data-availability/custody/index.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/probes", + "fullPath": "/ethereum/data-availability/probes", + "name": "Probes", + "description": "PeerDAS data availability probes showing column sampling results across the network", + "parameters": [], + "parent": "/ethereum/data-availability", + "isIndex": false, + "id": "/ethereum/data-availability/probes", + "filePath": "ethereum/data-availability/probes/index.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/", + "fullPath": "/ethereum/data-availability/probes/", + "name": "Data Availability", + "description": "PeerDAS data availability probes showing column sampling results across the network", + "parameters": [], + "parent": "/ethereum/data-availability", + "isIndex": true, + "id": "/ethereum/data-availability/probes/", + "filePath": "ethereum/data-availability/probes/index.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/entities", + "fullPath": "/ethereum/entities", + "name": "Entities", + "description": "Explore Ethereum validator entities. View entity attestation performance, block proposals, and activity.", + "parameters": [], + "parent": "/ethereum", + "isIndex": false, + "id": "/ethereum/entities", + "filePath": "ethereum/entities/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/", + "fullPath": "/ethereum/entities/", + "name": "Ethereum", + "description": "Explore Ethereum validator entities. View entity attestation performance, block proposals, and activity.", + "parameters": [], + "parent": "/ethereum", + "isIndex": true, + "id": "/ethereum/entities/", + "filePath": "ethereum/entities/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/$entity", + "fullPath": "/ethereum/entities/$entity", + "name": "Entities Detail", + "description": "Detailed analysis of a validator entity including attestation performance, block proposals, and historical activity trends.", + "parameters": [ + "entity" + ], + "parent": "/ethereum/entities", + "isIndex": false, + "id": "/ethereum/entities/$entity", + "filePath": "ethereum/entities/$entity.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/epochs", + "fullPath": "/ethereum/epochs", + "name": "Epochs", + "description": "Explore Ethereum beacon chain epochs. View recent epoch data and dive into detailed epoch analytics.", + "parameters": [], + "parent": "/ethereum", + "isIndex": false, + "id": "/ethereum/epochs", + "filePath": "ethereum/epochs/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/", + "fullPath": "/ethereum/epochs/", + "name": "Ethereum", + "description": "Explore Ethereum beacon chain epochs. View recent epoch data and dive into detailed epoch analytics.", + "parameters": [], + "parent": "/ethereum", + "isIndex": true, + "id": "/ethereum/epochs/", + "filePath": "ethereum/epochs/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/$epoch", + "fullPath": "/ethereum/epochs/$epoch", + "name": "Epochs Detail", + "description": "Detailed analysis of a beacon chain epoch including attestations, block proposals, and validator performance across all slots.", + "parameters": [ + "epoch" + ], + "parent": "/ethereum/epochs", + "isIndex": false, + "id": "/ethereum/epochs/$epoch", + "filePath": "ethereum/epochs/$epoch.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/execution", + "fullPath": "/ethereum/execution", + "name": "Execution", + "description": "", + "parameters": [], + "parent": "/ethereum", + "isIndex": false, + "id": "/ethereum/execution", + "filePath": "ethereum/execution.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/overview", + "fullPath": "/ethereum/execution/overview", + "name": "Overview", + "description": "Ethereum execution layer overview with transaction throughput metrics and TPS charts", + "parameters": [], + "parent": "/ethereum/execution", + "isIndex": false, + "id": "/ethereum/execution/overview", + "filePath": "ethereum/execution/overview.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/payloads", + "fullPath": "/ethereum/execution/payloads", + "name": "Payloads", + "description": "Analyze engine_newPayload API calls to identify blocks that took abnormally long to validate", + "parameters": [], + "parent": "/ethereum/execution", + "isIndex": false, + "id": "/ethereum/execution/payloads", + "filePath": "ethereum/execution/payloads/index.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/", + "fullPath": "/ethereum/execution/payloads/", + "name": "Execution", + "description": "Analyze engine_newPayload API calls to identify blocks that took abnormally long to validate", + "parameters": [], + "parent": "/ethereum/execution", + "isIndex": true, + "id": "/ethereum/execution/payloads/", + "filePath": "ethereum/execution/payloads/index.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/state-expiry", + "fullPath": "/ethereum/execution/state-expiry", + "name": "State Expiry", + "description": "Analyze the impact of state expiry policies on Ethereum storage slots, comparing active slot counts and size savings across different inactivity periods", + "parameters": [], + "parent": "/ethereum/execution", + "isIndex": false, + "id": "/ethereum/execution/state-expiry", + "filePath": "ethereum/execution/state-expiry.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/state-growth", + "fullPath": "/ethereum/execution/state-growth", + "name": "State Growth", + "description": "Ethereum execution layer state growth visualization showing growth of accounts, storage, and contract code over time", + "parameters": [], + "parent": "/ethereum/execution", + "isIndex": false, + "id": "/ethereum/execution/state-growth", + "filePath": "ethereum/execution/state-growth.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/timings", + "fullPath": "/ethereum/execution/timings", + "name": "Timings", + "description": "Monitor CL-EL communication timing for engine_newPayload and engine_getBlobs API calls across the Ethereum network", + "parameters": [], + "parent": "/ethereum/execution", + "isIndex": false, + "id": "/ethereum/execution/timings", + "filePath": "ethereum/execution/timings/index.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/", + "fullPath": "/ethereum/execution/timings/", + "name": "Execution", + "description": "Monitor CL-EL communication timing for engine_newPayload and engine_getBlobs API calls across the Ethereum network", + "parameters": [], + "parent": "/ethereum/execution", + "isIndex": true, + "id": "/ethereum/execution/timings/", + "filePath": "ethereum/execution/timings/index.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/forks", + "fullPath": "/ethereum/forks", + "name": "Forks", + "description": "View Ethereum consensus layer fork history and upcoming network upgrades", + "parameters": [], + "parent": "/ethereum", + "isIndex": false, + "id": "/ethereum/forks", + "filePath": "ethereum/forks/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/", + "fullPath": "/ethereum/forks/", + "name": "Ethereum", + "description": "View Ethereum consensus layer fork history and upcoming network upgrades", + "parameters": [], + "parent": "/ethereum", + "isIndex": true, + "id": "/ethereum/forks/", + "filePath": "ethereum/forks/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/$fork", + "fullPath": "/ethereum/forks/$fork", + "name": "Forks Detail", + "description": "Detailed information about the Ethereum network upgrade including activation epoch, timeline, and related blob schedule changes.", + "parameters": [ + "fork" + ], + "parent": "/ethereum/forks", + "isIndex": false, + "id": "/ethereum/forks/$fork", + "filePath": "ethereum/forks/$fork.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/live", + "fullPath": "/ethereum/live", + "name": "Live", + "description": "Live visualization of Ethereum beacon chain slots with detailed block and attestation data.", + "parameters": [], + "parent": "/ethereum", + "isIndex": false, + "id": "/ethereum/live", + "filePath": "ethereum/live.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/slots", + "fullPath": "/ethereum/slots", + "name": "Slots", + "description": "Browse Ethereum consensus layer slots with detailed block, proposer, and blob information.", + "parameters": [], + "parent": "/ethereum", + "isIndex": false, + "id": "/ethereum/slots", + "filePath": "ethereum/slots/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/", + "fullPath": "/ethereum/slots/", + "name": "Ethereum", + "description": "Browse Ethereum consensus layer slots with detailed block, proposer, and blob information.", + "parameters": [], + "parent": "/ethereum", + "isIndex": true, + "id": "/ethereum/slots/", + "filePath": "ethereum/slots/index.tsx", + "hasChildren": true, + "category": "ethereum" + }, + { + "path": "/$slot", + "fullPath": "/ethereum/slots/$slot", + "name": "Slots Detail", + "description": "Detailed visualization and analysis of Ethereum consensus layer slot data including attestations, block propagation, and blob availability.", + "parameters": [ + "slot" + ], + "parent": "/ethereum/slots", + "isIndex": false, + "id": "/ethereum/slots/$slot", + "filePath": "ethereum/slots/$slot.tsx", + "hasChildren": false, + "category": "ethereum" + }, + { + "path": "/experiments", + "fullPath": "/experiments", + "name": "Experiments", + "description": "Explore visualizations and data analysis tools for Ethereum and Xatu networks.", + "parameters": [], + "parent": "/", + "isIndex": false, + "id": "/experiments", + "filePath": "experiments/index.tsx", + "hasChildren": true, + "category": "experiments" + }, + { + "path": "/", + "fullPath": "/experiments/", + "name": "Overview", + "description": "Explore visualizations and data analysis tools for Ethereum and Xatu networks.", + "parameters": [], + "parent": "/", + "isIndex": true, + "id": "/experiments/", + "filePath": "experiments/index.tsx", + "hasChildren": true, + "category": "experiments" + }, + { + "path": "/block-production-flow", + "fullPath": "/experiments/block-production-flow", + "name": "Block Production Flow", + "description": "", + "parameters": [], + "parent": "/experiments", + "isIndex": false, + "id": "/experiments/block-production-flow", + "filePath": "experiments/block-production-flow.tsx", + "hasChildren": false, + "category": "experiments" + }, + { + "path": "/live-slots", + "fullPath": "/experiments/live-slots", + "name": "Live Slots", + "description": "", + "parameters": [], + "parent": "/experiments", + "isIndex": false, + "id": "/experiments/live-slots", + "filePath": "experiments/live-slots.tsx", + "hasChildren": false, + "category": "experiments" + }, + { + "path": "/xatu", + "fullPath": "/xatu", + "name": "Xatu", + "description": "", + "parameters": [], + "parent": "/", + "isIndex": false, + "id": "/xatu", + "filePath": "xatu.tsx", + "hasChildren": true, + "category": "xatu" + }, + { + "path": "/xatu-data", + "fullPath": "/xatu-data", + "name": "Overview", + "description": "", + "parameters": [], + "parent": "/", + "isIndex": true, + "id": "/xatu-data/", + "filePath": "xatu-data/index.tsx", + "hasChildren": true, + "category": "xatu-data" + }, + { + "path": "/xatu-data/fork-readiness", + "fullPath": "/xatu-data/fork-readiness", + "name": "Fork Readiness", + "description": "", + "parameters": [], + "parent": "/xatu-data", + "isIndex": false, + "id": "/xatu-data/fork-readiness", + "filePath": "xatu-data/fork-readiness.tsx", + "hasChildren": false, + "category": "xatu-data" + }, + { + "path": "/xatu-data/geographical-checklist", + "fullPath": "/xatu-data/geographical-checklist", + "name": "Geographical Checklist", + "description": "", + "parameters": [], + "parent": "/xatu-data", + "isIndex": false, + "id": "/xatu-data/geographical-checklist", + "filePath": "xatu-data/geographical-checklist.tsx", + "hasChildren": false, + "category": "xatu-data" + }, + { + "path": "/contributors", + "fullPath": "/xatu/contributors", + "name": "Contributors", + "description": "Browse Xatu Contributoor nodes and their network participation metrics.", + "parameters": [], + "parent": "/xatu", + "isIndex": false, + "id": "/xatu/contributors", + "filePath": "xatu/contributors/index.tsx", + "hasChildren": true, + "category": "xatu" + }, + { + "path": "/", + "fullPath": "/xatu/contributors/", + "name": "Xatu", + "description": "Browse Xatu Contributoor nodes and their network participation metrics.", + "parameters": [], + "parent": "/xatu", + "isIndex": true, + "id": "/xatu/contributors/", + "filePath": "xatu/contributors/index.tsx", + "hasChildren": true, + "category": "xatu" + }, + { + "path": "/$id", + "fullPath": "/xatu/contributors/$id", + "name": "Contributors Detail", + "description": "Detailed contribution metrics and live network performance data", + "parameters": [ + "id" + ], + "parent": "/xatu/contributors", + "isIndex": false, + "id": "/xatu/contributors/$id", + "filePath": "xatu/contributors/$id.tsx", + "hasChildren": false, + "category": "xatu" + }, + { + "path": "/fork-readiness", + "fullPath": "/xatu/fork-readiness", + "name": "Fork Readiness", + "description": "Monitor Ethereum client readiness for upcoming network forks and protocol upgrades.", + "parameters": [], + "parent": "/xatu", + "isIndex": false, + "id": "/xatu/fork-readiness", + "filePath": "xatu/fork-readiness.tsx", + "hasChildren": false, + "category": "xatu" + }, + { + "path": "/geographical-checklist", + "fullPath": "/xatu/geographical-checklist", + "name": "Geographical Checklist", + "description": "Explore the global distribution of Contributoor nodes across continents, countries, and cities with interactive 3D globe visualization and detailed geographical breakdown.", + "parameters": [], + "parent": "/xatu", + "isIndex": false, + "id": "/xatu/geographical-checklist", + "filePath": "xatu/geographical-checklist.tsx", + "hasChildren": false, + "category": "xatu" + }, + { + "path": "/locally-built-blocks", + "fullPath": "/xatu/locally-built-blocks", + "name": "Locally Built Blocks", + "description": "Track and visualize blocks built locally by Contributoor nodes across the Ethereum network.", + "parameters": [], + "parent": "/xatu", + "isIndex": false, + "id": "/xatu/locally-built-blocks", + "filePath": "xatu/locally-built-blocks.tsx", + "hasChildren": false, + "category": "xatu" + } + ], + "categories": [ + "beacon", + "ethereum", + "experiments", + "root", + "xatu", + "xatu-data" + ] +} \ No newline at end of file diff --git a/scripts/generate-routes-json.js b/scripts/generate-routes-json.js new file mode 100644 index 000000000..61a2a6aec --- /dev/null +++ b/scripts/generate-routes-json.js @@ -0,0 +1,355 @@ +#!/usr/bin/env node +/** + * Routes JSON Generator + * + * This script parses the TanStack Router route tree and generates a routes.json file + * for programmatic access to Lab routes. + * + * Usage: node scripts/generate-routes-json.js + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const ROOT_DIR = path.resolve(__dirname, '..'); +const ROUTES_DIR = path.join(ROOT_DIR, 'src', 'routes'); +const ROUTE_TREE_PATH = path.join(ROOT_DIR, 'src', 'routeTree.gen.ts'); +const OUTPUT_PATH = path.join(ROOT_DIR, 'routes.json'); + +/** + * Parse route tree file to extract route metadata + */ +function parseRouteTree() { + const content = fs.readFileSync(ROUTE_TREE_PATH, 'utf-8'); + const routes = new Map(); + + // Find the FileRoutesByPath interface + const interfaceMatch = content.match(/interface FileRoutesByPath\s*\{([\s\S]*?)\n\}/); + if (!interfaceMatch) { + console.warn('Could not find FileRoutesByPath interface'); + return routes; + } + + const interfaceContent = interfaceMatch[1]; + + // Match each route entry - key is a quoted string followed by object literal + // Pattern matches: '/path': { ... } + const routePattern = /['"]([^'"]+)['"]\s*:\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/g; + let match; + + while ((match = routePattern.exec(interfaceContent)) !== null) { + const routeKey = match[1]; + const routeBody = match[2]; + + // Extract properties from the route body + const idMatch = routeBody.match(/id\s*:\s*['"]([^'"]*)['"]/); + const pathMatch = routeBody.match(/path\s*:\s*['"]([^'"]*)['"]/); + const fullPathMatch = routeBody.match(/fullPath\s*:\s*['"]([^'"]*)['"]/); + const parentMatch = routeBody.match(/parentRoute\s*:\s*typeof\s+(\w+)/); + + if (fullPathMatch) { + const fullPath = fullPathMatch[1]; + const routePath = pathMatch ? pathMatch[1] : fullPath; + const routeId = idMatch ? idMatch[1] : routeKey; + + routes.set(fullPath, { + id: routeId, + path: routePath, + fullPath: fullPath, + parent: parentMatch ? parentMatch[1] : null, + isIndex: routeId.endsWith('/'), + }); + } + } + + return routes; +} + +/** + * Extract description from a route file by looking for the content property in head.meta + */ +function extractDescription(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf-8'); + + // Look for description in head.meta array + const descMatch = content.match(/name\s*:\s*['"]description['"]\s*,\s*content\s*:\s*['"]([^'"]+)['"]/); + if (descMatch) { + return descMatch[1]; + } + + // Alternative pattern with backticks (handle multiline) + const descMatchBacktick = content.match(/name\s*:\s*['"]description['"]\s*,\s*content\s*:\s*`([^`]*)`/s); + if (descMatchBacktick) { + return descMatchBacktick[1] + .replace(/\$\{[^}]+\}/g, '') // Remove template literals + .replace(/\s+/g, ' ') // Normalize whitespace + .trim(); + } + + return ''; + } catch { + return ''; + } +} + +/** + * Generate human-readable name from route path + */ +function generateName(fullPath, isIndex) { + // Remove leading slash and split + const parts = fullPath.replace(/^\//, '').split('/').filter(Boolean); + + if (parts.length === 0) { + return 'Home'; + } + + // Get last meaningful part + const lastPart = parts[parts.length - 1]; + + // Handle index routes + if (isIndex || lastPart === '') { + if (parts.length <= 1) { + return 'Overview'; + } + const parentPart = parts[parts.length - 2]; + return formatSegment(parentPart); + } + + // Handle parameter routes + if (lastPart.startsWith('$')) { + const paramName = lastPart.slice(1); + const parentPart = parts.length > 1 ? parts[parts.length - 2] : ''; + if (parentPart) { + return `${formatSegment(parentPart)} Detail`; + } + return `${formatSegment(paramName)} Detail`; + } + + return formatSegment(lastPart); +} + +/** + * Format a route segment into a human-readable name + */ +function formatSegment(segment) { + // Handle hyphenated names + const words = segment.split('-').map(word => { + // Capitalize first letter + return word.charAt(0).toUpperCase() + word.slice(1); + }); + + return words.join(' '); +} + +/** + * Extract parameters from a route path + */ +function extractParameters(routePath) { + const params = []; + const parts = routePath.split('/'); + + for (const part of parts) { + if (part.startsWith('$')) { + params.push(part.slice(1)); + } + } + + return params; +} + +/** + * Get category from route path + */ +function getCategory(fullPath) { + const parts = fullPath.replace(/^\//, '').split('/').filter(Boolean); + return parts.length > 0 ? parts[0] : 'root'; +} + +/** + * Find the actual file path for a route + */ +function findRouteFilePath(fullPath, isIndex, routeId) { + // Convert fullPath to file path + let filePath = fullPath; + + // Remove leading slash + filePath = filePath.replace(/^\//, ''); + + // Handle index routes + if (isIndex || fullPath.endsWith('/')) { + // For index routes, look for index.tsx in the directory + const dirPath = path.join(ROUTES_DIR, filePath.replace(/\/$/, '')); + if (fs.existsSync(path.join(dirPath, 'index.tsx'))) { + return path.join(filePath.replace(/\/$/, ''), 'index.tsx'); + } + } + + // Handle parameter routes - replace $param with $param.tsx + const parts = filePath.split('/'); + const fileName = parts[parts.length - 1]; + + if (fileName.startsWith('$')) { + return `${filePath}.tsx`; + } + + // Check if it's a directory with an index + const dirPath = path.join(ROUTES_DIR, filePath); + if (fs.existsSync(path.join(dirPath, 'index.tsx'))) { + return path.join(filePath, 'index.tsx'); + } + + // Try .tsx extension + if (fs.existsSync(path.join(ROUTES_DIR, `${filePath}.tsx`))) { + return `${filePath}.tsx`; + } + + // Try to reconstruct from routeId + if (routeId && routeId !== '/') { + const idParts = routeId.replace(/^\//, '').split('/').filter(Boolean); + if (idParts.length > 0) { + const reconstructed = idParts.join('/'); + if (fs.existsSync(path.join(ROUTES_DIR, `${reconstructed}.tsx`))) { + return `${reconstructed}.tsx`; + } + if (fs.existsSync(path.join(ROUTES_DIR, reconstructed, 'index.tsx'))) { + return path.join(reconstructed, 'index.tsx'); + } + } + } + + return filePath; +} + +/** + * Build parent route reference from fullPath + */ +function buildParentPath(fullPath, isIndex) { + const parts = fullPath.replace(/^\//, '').split('/').filter(Boolean); + + if (parts.length === 0) { + return null; + } + + // Remove last part + parts.pop(); + + if (parts.length === 0) { + return '/'; // Root is parent + } + + // Build parent path + const parentPath = '/' + parts.join('/'); + + return parentPath; +} + +/** + * Check if a route has children by looking at other routes + */ +function hasChildren(fullPath, allPaths) { + // Normalize path (ensure it doesn't end with / for comparison) + const normalizedPath = fullPath.replace(/\/$/, ''); + + for (const otherPath of allPaths) { + if (otherPath === fullPath) continue; + + // Check if other path starts with this path + / + const otherNormalized = otherPath.replace(/\/$/, ''); + if (otherNormalized.startsWith(normalizedPath + '/') && otherNormalized !== normalizedPath) { + return true; + } + } + + return false; +} + +/** + * Main function to generate routes.json + */ +function generateRoutes() { + console.log('🔍 Parsing route tree...'); + + const routeTreeRoutes = parseRouteTree(); + const routes = []; + const categories = new Set(); + + // Get all full paths + const allFullPaths = Array.from(routeTreeRoutes.keys()); + + console.log(` Found ${allFullPaths.length} routes in route tree`); + + for (const [fullPath, routeInfo] of routeTreeRoutes) { + if (!routeInfo.fullPath) continue; + + const isIndex = routeInfo.isIndex || fullPath.endsWith('/'); + const routePath = routeInfo.path || fullPath; + const filePath = findRouteFilePath(fullPath, isIndex, routeInfo.id); + const absoluteFilePath = path.join(ROUTES_DIR, filePath); + const description = extractDescription(absoluteFilePath); + const category = getCategory(fullPath); + categories.add(category); + + // Build parent path + let parentPath = null; + if (routeInfo.id && routeInfo.id !== '__root__' && routeInfo.id !== '/') { + parentPath = buildParentPath(fullPath, isIndex); + } + + const route = { + path: routePath, + fullPath: fullPath, + name: generateName(fullPath, isIndex), + description: description, + parameters: extractParameters(routePath), + parent: parentPath, + isIndex: isIndex, + id: routeInfo.id || fullPath, + filePath: filePath, + hasChildren: hasChildren(fullPath, allFullPaths), + category: category, + }; + + routes.push(route); + } + + // Sort routes by fullPath for consistency + routes.sort((a, b) => a.fullPath.localeCompare(b.fullPath)); + + const output = { + generatedAt: new Date().toISOString(), + version: '1.0.0', + routes: routes, + categories: Array.from(categories).sort(), + }; + + // Write output + fs.writeFileSync(OUTPUT_PATH, JSON.stringify(output, null, 2)); + + console.log(`✅ Generated routes.json with ${routes.length} routes`); + console.log(`📁 Output: ${OUTPUT_PATH}`); + console.log(`📂 Categories: ${Array.from(categories).sort().join(', ')}`); + + // Print some statistics + const paramRoutes = routes.filter(r => r.parameters.length > 0); + console.log(`🔢 Parameterized routes: ${paramRoutes.length}`); + + if (paramRoutes.length > 0) { + console.log(' Examples:'); + paramRoutes.slice(0, 5).forEach(r => { + console.log(` - ${r.fullPath} (${r.parameters.join(', ')})`); + }); + } +} + +// Run the generator +try { + generateRoutes(); +} catch (error) { + console.error('❌ Error generating routes:', error); + process.exit(1); +}