UNPKG

everything-dev

Version:

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

1 lines • 14.1 kB
{"version":3,"file":"compose-seam.cjs","names":["Effect","fileExists","readText","readFile","readFileSync","Finding"],"sources":["../../../../../src/services/scaffold/upgrade-audit/checks/compose-seam.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { Effect } from \"effect\";\nimport { Finding } from \"../domain\";\nimport { fileExists, readText } from \"../fs\";\n\n/**\n * Compose-seam guard — replays the host's SSR composition collision locally:\n * our core ui manifest (which claims /login) composed with the deployed\n * auth-ui manifest (which ships _public/login plus a device-login subtree).\n *\n * every-plugin's constructTree handles the collision by design (\"core ui\n * wins; shadowed route skipped\") but pre-fix releases mark the shadowed\n * children without splicing them out of `pending`, so the loop exits\n * non-empty and throws a false \"unresolvable parentage\" — the prod boot\n * failure this guard watches for. Run with: pnpm compose-seam [--live]\n */\n\nexport const UI_MANIFEST_PATH = \"ui/src/manifest.gen.json\";\nexport const AUTH_FIXTURE_PATH = \"fixtures/compose-seam/auth-ui.manifest.gen.json\";\nexport const AUTH_MANIFEST_URL =\n \"https://cdn.everything.dev/bundles/dev.everything.near/everything.dev/auth-ui/manifest.gen.json\";\nexport const AUTH_PLUGIN_KEY = \"auth\";\nexport const CORE_UI_KEY = \"ui\";\nexport const SHADOW_LOG_MARKER = \"core ui wins; shadowed route skipped\";\n\nexport interface ManifestRoute {\n readonly id: string;\n readonly type: \"index\" | \"layout\" | \"route\";\n readonly path?: string;\n readonly parentId?: string;\n readonly mount?: string;\n}\n\nexport interface PluginManifestData {\n readonly name: string;\n readonly manifestVersion: number;\n readonly routes: readonly ManifestRoute[];\n}\n\nexport type ComposeSeam =\n | \"ok\"\n | \"cascade-bug\"\n | \"path-collision\"\n | \"compose-failed\"\n | \"absent-ui-manifest\";\n\nexport interface ComposeProbe {\n readonly seam: ComposeSeam;\n /** `[Compose]` lines emitted by constructTree during the probe */\n readonly composeLog: readonly string[];\n readonly error?: string;\n readonly mountCounts?: Record<string, number>;\n readonly uiClaimsLogin: boolean;\n}\n\nexport type ConstructTreeFn = (input: {\n name: string;\n plugins: ReadonlyArray<{ key: string; mfName: string }>;\n resolve: (ref: { key: string; mfName: string }) => Promise<{\n key: string;\n manifest: PluginManifestData;\n routeConfig: { routeConfigLoaders: Record<string, () => Promise<unknown>> };\n }>;\n rootOptions?: unknown;\n}) => Promise<{ mountCounts: Record<string, number> }>;\n\n/** Pure — \"unresolvable parentage\" after a shadow log is the known cascade bug. */\nexport const classifyComposeError = (\n message: string,\n shadowedARoute: boolean,\n): Exclude<ComposeSeam, \"ok\" | \"absent-ui-manifest\"> => {\n if (message.includes(\"path collision:\")) return \"path-collision\";\n if (message.includes(\"unresolvable parentage\") && shadowedARoute) return \"cascade-bug\";\n return \"compose-failed\";\n};\n\n/** Pure — every manifest route id needs a loader or constructTree refuses. */\nexport const stubRouteConfigLoaders = (manifest: PluginManifestData) =>\n Object.fromEntries(manifest.routes.map((record) => [record.id, async () => ({})]));\n\nexport const uiClaimsLogin = (manifest: PluginManifestData): boolean =>\n manifest.routes.some((record) => record.id === \"_public/login\" && record.path === \"/login\");\n\nconst captureConsole = async (fn: () => Promise<{ mountCounts: Record<string, number> }>) => {\n const lines: string[] = [];\n const original = console.log;\n console.log = (...args: unknown[]) => {\n lines.push(args.map(String).join(\" \"));\n };\n let result: { mountCounts: Record<string, number> } | undefined;\n let error: unknown;\n try {\n result = await fn();\n } catch (cause) {\n error = cause;\n } finally {\n console.log = original;\n }\n return { lines, result, error };\n};\n\n/**\n * Runs the real composer (or an injected stand-in) over the two manifests.\n * The auth manifest's /login tree is the collision shape; classification\n * only — never throws.\n */\nexport const runComposeProbe = async (\n uiManifest: PluginManifestData,\n authManifest: PluginManifestData,\n constructTree: ConstructTreeFn,\n): Promise<ComposeProbe> => {\n const claimsLogin = uiClaimsLogin(uiManifest);\n const manifests = new Map<string, PluginManifestData>([\n [CORE_UI_KEY, uiManifest],\n [AUTH_PLUGIN_KEY, authManifest],\n ]);\n try {\n const { lines, result, error } = await captureConsole(() =>\n constructTree({\n name: \"compose-seam-probe\",\n plugins: [\n { key: CORE_UI_KEY, mfName: CORE_UI_KEY },\n { key: AUTH_PLUGIN_KEY, mfName: AUTH_PLUGIN_KEY },\n ],\n resolve: async (ref) => {\n const manifest = manifests.get(ref.key);\n if (!manifest) throw new Error(`probe has no manifest for \"${ref.key}\"`);\n return {\n key: ref.key,\n manifest,\n routeConfig: { routeConfigLoaders: stubRouteConfigLoaders(manifest) },\n };\n },\n }),\n );\n if (error === undefined) {\n return {\n seam: \"ok\",\n composeLog: lines,\n mountCounts: result?.mountCounts,\n uiClaimsLogin: claimsLogin,\n };\n }\n const message = error instanceof Error ? error.message : String(error);\n const shadowedARoute = lines.some((line) => line.includes(SHADOW_LOG_MARKER));\n return {\n seam: classifyComposeError(message, shadowedARoute),\n composeLog: lines,\n error: message,\n uiClaimsLogin: claimsLogin,\n };\n } catch (cause) {\n const message = cause instanceof Error ? cause.message : String(cause);\n return {\n seam: \"compose-failed\",\n composeLog: [],\n error: message,\n uiClaimsLogin: claimsLogin,\n };\n }\n};\n\nconst loadManifest = (source: string, label: string): Effect.Effect<PluginManifestData, Error> =>\n Effect.try({\n try: () => {\n const parsed = JSON.parse(source) as PluginManifestData;\n if (!Array.isArray(parsed.routes) || typeof parsed.name !== \"string\") {\n throw new Error(\"not a plugin manifest (name/routes missing)\");\n }\n return parsed;\n },\n catch: (error) => new Error(`${label}: ${String(error)}`),\n });\n\nexport interface ProbeOptions {\n /** fetch the deployed auth-ui manifest instead of the committed fixture */\n readonly live?: boolean;\n}\n\n/** Reads both manifests and runs the probe; succeeds even when compose fails. */\nexport const probeComposeSeam = (\n projectDir: string,\n options?: ProbeOptions,\n): Effect.Effect<ComposeProbe, Error> =>\n Effect.gen(function* () {\n const uiExists = yield* fileExists(projectDir, UI_MANIFEST_PATH);\n if (!uiExists) {\n return {\n seam: \"absent-ui-manifest\",\n composeLog: [],\n uiClaimsLogin: false,\n } satisfies ComposeProbe;\n }\n\n const uiManifest = yield* loadManifest(\n yield* readText(projectDir, UI_MANIFEST_PATH),\n UI_MANIFEST_PATH,\n );\n\n let authSource: string;\n if (options?.live) {\n authSource = yield* Effect.tryPromise({\n try: async () => {\n const response = await fetch(AUTH_MANIFEST_URL);\n if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);\n return await response.text();\n },\n catch: (error) => new Error(`failed to fetch deployed auth-ui manifest: ${String(error)}`),\n });\n } else {\n authSource = yield* Effect.tryPromise({\n try: () => readFile(new URL(`../${AUTH_FIXTURE_PATH}`, import.meta.url), \"utf-8\"),\n catch: (error) =>\n new Error(`failed to load the committed auth-ui fixture: ${String(error)}`),\n });\n }\n const authManifest = yield* loadManifest(authSource, \"auth-ui manifest\");\n\n const { constructTree } = yield* Effect.tryPromise({\n try: () => import(\"every-plugin/ui/manifest\"),\n catch: (error) => new Error(`failed to load every-plugin's composer: ${String(error)}`),\n });\n\n return yield* Effect.tryPromise({\n try: () =>\n runComposeProbe(uiManifest, authManifest, constructTree as unknown as ConstructTreeFn),\n catch: (error) => new Error(`compose probe failed: ${String(error)}`),\n });\n });\n\nexport const readEveryPluginVersion = (projectDir: string): Effect.Effect<string> =>\n Effect.sync(() => {\n try {\n const source = readFileSync(`${projectDir}/node_modules/every-plugin/package.json`, \"utf-8\");\n return (JSON.parse(source) as { version?: string }).version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n });\n\n/** Pure — probe outcome → audit findings. */\nexport const composeFindings = (probe: ComposeProbe, everyPluginVersion: string): Finding[] => {\n if (probe.seam === \"ok\") return [];\n if (probe.seam === \"absent-ui-manifest\") {\n return [\n new Finding({\n check: \"compose-seam\",\n path: UI_MANIFEST_PATH,\n severity: \"warn\",\n message:\n \"ui/src/manifest.gen.json is absent — run `pnpm types:gen`; the compose probe needs the generated manifest\",\n }),\n ];\n }\n const messages: Record<\"cascade-bug\" | \"path-collision\" | \"compose-failed\", string> = {\n \"cascade-bug\":\n `every-plugin ${everyPluginVersion} still throws a false \"unresolvable parentage\" for the /login shadow cascade ` +\n \"(constructTree marks shadowed children without splicing them out of pending) — prod boot SSR composition fails with this. \" +\n \"Bump everything-dev/every-plugin to the release with the cascade-splice fix, then redeploy\",\n \"path-collision\":\n 'the composer treats the /login shadow as a hard path collision instead of \"core ui wins\" — shadow handling regressed; ' +\n `check every-plugin ${everyPluginVersion} constructTree against the auth-ui manifest`,\n \"compose-failed\": `compose probe failed: ${probe.error ?? \"unknown error\"}`,\n };\n return [\n new Finding({\n check: \"compose-seam\",\n path: \"ui/src/manifest.gen.json + auth-ui manifest\",\n severity: \"blocker\",\n message: messages[probe.seam],\n }),\n ];\n};\n\nexport const checkComposeSeam = (\n projectDir: string,\n options?: ProbeOptions,\n): Effect.Effect<Finding[], Error> =>\n Effect.gen(function* () {\n const probe = yield* probeComposeSeam(projectDir, options);\n const version = yield* readEveryPluginVersion(projectDir);\n return composeFindings(probe, version);\n });\n"],"mappings":"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"}