everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
100 lines (98 loc) • 3.58 kB
JavaScript
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