import { readdirSync, statSync, writeFileSync } from "fs"; import _ from "lodash"; import { basename, extname, join, relative } from "path"; const PAGES_DIR = join(process.cwd(), "src/pages"); const OUTPUT_FILE = join(process.cwd(), "src/AppRoutes.tsx"); // 🧩 Ubah nama file ke nama komponen (PascalCase) const toComponentName = (fileName: string) => fileName .replace(/_/g, " ") .replace(/\b\w/g, (c) => c.toUpperCase()) .replace(/\s/g, ""); // 🧩 Ubah nama file ke path route function toRoutePath(name: string): string { if (name.toLowerCase() === "home") return "/"; if (name.toLowerCase() === "login") return "/login"; if (name.toLowerCase() === "notfound") return "/*"; if (name.endsWith("_page")) return name.replace("_page", "").toLowerCase(); if (name.startsWith("form_")) return name.replace("form_", "").toLowerCase(); return name.toLowerCase(); } // 🧭 Scan folder pages secara rekursif function scan(dir: string): any[] { const items = readdirSync(dir); const routes: any[] = []; for (const item of items) { const full = join(dir, item); const stat = statSync(full); if (stat.isDirectory()) { routes.push({ name: item, path: item.toLowerCase(), children: scan(full), }); } else if (extname(item) === ".tsx") { routes.push({ name: basename(item, ".tsx"), filePath: relative(join(process.cwd(), "src"), full).replace(/\\/g, "/"), }); } } return routes; } // 🏗️ Generate JSX dari struktur folder function generateJSX(routes: any[], parentPath = ""): string { let jsx = ""; for (const route of routes) { if (route.children) { // cari layout di folder const layout = route.children.find((r: any) => r.name.endsWith("_layout")); if (layout) { const LayoutComponent = toComponentName(layout.name.replace("_layout", "Layout")); const nested = route.children.filter((r: any) => r !== layout); const nestedRoutes = generateJSX(nested, `${parentPath}/${route.path}`); jsx += ` }> ${nestedRoutes} `; } else { jsx += generateJSX(route.children, `${parentPath}/${route.path}`); } } else { const Component = toComponentName(route.name); const routePath = toRoutePath(route.name); // Hapus duplikasi segmen const fullPath = routePath.startsWith("/") ? routePath : `${parentPath}/${_.kebabCase(routePath)}`.replace(/\/+/g, "/"); jsx += `} />\n`; } } return jsx; } // 🧾 Generate import otomatis function generateImports(routes: any[]): string { const imports = new Set(); function collect(rs: any[]) { for (const r of rs) { if (r.children) collect(r.children); else { const Comp = toComponentName(r.name); imports.add(`import ${Comp} from "./${r.filePath.replace(/\.tsx$/, "")}";`); } } } collect(routes); return Array.from(imports).join("\n"); } // 🧠 Main generator const allRoutes = scan(PAGES_DIR); const imports = generateImports(allRoutes); const jsxRoutes = generateJSX(allRoutes); const finalCode = ` // ⚡ Auto-generated by generateRoutes.ts — DO NOT EDIT MANUALLY import { BrowserRouter, Routes, Route } from "react-router-dom"; import ProtectedRoute from "./components/ProtectedRoute"; ${imports} export default function AppRoutes() { return ( ${jsxRoutes} ); } `; writeFileSync(OUTPUT_FILE, finalCode); console.log("✅ Routes generated successfully → src/AppRoutes.generated.tsx"); Bun.spawnSync(["bunx", "prettier", "--write", "src/**/*.tsx"])