UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

100 lines (98 loc) • 3.58 kB
import { Finding, KNOWN_MOUNTS, MOUNT_ALIASES } from "../domain.mjs"; import { listFiles, readText } from "../fs.mjs"; import { Effect } from "effect"; //#region src/services/scaffold/upgrade-audit/checks/routes.ts const ROUTE_FILE_RE = /createFileRoute(?:<[^>]*>)?\(\s*["'`]([^"'`]+)["'`]/; const AUTHED_MOUNTS = /* @__PURE__ */ new Set([ "authenticated", "admin", "organization" ]); const SESSION_CALLEES = ["authClient.getSession(", "sessionQueryOptions("]; const firstSegment = (routePath) => { const segments = routePath.split("/").filter(Boolean); return segments.length > 0 ? segments[0] : null; }; const isLayoutPath = (routePath) => routePath.split("/").filter(Boolean).length === 1; const parseRoutePath = (source) => { return source.match(ROUTE_FILE_RE)?.[1] ?? null; }; const checkRoot = (relativePath, source) => { const findings = []; if (!/<html[\s>]/.test(source)) { findings.push(new Finding({ check: "routes", path: relativePath, severity: "warn", message: "root route has no document shell — already thin, graft-ready" })); return findings; } findings.push(new Finding({ check: "routes", path: relativePath, severity: "warn", message: "root route owns the document shell (<html>) — acceptable today, must become thin (<Outlet />) before the app.ts/grafting model" })); return findings; }; const checkRouteFile = (relativePath, source) => { if (relativePath.endsWith("__root.tsx")) return checkRoot(relativePath, source); const routePath = parseRoutePath(source); if (!routePath) return [new Finding({ check: "routes", path: relativePath, severity: "blocker", message: "no createFileRoute() call with a path literal found" })]; const segment = firstSegment(routePath); if (!segment?.startsWith("_")) return [new Finding({ check: "routes", path: relativePath, severity: "blocker", message: `route "/${routePath.replace(/^\//, "")}" is not mounted under a pathless layout — re-home under _<mount>` })]; const mountId = segment.slice(1); const canonical = MOUNT_ALIASES[mountId] ?? mountId; const findings = []; if (!KNOWN_MOUNTS.includes(canonical)) { findings.push(new Finding({ check: "routes", path: relativePath, severity: "blocker", message: `unknown mount "_${mountId}" — not in the host mount registry` })); return findings; } if (isLayoutPath(routePath) && AUTHED_MOUNTS.has(canonical) && !/beforeLoad\s*[:(]/.test(source)) findings.push(new Finding({ check: "routes", path: relativePath, severity: "blocker", message: `authed layout "_${mountId}" has no beforeLoad gate — session checks must live on the mount` })); if (!isLayoutPath(routePath) && SESSION_CALLEES.some((callee) => source.includes(callee))) findings.push(new Finding({ check: "routes", path: relativePath, severity: "warn", message: "leaf route resolves the session directly — inherit the mount gate instead" })); return findings; }; const checkRoutes = (projectDir) => Effect.gen(function* () { const files = (yield* listFiles(projectDir, "ui/src/routes/**/*.{ts,tsx}")).filter((file) => !file.endsWith(".test.ts")); if (files.length === 0) return [new Finding({ check: "routes", path: "ui/src/routes", severity: "blocker", message: "no route files found — is this a child app with UI overrides?" })]; const findings = []; for (const file of files) { const source = yield* readText(projectDir, file); findings.push(...checkRouteFile(file, source)); } return findings; }); //#endregion export { checkRoutes }; //# sourceMappingURL=routes.mjs.map