UNPKG

everything-dev

Version:

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

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