UNPKG

everything-dev

Version:

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

193 lines (191 loc) • 7.96 kB
const require_fs = require('../fs.cjs'); const require_ui_seam = require('../checks/ui-seam.cjs'); let effect = require("effect"); //#region src/services/scaffold/upgrade-audit/steps/migrate-ui-seam.ts const ROUTER_PATH = "ui/src/router.tsx"; const ROUTER_CLIENT_PATH = "ui/src/router-client.ts"; const TSCONFIG_PATH = "ui/tsconfig.json"; const CREATE_QUERY_CLIENT_RE = /export\s+function\s+createQueryClient\s*\(\s*\)\s*:\s*QueryClient\s*\{/; const DEFAULT_COMPONENT_MARKERS = [ ["defaultErrorComponent", "Oops!"], ["defaultNotFoundComponent", "Not Found"], ["defaultPendingComponent", "Loading..."] ]; const extractBlock = (source, openIdx) => { let depth = 0; for (let i = openIdx; i < source.length; i++) { const ch = source[i]; if (ch === "{") depth++; else if (ch === "}") { depth--; if (depth === 0) return source.slice(openIdx, i + 1); } } return null; }; const extractCreateQueryClient = (source) => { const match = source.match(CREATE_QUERY_CLIENT_RE); if (!match || match.index === void 0) return null; const openIdx = source.indexOf("{", match.index + match[0].length - 1); if (openIdx === -1) return null; const block = extractBlock(source, openIdx); if (!block) return null; return source.slice(match.index, openIdx + block.length).trimEnd(); }; const customComponentNotes = (source) => { const notes = []; for (const [name, marker] of DEFAULT_COMPONENT_MARKERS) if (new RegExp(`function\\s+${name}\\s*\\(`).test(source) && !source.includes(marker)) notes.push(`custom ${name} detected — the core router hardcodes its defaults until router-client spreads opts; re-apply it after the spread on a framework version that supports it`); return notes; }; const renderAuthoredRouter = (queryClientFn) => [ "/**", " * Client router — the app's authored router factory, the single customization", " * seam for router policy (notFound/pending/error components, scroll behavior,", " * query timings via the `createQueryClient` export). Minted through the", " * framework's client factory; the composed manifest tree arrives at call", " * time and wins over the bundled core-only tree.", " *", " * This file is yours — scaffolded once, never overwritten by `bos sync`", " * (ADR 0023). Edit the policy defaults after the `...opts` spread freely.", " */", "", ...queryClientFn ? ["import { QueryClient } from \"@tanstack/react-query\";"] : [], "import { createRouter as createCoreRouter } from \"everything-dev/ui/router-client\";", "import type { ApiClient, CreateRouterOptions, SessionData } from \"./app\";", "import { routeTree } from \"./routeTree.gen\";", "", "export type {", " ClientRuntimeConfig,", " CreateRouterOptions,", " RouterContext,", " RouterModule,", "} from \"./app\";", "", "export function createRouter(opts: CreateRouterOptions) {", " return createCoreRouter<ApiClient, SessionData, typeof routeTree>({", " ...opts,", " defaultRouteTree: routeTree,", " });", "}", ...queryClientFn ? ["", queryClientFn] : [], "", "export { routeTree };", "", "declare module \"@tanstack/react-router\" {", " interface Register {", " router: ReturnType<typeof createRouter>[\"router\"];", " }", "}", "" ].join("\n"); const renderRouterClientShim = () => [ "/**", " * LOCAL SHIM — stand-in for `everything-dev/ui/router-client`.", " *", " * Why this exists: the pinned framework release predates the v2 train and", " * does not publish `ui/router-client`. This shim lets the authored router", " * seam (router.tsx importing createCoreRouter from", " * `everything-dev/ui/router-client`) resolve and typecheck regardless.", " *", " * Upstream canonical shape to conform to:", " * createCoreRouter<ApiClient, SessionData, typeof routeTree>({", " ...opts,", " defaultRouteTree: routeTree,", " })", " * Conformance rules this shim must keep:", " * 1. Accept `defaultRouteTree` and forward it as `routeTree`.", " * 2. Spread caller opts LAST so overrides win over shim defaults.", " * 3. Preserve the <TApiClient, TSession, TRouteTree> generic signature.", " *", " * Detecting native availability (upgrade off this shim):", " * ls node_modules/everything-dev/dist/ui/router-client.d.mts", " * v2 releases of everything-dev publish that module.", " *", " * Exit path (once on a framework version that publishes ui/router-client):", " * 1. Delete this file.", " * 2. Remove the \"everything-dev/ui/router-client\" entry from", " * ui/tsconfig.json compilerOptions.paths.", " * 3. Run `pnpm run typecheck` — resolution must fall through to the", " * framework module with no type errors.", " *", " * NOTE: the upgrade audit classifies router.tsx as authored-seam by its", " * import string, so re-running `bos upgrade` will never revisit this", " * shim — migration off it is manual by design.", " */", "", "import { createRouter as createTanStackRouter } from \"@tanstack/react-router\";", "import type { CreateRouterOptions } from \"@tanstack/react-router\";", "", "export function createCoreRouter<TApiClient, TSession, TRouteTree extends object>(", " opts: Omit<CreateRouterOptions<TApiClient, TSession>, \"routeTree\"> & {", " defaultRouteTree?: TRouteTree;", " },", ") {", " return createTanStackRouter({", " ...opts,", " ...(opts.defaultRouteTree ? { routeTree: opts.defaultRouteTree } : {}),", " });", "}", "" ].join("\n"); const renderTsconfigPathMapping = (existing) => { const parsed = JSON.parse(existing); const paths = parsed.compilerOptions?.paths ?? {}; if (paths["everything-dev/ui/router-client"]) return existing; paths["everything-dev/ui/router-client"] = ["./src/router-client"]; parsed.compilerOptions.paths = paths; return `${JSON.stringify(parsed, null, 2)}\n`; }; const migrateUiSeam = (projectDir, dryRun) => effect.Effect.gen(function* () { if (!(yield* require_fs.fileExists(projectDir, ROUTER_PATH))) return { writes: [], deletes: [], notes: ["ui/src/router.tsx absent — nothing to migrate"] }; const source = yield* require_fs.readText(projectDir, ROUTER_PATH); const seam = require_ui_seam.detectRouterSeam(source); if (seam === "authored-seam") return { writes: [], deletes: [], notes: ["router.tsx already on the authored seam"] }; if (seam === "unknown") return { writes: [], deletes: [], notes: ["router.tsx is not the known monolithic factory — left untouched, inspect manually"] }; const queryClientFn = extractCreateQueryClient(source); const next = renderAuthoredRouter(queryClientFn); if (!dryRun) yield* require_fs.writeText(projectDir, ROUTER_PATH, next); const shimExists = yield* require_fs.fileExists(projectDir, ROUTER_CLIENT_PATH); let shimWritten = false; if (!shimExists) { if (!dryRun) yield* require_fs.writeText(projectDir, ROUTER_CLIENT_PATH, renderRouterClientShim()); shimWritten = true; } let tsconfigWritten = false; if (yield* require_fs.fileExists(projectDir, TSCONFIG_PATH)) { const tsconfigSource = yield* require_fs.readText(projectDir, TSCONFIG_PATH); const nextTsconfig = renderTsconfigPathMapping(tsconfigSource); if (nextTsconfig !== tsconfigSource && !dryRun) { yield* require_fs.writeText(projectDir, TSCONFIG_PATH, nextTsconfig); tsconfigWritten = true; } } const allWrites = [ROUTER_PATH]; if (shimWritten) allWrites.push(ROUTER_CLIENT_PATH); if (tsconfigWritten) allWrites.push(TSCONFIG_PATH); return { writes: allWrites, deletes: [], notes: [ "router factory migrated to the createCoreRouter authored seam (ADR 0023) — the composed manifest tree now wins over the bundled core-only tree", ...queryClientFn ? [] : ["no createQueryClient found in the old factory — the core default query timings apply"], ...customComponentNotes(source) ] }; }); //#endregion exports.extractCreateQueryClient = extractCreateQueryClient; exports.migrateUiSeam = migrateUiSeam; //# sourceMappingURL=migrate-ui-seam.cjs.map