Move business logic out of App Router into module packages, add boundary validation scripts, and keep all routes as thin re-exports without changing URLs or API behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
86 lines
3.1 KiB
JavaScript
86 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Move app/accounting page logic to src/modules/accounting/pages and leave thin route adapters. */
|
|
import {
|
|
readFileSync,
|
|
writeFileSync,
|
|
mkdirSync,
|
|
readdirSync,
|
|
statSync,
|
|
} from "node:fs";
|
|
import { join, dirname, relative, sep } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "..");
|
|
const APP = join(ROOT, "app", "accounting");
|
|
const PAGES = join(ROOT, "src", "modules", "accounting", "pages");
|
|
|
|
function walkPages(dir, acc = []) {
|
|
for (const entry of readdirSync(dir)) {
|
|
const full = join(dir, entry);
|
|
if (statSync(full).isDirectory()) walkPages(full, acc);
|
|
else if (entry === "page.tsx") acc.push(full);
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
function routeToExportName(routeRel) {
|
|
const base = routeRel.replace(/\/page\.tsx$/, "").replace(/^\.\/?/, "");
|
|
if (!base || base === "page.tsx") return "AccountingDashboardPage";
|
|
const segments = base.split("/").map((part) => {
|
|
if (part.startsWith("[") && part.endsWith("]")) {
|
|
const inner = part.slice(1, -1);
|
|
return inner.charAt(0).toUpperCase() + inner.slice(1);
|
|
}
|
|
return part
|
|
.split("-")
|
|
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
.join("");
|
|
});
|
|
return `${segments.join("")}Page`;
|
|
}
|
|
|
|
function moduleRelFromRoute(routePagePath) {
|
|
const relDir = relative(APP, dirname(routePagePath)).split(sep).join("/");
|
|
if (!relDir || relDir === ".") return "dashboard";
|
|
return relDir.replace(/\[(\w+)\]/g, "$1");
|
|
}
|
|
|
|
function convertToNamedExport(content, exportName) {
|
|
if (/export default function \w+/.test(content)) {
|
|
return content.replace(/export default function (\w+)/, "export function $1");
|
|
}
|
|
if (/export default function Page/.test(content)) {
|
|
return content.replace(/export default function Page/, `export function ${exportName}`);
|
|
}
|
|
if (/export default function/.test(content)) {
|
|
return content.replace(/export default function/, "export function");
|
|
}
|
|
return `${content}\nexport { ${exportName} };\n`;
|
|
}
|
|
|
|
function resolveExportName(content, fallback) {
|
|
const named = content.match(/export default function (\w+)/);
|
|
if (named && named[1] !== "Page") return named[1];
|
|
return fallback;
|
|
}
|
|
|
|
let moved = 0;
|
|
for (const pageFile of walkPages(APP)) {
|
|
const routeRel = relative(APP, pageFile).split(sep).join("/");
|
|
const moduleRel = moduleRelFromRoute(pageFile);
|
|
const fallbackName = routeToExportName(routeRel);
|
|
const original = readFileSync(pageFile, "utf8");
|
|
const exportName = resolveExportName(original, fallbackName);
|
|
const moduleContent = convertToNamedExport(original, exportName);
|
|
const targetPath = join(PAGES, `${moduleRel}.tsx`);
|
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
writeFileSync(targetPath, moduleContent, "utf8");
|
|
|
|
const importPath = `@/src/modules/accounting/pages/${moduleRel}`;
|
|
const thin = `"use client";\n\nexport { ${exportName} as default } from "${importPath}";\n`;
|
|
writeFileSync(pageFile, thin, "utf8");
|
|
moved++;
|
|
}
|
|
|
|
console.log(`Migrated ${moved} accounting pages to src/modules/accounting/pages/`);
|