/** * Generates src/lib/viewRouteMeta.generated.ts from discovered PHP view files. * Run: node scripts/gen-view-route-meta.mjs */ import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const projectRoot = path.resolve(__dirname, '..') const parentRoot = path.resolve(projectRoot, '..') const outFile = path.resolve(projectRoot, 'src/lib/viewRouteMeta.generated.ts') const SKIP_DIRS = new Set([ 'layout', 'partials', 'errors', 'pagination', 'docs', ]) function humanize(slug) { const normalized = slug.replace(/(\.blade)?\.php$/i, '').replace(/_/g, ' ') return normalized.replace(/\b\w/g, (char) => char.toUpperCase()) } function walk(dir, baseRel) { const out = [] let entries = [] try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return out } for (const entry of entries) { const rel = path.join(baseRel, entry.name) const full = path.join(dir, entry.name) if (entry.isDirectory()) { if (SKIP_DIRS.has(entry.name)) continue out.push(...walk(full, rel)) continue } if (entry.isFile() && /\.php$/i.test(entry.name)) { out.push(rel.replace(/\\/g, '/')) } } return out } function discoverViewRoots() { const roots = [] const fromEnv = process.env.VIEW_SOURCE_ROOT?.trim() if (fromEnv) { roots.push(path.resolve(projectRoot, fromEnv)) } let siblings = [] try { siblings = fs.readdirSync(parentRoot, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => path.join(parentRoot, entry.name)) } catch { siblings = [] } for (const sibling of siblings) { roots.push(path.join(sibling, 'app', 'Views')) roots.push(path.join(sibling, 'resources', 'views')) } return [...new Set(roots)].filter((root) => fs.existsSync(root)) } function resolveViewsRoot() { let best = null let bestCount = -1 for (const candidate of discoverViewRoots()) { const count = walk(candidate, '').length if (count > bestCount) { best = candidate bestCount = count } } return best } function main() { const viewsRoot = resolveViewsRoot() if (!viewsRoot) { console.error('Missing PHP view source folder. Set VIEW_SOURCE_ROOT to override discovery.') process.exit(1) } const files = walk(viewsRoot, '') .filter((file) => !file.includes('/layout/')) .sort() const entries = [] for (const file of files) { const parts = file.split('/') const base = parts.pop().replace(/(\.blade)?\.php$/i, '') const routeKey = [...parts, base].filter(Boolean).join('/') const sourceViewPath = file const title = humanize(base) entries.push({ routeKey, title, sourceViewPath }) } const lines = [] lines.push('/** Auto-generated by scripts/gen-view-route-meta.mjs — do not edit by hand. */') lines.push('') lines.push('export type ViewRouteMetaEntry = {') lines.push(' title: string') lines.push(' sourceViewPath: string') lines.push('}') lines.push('') lines.push('export const viewRouteMeta: Record = {') for (const { routeKey, title, sourceViewPath } of entries) { const safeTitle = title.replace(/\\/g, '\\\\').replace(/'/g, "\\'") const safeView = sourceViewPath.replace(/\\/g, '/') lines.push(` '${routeKey}': { title: '${safeTitle}', sourceViewPath: '${safeView}' },`) } lines.push('}') lines.push('') fs.mkdirSync(path.dirname(outFile), { recursive: true }) fs.writeFileSync(outFile, lines.join('\n'), 'utf8') console.log(`Wrote ${entries.length} entries → ${path.relative(process.cwd(), outFile)}`) } main()