UNPKG

everything-dev

Version:

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

1 lines • 10.8 kB
{"version":3,"file":"composition.cjs","names":["resolvePlatformManifest","stampInheritedReleaseSlots","collectSlotPinRefs","resolveSlotVersion","findWorkspaceRoot","checkPlatformCompat","readInstalledFrameworkVersion","readWorkspaceCatalog"],"sources":["../../../src/services/deploy/composition.ts"],"sourcesContent":["import type { BosConfigInput } from \"../../types\";\nimport { readInstalledFrameworkVersion } from \"../framework-version\";\nimport {\n checkPlatformCompat,\n type PlatformReleaseManifest,\n type ResolvePlatformManifestOptions,\n resolvePlatformManifest,\n} from \"../platform/manifest\";\nimport { stampInheritedReleaseSlots } from \"../resolution/release-plane\";\nimport { readWorkspaceCatalog } from \"../workspace/catalog\";\nimport { findWorkspaceRoot } from \"../workspace/workspace\";\nimport { collectSlotPinRefs } from \"./rollback\";\nimport { resolveSlotVersion } from \"./version-manifest\";\n\n/**\n * The deploy train's composition plane (ADR 0034 amendment — release-scoped\n * extends inheritance): every remote slot the leaf does not author is a\n * base-runtime slot, and its bytes must come from the child's pinned release\n * instead of drifting with the parent's live config pointer.\n *\n * Without this plane, a root publish (rc.N+1) moves the inherited auth pin\n * while a platform-pinned child's own ui stays built against rc.N — the\n * host's shared-singleton guard (#546) refuses the composition at boot and\n * the child's health degrades after its next redeploy. Two layers close it:\n * the release manifest records its own slot-pin map (the `slots` field,\n * composed on the release leg), and the deploy train stamps those pins onto\n * the inherited slots of the child's publish payload — child-wins over the\n * parent's live pointer at every consumer boot.\n */\n\n/**\n * Resolve the release manifest for a deploy: the authored `platform` pin via\n * slot-first resolution (children), otherwise the latest pointer (root\n * runtimes). Returns null when no manifest is published — a normal state for\n * children pinned to pre-manifest releases. Resolution failures surface; the\n * caller warns and continues unpinned (the composition gate still\n * cross-checks the actual resolved bytes).\n */\nexport async function resolveReleaseManifestForDeploy(input: {\n configDir: string;\n authoredConfig: Record<string, unknown>;\n /** test/registry seam — threaded to the platform-manifest resolution */\n resolveOptions?: Partial<ResolvePlatformManifestOptions>;\n}): Promise<PlatformReleaseManifest | null> {\n const resolved = await resolvePlatformManifest({\n configDir: input.configDir,\n localConfig: input.authoredConfig,\n ...input.resolveOptions,\n });\n return resolved?.manifest ?? null;\n}\n\n/** Authored slot paths: every slot whose code source the leaf itself declares. */\nexport { authoredSlotPaths } from \"../resolution/release-plane\";\n\nexport interface StampOutcome {\n stamped: string[];\n}\n\n/**\n * Stamp inherited slots: for every `app.*` slot in the release manifest's\n * `slots` map that the authored config does not author (see\n * `authoredSlotPaths` — configuring a slot is not authoring it), write the release's\n * recorded production URL + pin into the payload. Child-wins deep merge then\n * shadows the parent's live config pointer at every consumer boot, so the\n * child composes exactly the bytes its pinned release published. Slots the\n * child authors stay untouched, and a manifest without `slots` (pre-amendment\n * releases) stamps nothing — the composition gate covers those children.\n */\nexport function stampInheritedSlotsFromRelease(input: {\n payload: Record<string, unknown>;\n authoredConfig: Record<string, unknown>;\n manifest: PlatformReleaseManifest;\n}): StampOutcome {\n const outcome = stampInheritedReleaseSlots({\n config: input.payload,\n authoredConfig: input.authoredConfig,\n slots: input.manifest.slots,\n mode: \"payload\",\n });\n return { stamped: outcome.stamped };\n}\n\nexport interface CompositionGateReport {\n ok: boolean;\n violations: string[];\n}\n\nexport interface CompositionGateInput {\n configDir: string;\n authoredConfig: Record<string, unknown>;\n resolvedShape: Record<string, unknown>;\n resolvedRelease: PlatformReleaseManifest | null;\n /**\n * The slot names the leaf-payload pin verification leg proved servable;\n * an unresolvable merged-shape pin outside this set is reported as a\n * violation instead of skipped-silently (inherited pins are not covered\n * by that leg).\n */\n verifiedSlots?: ReadonlySet<string>;\n /** test seam — threaded to the version-manifest fetch */\n fetchImpl?: typeof fetch;\n}\n\n/**\n * The deploy-train composition gate: cross-compare the shared-singleton\n * versions of every pinned UI slot in the extends-merged shape (the boot\n * guard's mirror, #546), plus — for authored platform pins — the ADR 0034\n * compat check of the child against its own release. Runs after pin\n * verification, before publish.\n */\nexport async function runCompositionGate(\n input: CompositionGateInput,\n): Promise<CompositionGateReport> {\n const violations: string[] = [\n ...(await compositionSkewViolations(input.resolvedShape, {\n ...(input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}),\n ...(input.verifiedSlots ? { verifiedSlots: input.verifiedSlots } : {}),\n })),\n ];\n if (typeof input.authoredConfig.platform === \"string\" && input.resolvedRelease) {\n violations.push(...platformCompatViolations(input.resolvedRelease, input.configDir));\n }\n return { ok: violations.length === 0, violations };\n}\n\n/**\n * Cross-compare each pinned UI slot's embedded shared-singleton versions. An\n * unresolvable pin the pin/SRI verification leg already proved servable is\n * skipped (transient fetch failure); any unresolvable pin outside that set is\n * reported — the boot's version-manifest derivation would refuse it anyway.\n */\nexport async function compositionSkewViolations(\n merged: Record<string, unknown>,\n opts: { fetchImpl?: typeof fetch; verifiedSlots?: ReadonlySet<string> } = {},\n): Promise<string[]> {\n const uiSlots = collectSlotPinRefs(merged as BosConfigInput).filter(\n (ref) => ref.slot === \"app.ui\" || ref.slot.endsWith(\".ui\"),\n );\n if (uiSlots.length === 0) return [];\n\n const versionsByName = new Map<string, Array<{ slot: string; version: string }>>();\n const violations: string[] = [];\n\n await Promise.all(\n uiSlots.map(async ({ slot, base, pin }) => {\n try {\n const resolved = await resolveSlotVersion({\n base,\n pin,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n for (const [name, version] of Object.entries(resolved.sharedVersions ?? {})) {\n const uses = versionsByName.get(name) ?? [];\n uses.push({ slot, version });\n versionsByName.set(name, uses);\n }\n } catch (error) {\n if (opts.verifiedSlots?.has(slot)) return;\n const reason = error instanceof Error ? error.message : String(error);\n violations.push(\n `Pinned UI slot \"${slot}\" version manifest does not serve at its production URL (${reason}) — ` +\n `a consumer boot cannot derive its shared singletons; investigate the storage origin or redeploy the bundle.`,\n );\n }\n }),\n );\n\n for (const [name, uses] of versionsByName) {\n const first = uses[0];\n if (!first) continue;\n const diverged = uses.find((use) => use.version !== first.version);\n if (!diverged) continue;\n violations.push(\n `Shared singleton \"${name}\" skew across UI sources: \"${first.slot}\" was built against ` +\n `${first.version}, \"${diverged.slot}\" against ${diverged.version} — the composition refuses to load ` +\n `a split module graph. Redeploy the older bundle: \\`bos upgrade\\` realigns the platform pin, ` +\n `\\`bos deploy\\` rebuilds against it (issue #546).`,\n );\n }\n return violations;\n}\n\n/**\n * The ADR 0034 compat check, scoping the child's installed framework version\n * and workspace catalog against its OWN pinned release — a child resolving\n * via slot-first never fails for being older than the parent's latest.\n */\nexport function platformCompatViolations(\n manifest: PlatformReleaseManifest,\n configDir: string,\n): string[] {\n const root = findWorkspaceRoot(configDir)?.dir ?? configDir;\n const violations = checkPlatformCompat(manifest, {\n platformVersion: readInstalledFrameworkVersion(configDir, \"everything-dev\"),\n catalog: readWorkspaceCatalog(root),\n });\n return violations.map(\n (violation) =>\n `${violation.message} — run \\`bos upgrade\\` to realign your platform pin with \\`bos deploy\\``,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,eAAsB,gCAAgC,OAKV;CAM1C,QAAO,MALgBA,yCAAwB;EAC7C,WAAW,MAAM;EACjB,aAAa,MAAM;EACnB,GAAG,MAAM;CACX,CAAC,EACc,EAAE,YAAY;AAC/B;;;;;;;;;;;AAmBA,SAAgB,+BAA+B,OAI9B;CAOf,OAAO,EAAE,SANOC,iDAA2B;EACzC,QAAQ,MAAM;EACd,gBAAgB,MAAM;EACtB,OAAO,MAAM,SAAS;EACtB,MAAM;CACR,CACwB,CAAC,CAAC,QAAQ;AACpC;;;;;;;;AA8BA,eAAsB,mBACpB,OACgC;CAChC,MAAM,aAAuB,CAC3B,GAAI,MAAM,0BAA0B,MAAM,eAAe;EACvD,GAAI,MAAM,YAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;EACxD,GAAI,MAAM,gBAAgB,EAAE,eAAe,MAAM,cAAc,IAAI,CAAC;CACtE,CAAC,CACH;CACA,IAAI,OAAO,MAAM,eAAe,aAAa,YAAY,MAAM,iBAC7D,WAAW,KAAK,GAAG,yBAAyB,MAAM,iBAAiB,MAAM,SAAS,CAAC;CAErF,OAAO;EAAE,IAAI,WAAW,WAAW;EAAG;CAAW;AACnD;;;;;;;AAQA,eAAsB,0BACpB,QACA,OAA0E,CAAC,GACxD;CACnB,MAAM,UAAUC,oCAAmB,MAAwB,CAAC,CAAC,QAC1D,QAAQ,IAAI,SAAS,YAAY,IAAI,KAAK,SAAS,KAAK,CAC3D;CACA,IAAI,QAAQ,WAAW,GAAG,OAAO,CAAC;CAElC,MAAM,iCAAiB,IAAI,IAAsD;CACjF,MAAM,aAAuB,CAAC;CAE9B,MAAM,QAAQ,IACZ,QAAQ,IAAI,OAAO,EAAE,MAAM,MAAM,UAAU;EACzC,IAAI;GACF,MAAM,WAAW,MAAMC,4DAAmB;IACxC;IACA;IACA,GAAI,KAAK,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC;GACxD,CAAC;GACD,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,SAAS,kBAAkB,CAAC,CAAC,GAAG;IAC3E,MAAM,OAAO,eAAe,IAAI,IAAI,KAAK,CAAC;IAC1C,KAAK,KAAK;KAAE;KAAM;IAAQ,CAAC;IAC3B,eAAe,IAAI,MAAM,IAAI;GAC/B;EACF,SAAS,OAAO;GACd,IAAI,KAAK,eAAe,IAAI,IAAI,GAAG;GACnC,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACpE,WAAW,KACT,mBAAmB,KAAK,2DAA2D,OAAO,gHAE5F;EACF;CACF,CAAC,CACH;CAEA,KAAK,MAAM,CAAC,MAAM,SAAS,gBAAgB;EACzC,MAAM,QAAQ,KAAK;EACnB,IAAI,CAAC,OAAO;EACZ,MAAM,WAAW,KAAK,MAAM,QAAQ,IAAI,YAAY,MAAM,OAAO;EACjE,IAAI,CAAC,UAAU;EACf,WAAW,KACT,qBAAqB,KAAK,6BAA6B,MAAM,KAAK,sBAC7D,MAAM,QAAQ,KAAK,SAAS,KAAK,YAAY,SAAS,QAAQ,gLAGrE;CACF;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,yBACd,UACA,WACU;CACV,MAAM,OAAOC,oCAAkB,SAAS,CAAC,EAAE,OAAO;CAKlD,OAJmBC,qCAAoB,UAAU;EAC/C,iBAAiBC,wDAA8B,WAAW,gBAAgB;EAC1E,SAASC,qCAAqB,IAAI;CACpC,CACgB,CAAC,CAAC,KACf,cACC,GAAG,UAAU,QAAQ,wEACzB;AACF"}