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