everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
1 lines • 13 kB
Source Map (JSON)
{"version":3,"file":"migrate-route-mounts.cjs","names":["MOUNT_ALIASES","KNOWN_MOUNTS","UPSTREAM_REF","Effect","listFiles","readText","fileExists","writeText","removePath"],"sources":["../../../../../src/services/scaffold/upgrade-audit/steps/migrate-route-mounts.ts"],"sourcesContent":["import { Effect } from \"effect\";\nimport { KNOWN_MOUNTS, MOUNT_ALIASES } from \"../domain\";\nimport { fileExists, listFiles, readText, removePath, writeText } from \"../fs\";\nimport { UPSTREAM_REF } from \"../upstream-ref\";\nimport type { StepResult } from \"./author-config\";\n\nconst ROUTES_GLOB = \"ui/src/routes/**/*.{ts,tsx}\";\nconst ROUTE_FILE_RE = /createFileRoute(?:<[^>]*>)?\\(\\s*[\"'`]([^\"'`]+)[\"'`]/;\n\n/**\n * Unknown mount id → registered mount id. Child apps commonly root their UI\n * under `_layout` (v1 convention) which is not in the host mount registry.\n * Map it to `_public` by default; individual entries can be overridden.\n */\nconst MOUNT_REDIRECTS: Record<string, string> = {\n layout: \"public\",\n};\n\nconst mountIdOf = (routePath: string): string | null => {\n const segments = routePath.split(\"/\").filter(Boolean);\n if (segments.length === 0) return null;\n const first = segments[0];\n if (!first.startsWith(\"_\")) return null;\n return first.slice(1);\n};\n\nconst canonicalMount = (mountId: string): string => MOUNT_ALIASES[mountId] ?? mountId;\n\nconst isRegistered = (mountId: string): boolean =>\n KNOWN_MOUNTS.includes(mountId as (typeof KNOWN_MOUNTS)[number]);\n\nconst mountLayoutFile = (mountId: string): string => `ui/src/routes/_${mountId}.tsx`;\n\nconst renderMountLayout = (mountId: string): string => {\n const canonical = canonicalMount(mountId);\n const needsGate = canonical === \"authenticated\";\n return [\n \"/**\",\n \" * GENERATED mount layout — a minimal Outlet shell (plus the requireSession\",\n \" * gate on authed mounts) so rehomed routes sit on a registered mount.\",\n \" *\",\n ` * Upstream canonical shapes live in everything-dev's`,\n ` * upgrade-audit fixtures/upstream/routes/ at UPSTREAM_REF ${UPSTREAM_REF}`,\n \" * — conform to those when adding chrome (headers, session context) to\",\n \" * this mount. Chrome from the old layout was NOT carried over; port it\",\n \" * manually.\",\n \" */\",\n \"\",\n `import { createFileRoute, Outlet${needsGate ? \", redirect\" : \"\"} } from \"@tanstack/react-router\";`,\n needsGate ? `import { requireSession } from \"@/lib/auth\";` : \"\",\n \"\",\n \"export const Route = createFileRoute(\",\n ` \"/_${canonical}\",`,\n needsGate ? \" { beforeLoad: requireSession, component: Outlet },\" : \" { component: Outlet },\",\n \"});\",\n \"\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n};\n\nconst rehomeRoute = (\n file: string,\n source: string,\n targetMount: string,\n): { next: string; newFile: string } | null => {\n const match = source.match(ROUTE_FILE_RE);\n if (!match) return null;\n const oldPath = match[1];\n const segments = oldPath.split(\"/\").filter(Boolean);\n if (segments.length === 0) return null;\n // segments[0] is the old mount (e.g. \"_layout\")\n const oldMountDir = `_${segments[0].slice(1)}`;\n\n // Build the new route path: drop the old mount segment. If the next segment\n // is itself a registered mount (e.g. /_layout/_authenticated/dashboard),\n // it becomes the new mount; otherwise prepend the target mount.\n let newPathSegments: string[];\n if (segments.length >= 2 && segments[1].startsWith(\"_\")) {\n const segMountId = segments[1].slice(1);\n const canonical = canonicalMount(segMountId);\n if (KNOWN_MOUNTS.includes(canonical as (typeof KNOWN_MOUNTS)[number])) {\n newPathSegments = [`_${canonical}`, ...segments.slice(2)];\n } else {\n newPathSegments = [`_${targetMount}`, ...segments.slice(1)];\n }\n } else {\n newPathSegments = [`_${targetMount}`, ...segments.slice(1)];\n }\n const newPath =\n \"/\" +\n newPathSegments.join(\"/\") +\n (oldPath.endsWith(\"/\") && newPathSegments.length >= 1 ? \"/\" : \"\");\n const next = source.replace(match[0], match[0].replace(oldPath, newPath));\n\n // New file location: drop the old mount directory. If the next directory is\n // already the right mount, keep it; otherwise insert the target mount dir.\n const parts = file.split(\"/\");\n const mountIdx = parts.indexOf(oldMountDir);\n if (mountIdx === -1) return null;\n\n let insertTargetMount = true;\n if (mountIdx + 1 < parts.length) {\n const nextSeg = parts[mountIdx + 1];\n if (nextSeg.startsWith(\"_\")) {\n const nextMountId = nextSeg.slice(1);\n const canonical = canonicalMount(nextMountId);\n if (KNOWN_MOUNTS.includes(canonical as (typeof KNOWN_MOUNTS)[number])) {\n insertTargetMount = false;\n }\n }\n }\n\n const newParts = insertTargetMount\n ? [...parts.slice(0, mountIdx), `_${targetMount}`, ...parts.slice(mountIdx + 1)]\n : [...parts.slice(0, mountIdx), ...parts.slice(mountIdx + 1)];\n const newFile = newParts.join(\"/\");\n\n return { next, newFile };\n};\n\nexport const migrateRouteMounts = (\n projectDir: string,\n dryRun: boolean,\n): Effect.Effect<StepResult, Error> =>\n Effect.gen(function* () {\n const writes: string[] = [];\n const deletes: string[] = [];\n const notes: string[] = [];\n\n const files = (yield* listFiles(projectDir, ROUTES_GLOB)).filter(\n (f) => !f.endsWith(\".test.ts\"),\n );\n\n // Collect files whose route path is under an unknown mount\n const rehome: Array<{ file: string; source: string; targetMount: string }> = [];\n\n for (const file of files) {\n const source = yield* readText(projectDir, file);\n const match = source.match(ROUTE_FILE_RE);\n if (!match) continue;\n const segments = match[1].split(\"/\").filter(Boolean);\n if (segments.length === 0) continue;\n const mountId = mountIdOf(match[1]);\n if (!mountId || isRegistered(mountId)) continue;\n\n // Target mount precedence: the mount's own canonical alias\n // (e.g. _auth → authenticated), then a registered mount found in later\n // path segments (e.g. /_layout/_authenticated/dashboard → _authenticated),\n // then MOUNT_REDIRECTS, then \"public\".\n let targetMount = \"public\";\n const ownCanonical = canonicalMount(mountId);\n if (KNOWN_MOUNTS.includes(ownCanonical as (typeof KNOWN_MOUNTS)[number])) {\n targetMount = ownCanonical;\n } else {\n for (let i = 1; i < segments.length; i++) {\n const seg = segments[i];\n if (!seg.startsWith(\"_\")) continue;\n const segMountId = seg.slice(1);\n const canonical = canonicalMount(segMountId);\n if (KNOWN_MOUNTS.includes(canonical as (typeof KNOWN_MOUNTS)[number])) {\n targetMount = canonical;\n break;\n }\n }\n if (targetMount === \"public\" && MOUNT_REDIRECTS[mountId]) {\n targetMount = MOUNT_REDIRECTS[mountId];\n }\n }\n rehome.push({ file, source, targetMount });\n }\n\n if (rehome.length === 0) {\n notes.push(\"no routes on unknown mounts — nothing to rehome\");\n return { writes, deletes, notes };\n }\n\n // Create mount layout files for each target mount\n const targetMounts = [...new Set(rehome.map((r) => r.targetMount))];\n for (const targetMount of targetMounts) {\n const path = mountLayoutFile(targetMount);\n if (yield* fileExists(projectDir, path)) {\n notes.push(`${path} exists — left untouched`);\n continue;\n }\n if (!dryRun) yield* writeText(projectDir, path, renderMountLayout(targetMount));\n writes.push(path);\n notes.push(`created mount layout ${path}`);\n }\n\n // Rehome each route file\n for (const { file, source, targetMount } of rehome) {\n const result = rehomeRoute(file, source, targetMount);\n if (!result) {\n notes.push(`${file}: could not parse route path — left for manual rehome`);\n continue;\n }\n const { next, newFile } = result;\n if (next !== source && !dryRun) {\n yield* writeText(projectDir, newFile, next);\n }\n writes.push(newFile);\n if (!dryRun) {\n yield* removePath(projectDir, file);\n }\n deletes.push(file);\n notes.push(`rehomed ${file} → ${newFile}`);\n }\n\n // Remove old mount layout files and empty mount directories\n for (const mountId of Object.keys(MOUNT_REDIRECTS)) {\n const layoutPath = mountLayoutFile(mountId);\n if (yield* fileExists(projectDir, layoutPath)) {\n if (!dryRun) yield* removePath(projectDir, layoutPath);\n deletes.push(layoutPath);\n notes.push(`removed obsolete mount layout ${layoutPath}`);\n }\n }\n\n notes.push(\n \"mount layouts are minimal Outlet shells — migrate any chrome (Header/Footer, session) from the old layout into the new mount manually\",\n );\n\n return { writes, deletes, notes };\n });\n"],"mappings":";;;;;;AAMA,MAAM,cAAc;AACpB,MAAM,gBAAgB;;;;;;AAOtB,MAAM,kBAA0C,EAC9C,QAAQ,SACV;AAEA,MAAM,aAAa,cAAqC;CACtD,MAAM,WAAW,UAAU,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;CACpD,IAAI,SAAS,WAAW,GAAG,OAAO;CAClC,MAAM,QAAQ,SAAS;CACvB,IAAI,CAAC,MAAM,WAAW,GAAG,GAAG,OAAO;CACnC,OAAO,MAAM,MAAM,CAAC;AACtB;AAEA,MAAM,kBAAkB,YAA4BA,6BAAc,YAAY;AAE9E,MAAM,gBAAgB,YACpBC,4BAAa,SAAS,OAAwC;AAEhE,MAAM,mBAAmB,YAA4B,kBAAkB,QAAQ;AAE/E,MAAM,qBAAqB,YAA4B;CACrD,MAAM,YAAY,eAAe,OAAO;CACxC,MAAM,YAAY,cAAc;CAChC,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,8DAA8DC;EAC9D;EACA;EACA;EACA;EACA;EACA,mCAAmC,YAAY,eAAe,GAAG;EACjE,YAAY,iDAAiD;EAC7D;EACA;EACA,QAAQ,UAAU;EAClB,YAAY,yDAAyD;EACrE;EACA;CACF,CAAC,CACE,OAAO,OAAO,CAAC,CACf,KAAK,IAAI;AACd;AAEA,MAAM,eACJ,MACA,QACA,gBAC6C;CAC7C,MAAM,QAAQ,OAAO,MAAM,aAAa;CACxC,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,UAAU,MAAM;CACtB,MAAM,WAAW,QAAQ,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;CAClD,IAAI,SAAS,WAAW,GAAG,OAAO;CAElC,MAAM,cAAc,IAAI,SAAS,EAAE,CAAC,MAAM,CAAC;CAK3C,IAAI;CACJ,IAAI,SAAS,UAAU,KAAK,SAAS,EAAE,CAAC,WAAW,GAAG,GAAG;EACvD,MAAM,aAAa,SAAS,EAAE,CAAC,MAAM,CAAC;EACtC,MAAM,YAAY,eAAe,UAAU;EAC3C,IAAID,4BAAa,SAAS,SAA0C,GAClE,kBAAkB,CAAC,IAAI,aAAa,GAAG,SAAS,MAAM,CAAC,CAAC;OAExD,kBAAkB,CAAC,IAAI,eAAe,GAAG,SAAS,MAAM,CAAC,CAAC;CAE9D,OACE,kBAAkB,CAAC,IAAI,eAAe,GAAG,SAAS,MAAM,CAAC,CAAC;CAE5D,MAAM,UACJ,MACA,gBAAgB,KAAK,GAAG,KACvB,QAAQ,SAAS,GAAG,KAAK,gBAAgB,UAAU,IAAI,MAAM;CAChE,MAAM,OAAO,OAAO,QAAQ,MAAM,IAAI,MAAM,EAAE,CAAC,QAAQ,SAAS,OAAO,CAAC;CAIxE,MAAM,QAAQ,KAAK,MAAM,GAAG;CAC5B,MAAM,WAAW,MAAM,QAAQ,WAAW;CAC1C,IAAI,aAAa,IAAI,OAAO;CAE5B,IAAI,oBAAoB;CACxB,IAAI,WAAW,IAAI,MAAM,QAAQ;EAC/B,MAAM,UAAU,MAAM,WAAW;EACjC,IAAI,QAAQ,WAAW,GAAG,GAAG;GAC3B,MAAM,cAAc,QAAQ,MAAM,CAAC;GACnC,MAAM,YAAY,eAAe,WAAW;GAC5C,IAAIA,4BAAa,SAAS,SAA0C,GAClE,oBAAoB;EAExB;CACF;CAOA,OAAO;EAAE;EAAM,UALE,oBACb;GAAC,GAAG,MAAM,MAAM,GAAG,QAAQ;GAAG,IAAI;GAAe,GAAG,MAAM,MAAM,WAAW,CAAC;EAAC,IAC7E,CAAC,GAAG,MAAM,MAAM,GAAG,QAAQ,GAAG,GAAG,MAAM,MAAM,WAAW,CAAC,CAAC,EACtC,CAAC,KAAK,GAET;CAAE;AACzB;AAEA,MAAa,sBACX,YACA,WAEAE,cAAO,IAAI,aAAa;CACtB,MAAM,SAAmB,CAAC;CAC1B,MAAM,UAAoB,CAAC;CAC3B,MAAM,QAAkB,CAAC;CAEzB,MAAM,SAAS,OAAOC,qBAAU,YAAY,WAAW,EAAC,CAAE,QACvD,MAAM,CAAC,EAAE,SAAS,UAAU,CAC/B;CAGA,MAAM,SAAuE,CAAC;CAE9E,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,OAAOC,oBAAS,YAAY,IAAI;EAC/C,MAAM,QAAQ,OAAO,MAAM,aAAa;EACxC,IAAI,CAAC,OAAO;EACZ,MAAM,WAAW,MAAM,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;EACnD,IAAI,SAAS,WAAW,GAAG;EAC3B,MAAM,UAAU,UAAU,MAAM,EAAE;EAClC,IAAI,CAAC,WAAW,aAAa,OAAO,GAAG;EAMvC,IAAI,cAAc;EAClB,MAAM,eAAe,eAAe,OAAO;EAC3C,IAAIJ,4BAAa,SAAS,YAA6C,GACrE,cAAc;OACT;GACL,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;IACxC,MAAM,MAAM,SAAS;IACrB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;IAC1B,MAAM,aAAa,IAAI,MAAM,CAAC;IAC9B,MAAM,YAAY,eAAe,UAAU;IAC3C,IAAIA,4BAAa,SAAS,SAA0C,GAAG;KACrE,cAAc;KACd;IACF;GACF;GACA,IAAI,gBAAgB,YAAY,gBAAgB,UAC9C,cAAc,gBAAgB;EAElC;EACA,OAAO,KAAK;GAAE;GAAM;GAAQ;EAAY,CAAC;CAC3C;CAEA,IAAI,OAAO,WAAW,GAAG;EACvB,MAAM,KAAK,iDAAiD;EAC5D,OAAO;GAAE;GAAQ;GAAS;EAAM;CAClC;CAGA,MAAM,eAAe,CAAC,GAAG,IAAI,IAAI,OAAO,KAAK,MAAM,EAAE,WAAW,CAAC,CAAC;CAClE,KAAK,MAAM,eAAe,cAAc;EACtC,MAAM,OAAO,gBAAgB,WAAW;EACxC,IAAI,OAAOK,sBAAW,YAAY,IAAI,GAAG;GACvC,MAAM,KAAK,GAAG,KAAK,yBAAyB;GAC5C;EACF;EACA,IAAI,CAAC,QAAQ,OAAOC,qBAAU,YAAY,MAAM,kBAAkB,WAAW,CAAC;EAC9E,OAAO,KAAK,IAAI;EAChB,MAAM,KAAK,wBAAwB,MAAM;CAC3C;CAGA,KAAK,MAAM,EAAE,MAAM,QAAQ,iBAAiB,QAAQ;EAClD,MAAM,SAAS,YAAY,MAAM,QAAQ,WAAW;EACpD,IAAI,CAAC,QAAQ;GACX,MAAM,KAAK,GAAG,KAAK,sDAAsD;GACzE;EACF;EACA,MAAM,EAAE,MAAM,YAAY;EAC1B,IAAI,SAAS,UAAU,CAAC,QACtB,OAAOA,qBAAU,YAAY,SAAS,IAAI;EAE5C,OAAO,KAAK,OAAO;EACnB,IAAI,CAAC,QACH,OAAOC,sBAAW,YAAY,IAAI;EAEpC,QAAQ,KAAK,IAAI;EACjB,MAAM,KAAK,WAAW,KAAK,KAAK,SAAS;CAC3C;CAGA,KAAK,MAAM,WAAW,OAAO,KAAK,eAAe,GAAG;EAClD,MAAM,aAAa,gBAAgB,OAAO;EAC1C,IAAI,OAAOF,sBAAW,YAAY,UAAU,GAAG;GAC7C,IAAI,CAAC,QAAQ,OAAOE,sBAAW,YAAY,UAAU;GACrD,QAAQ,KAAK,UAAU;GACvB,MAAM,KAAK,iCAAiC,YAAY;EAC1D;CACF;CAEA,MAAM,KACJ,uIACF;CAEA,OAAO;EAAE;EAAQ;EAAS;CAAM;AAClC,CAAC"}