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
2.6 KiB
JavaScript
86 lines
2.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Ensure app/beauty routes remain wired and URLs are preserved. */
|
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
import { join, relative, sep } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const ROOT = join(fileURLToPath(new URL(".", import.meta.url)), "..");
|
|
const BEAUTY_APP = join(ROOT, "app", "beauty");
|
|
|
|
const REQUIRED_LAYOUTS = [
|
|
"layout.tsx",
|
|
"admin/layout.tsx",
|
|
"customer/layout.tsx",
|
|
"owner/layout.tsx",
|
|
"(owner)/layout.tsx",
|
|
"reception/layout.tsx",
|
|
"staff-portal/layout.tsx",
|
|
"site/layout.tsx",
|
|
];
|
|
|
|
/** @type {string[]} */
|
|
const issues = [];
|
|
|
|
function walkPages(dir, pages = []) {
|
|
for (const entry of readdirSync(dir)) {
|
|
const full = join(dir, entry);
|
|
if (statSync(full).isDirectory()) walkPages(full, pages);
|
|
else if (entry === "page.tsx") pages.push(full);
|
|
}
|
|
return pages;
|
|
}
|
|
|
|
if (!existsSync(BEAUTY_APP)) {
|
|
console.error("❌ app/beauty missing");
|
|
process.exit(1);
|
|
}
|
|
|
|
for (const layout of REQUIRED_LAYOUTS) {
|
|
const p = join(BEAUTY_APP, layout);
|
|
if (!existsSync(p)) issues.push(`Missing layout: app/beauty/${layout}`);
|
|
}
|
|
|
|
const pages = walkPages(BEAUTY_APP);
|
|
if (pages.length < 90) {
|
|
issues.push(`Expected ~100 beauty pages, found ${pages.length}`);
|
|
}
|
|
|
|
for (const page of pages) {
|
|
const rel = relative(ROOT, page).split(sep).join("/");
|
|
const content = readFileSync(page, "utf8");
|
|
if (!/export\s+(default|\{)/.test(content)) {
|
|
issues.push(`${rel}: no default export`);
|
|
}
|
|
// Route files should not contain heavy business logic (>80 lines)
|
|
const lines = content.split("\n").length;
|
|
if (lines > 80 && !rel.includes("(owner)")) {
|
|
// owner routes recreated as thin - warn only for non-owner
|
|
if (lines > 15 && !content.includes("@/modules/beauty")) {
|
|
issues.push(`${rel}: ${lines} lines — route should re-export from modules/beauty`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hub entry redirect
|
|
const rootPage = join(BEAUTY_APP, "page.tsx");
|
|
if (existsSync(rootPage)) {
|
|
const c = readFileSync(rootPage, "utf8");
|
|
if (!c.includes("/beauty/hub") && !c.includes("redirect")) {
|
|
issues.push("app/beauty/page.tsx should redirect to hub");
|
|
}
|
|
}
|
|
|
|
console.log("Beauty Route Validation\n");
|
|
console.log(`Pages: ${pages.length}`);
|
|
console.log(`Layouts checked: ${REQUIRED_LAYOUTS.length}`);
|
|
|
|
if (issues.length) {
|
|
console.error(`\n❌ ${issues.length} issue(s):`);
|
|
for (const i of issues) console.error(` - ${i}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("\n✅ All beauty routes and layouts present");
|
|
console.log("✅ Route files are thin adapters");
|
|
process.exit(0);
|