UNPKG

everything-dev

Version:

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

137 lines (135 loc) • 6.15 kB
import { readFrameworkVersion } from "./image.mjs"; import { readWorkspaceCatalog } from "../workspace/catalog.mjs"; import { walkConfigSlots } from "../config-slots.mjs"; import { satisfiesConstraint } from "../../utils/semver.mjs"; import { computeSriHash } from "./integrity.mjs"; import { composePlatformReleaseManifest, computeTemplateInventory, getPlatformManifestKey, getPlatformVersionSlotKey, resolvePlatformManifest, serializePlatformReleaseManifest } from "../platform/manifest.mjs"; import { readFileSync } from "node:fs"; import { join } from "node:path"; //#region src/services/deploy/platform-manifest.ts /** * The deploy-side half of the platform release manifest (ADR 0034 pt 1/2): * wires the framework repo's release inputs into the pure composer and shapes * the registry entry that rides the train's atomic FastKV write. Framework- * rooted deploys only — a child deploy publishes no release manifest. */ /** The universal image (ADR 0020/0021) — the release manifest's image ref. */ const PLATFORM_IMAGE_REF = "ghcr.io/nearbuilders/everything-dev"; /** * The release's pinned slots: every config slot (dotted config path, keyed by * the same path shape the release-slot stamping writes back — `app.auth`, * `app.auth.ui`, `plugins.votes.ui`, …) with a production URL and an * immutable version-manifest pin in the release's publish payload. The * release leg records them in the release manifest so pinned children can * re-point inherited slots at these bytes. */ function collectReleaseSlotPins(config) { const slots = []; walkConfigSlots(config, ({ slot, entry }) => { if (typeof entry !== "object" || entry === null) return; const base = typeof entry.production === "string" ? entry.production : void 0; const pin = entry.pin; const manifest = typeof pin?.manifest === "string" ? pin.manifest : void 0; const integrity = typeof pin?.integrity === "string" ? pin.integrity : void 0; if (base && manifest && integrity) slots.push({ slot, base, pin: { manifest, integrity } }); }); return slots; } /** The `Record<slot, ReleaseSlotRecord>` shape the manifest schema stores. */ function releaseSlotMap(config) { return Object.fromEntries(collectReleaseSlotPins(config).map(({ slot, base, pin }) => [slot, { base, pin }])); } function readWorkspacePackageVersion(configDir, name) { try { const raw = readFileSync(join(configDir, "packages", name, "package.json"), "utf-8"); const pkg = JSON.parse(raw); return typeof pkg.version === "string" ? pkg.version : void 0; } catch { return; } } function readEveryPluginVersion(configDir) { return readWorkspacePackageVersion(configDir, "every-plugin"); } function composePlatformManifestPublishEntry(opts) { const frameworkVersion = readFrameworkVersion(opts.configDir); const everyPluginVersion = readEveryPluginVersion(opts.configDir); if (!frameworkVersion || !everyPluginVersion) return null; const manifest = composePlatformReleaseManifest({ frameworkVersion, everyPluginVersion, ...opts.gitSha ? { gitSha: opts.gitSha } : {}, imageRef: opts.imageRef ?? "ghcr.io/nearbuilders/everything-dev", catalog: readWorkspaceCatalog(opts.configDir), templateInventory: computeTemplateInventory(opts.configDir), compatMinChild: opts.previousFrameworkVersion ?? frameworkVersion, ...opts.publishPayload ? { slots: releaseSlotMap(opts.publishPayload) } : {} }); const value = serializePlatformReleaseManifest(manifest); return { key: getPlatformManifestKey(opts.account, opts.gateway), value, integrity: computeSriHash(value), manifest }; } /** * The truth gate (#546): every uploaded workspace's version manifest declares * the shared-singleton versions its build embedded; the release manifest pins * the same names from the release catalog. A version that appears in both but * disagrees means the workspace's install drifted from the catalog — the * manifest would lie about the uploaded bytes and pinned children would boot * a skewed composition. Pin shapes mirror `checkPlatformCompat`'s semantics * (via `satisfiesConstraint`): an exact pin must match exactly, a range pin * must be satisfied by the built version — the catalog pinning every-plugin * as `^X` while bundles record the exact resolved version is not a violation. * Returns human-readable violations; empty = clean. */ function platformTruthViolations(manifest, sharedByWorkspace) { const violations = []; for (const [workspace, shared] of sharedByWorkspace) { if (!shared) continue; for (const [name, version] of Object.entries(shared)) { const pin = manifest.sharedDeps[name]; if (pin && !satisfiesConstraint(version, pin.version)) violations.push(`${workspace} was built against ${name}@${version}, the release manifest pins ${pin.version} — a stale install publishes a manifest that lies about the uploaded bytes; run pnpm install and redeploy`); } } return violations; } /** * The derived-pin seam for image refs (ADR 0034 pt 2): when the platform * manifest resolves, a runtime's image ref derives as `ref@v<version>` — * never hand-pinned. Digest resolution rides the registry and arrives as * `digest` when the caller can fetch it; the deploy preflight consumer is * `resolveChildImagePin` (`services/deploy/legs.ts`), which resolves the * tag's digest and repoints the Railway service source. */ async function deriveImageRefFromPlatformManifest(opts) { const resolved = await resolvePlatformManifest({ configDir: opts.configDir, localConfig: opts.localConfig, fetchManifest: opts.fetchManifest, ...opts.fetchSlot ? { fetchSlot: opts.fetchSlot } : {}, ...opts.fetchParent ? { fetchParent: opts.fetchParent } : {} }); if (!resolved) return null; const tag = resolved.manifest.image.tags[0]; if (!tag) return null; return { image: resolved.manifest.image.ref, tag }; } //#endregion export { PLATFORM_IMAGE_REF, collectReleaseSlotPins, composePlatformManifestPublishEntry, deriveImageRefFromPlatformManifest, getPlatformManifestKey, getPlatformVersionSlotKey, platformTruthViolations, readEveryPluginVersion, releaseSlotMap }; //# sourceMappingURL=platform-manifest.mjs.map