UNPKG

everything-dev

Version:

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

83 lines (81 loc) • 3.92 kB
import { bundleUrlToStagedPath, resolveContained } from "./bundle-path.mjs"; import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; import path from "node:path"; import { createHash } from "node:crypto"; import { WorkspaceVersionManifestSchema } from "every-plugin/version-manifest"; //#region src/version-manifest-resolve.ts const cache = /* @__PURE__ */ new Map(); function clearSlotVersionCache() { cache.clear(); } function fetchText(url, fetchImpl) { return fetchImpl(url).then((response) => { if (!response.ok) throw new Error(`version manifest fetch failed: ${response.status}`); return response.text(); }); } function slotVersionFromManifest(base, manifest) { const trimmedBase = base.replace(/\/$/, ""); return { entryUrl: `${trimmedBase}/${manifest.entry}`, entryIntegrity: manifest.entryIntegrity, ...manifest.browserManifest ? { browserManifestUrl: `${trimmedBase}/${manifest.browserManifest.file}` } : {}, ...manifest.ssr ? { ssrEntryUrl: `${trimmedBase}/${manifest.ssr.entry}`, ssrIntegrity: manifest.ssr.integrity } : {}, ...manifest.files ? { files: manifest.files } : {} }; } /** * Staged-slot preference (ADR 0020/0021 self-contained tier): when a runtime * stages its own namespace under `BOS_BUNDLE_DIR`, a slot resolves from the * staged dist's own version manifest instead of the config's pin — pinned * manifest bytes that predate the image (deploy lag after a release bump) * cannot exist in the staged namespace, so their hashed files would 404 the * disk and fall through to the network, where the stale immutable bytes * still answer 200 and boot a foreign build. The staged manifest is trusted * (staged by the image build itself) and skipped entirely when the slot is * absent from the staged namespace or the pinned manifest is present (the * normal healthy case, which keeps the pin's SRI check). */ function readStagedSlotVersion(base, pin) { const bundleDir = process.env.BOS_BUNDLE_DIR; if (!bundleDir) return null; const slotDir = bundleUrlToStagedPath(base, bundleDir); if (!slotDir || !existsSync(slotDir)) return null; const pinnedManifestPath = resolveContained(slotDir, pin.manifest); if (!pinnedManifestPath || existsSync(pinnedManifestPath)) return null; const versionsDir = path.join(slotDir, "versions"); if (!existsSync(versionsDir)) return null; const manifests = readdirSync(versionsDir).filter((name) => name.endsWith(".json")).map((name) => path.join(versionsDir, name)).filter((file) => statSync(file).isFile()).sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs || (a < b ? -1 : 1)); if (manifests.length === 0) return null; const manifestPath = manifests[0]; if (!manifestPath) return null; try { return slotVersionFromManifest(base, WorkspaceVersionManifestSchema.parse(JSON.parse(readFileSync(manifestPath, "utf8")))); } catch { return null; } } async function resolveSlotVersion(input) { const key = `${input.base}::${input.pin.manifest}::${input.pin.integrity}`; const cached = cache.get(key); if (cached) return cached; const staged = readStagedSlotVersion(input.base, input.pin); if (staged) { cache.set(key, staged); return staged; } const manifestUrl = `${input.base.replace(/\/$/, "")}/${input.pin.manifest.replace(/^\//, "")}`; const body = await fetchText(manifestUrl, input.fetchImpl ?? fetch); const computed = `sha384-${createHash("sha384").update(body).digest("base64")}`; if (computed !== input.pin.integrity) throw new Error(`[SRI] version manifest integrity mismatch for ${manifestUrl}\n Expected: ${input.pin.integrity}\n Computed: ${computed}`); const manifest = WorkspaceVersionManifestSchema.parse(JSON.parse(body)); const resolved = slotVersionFromManifest(input.base, manifest); cache.set(key, resolved); return resolved; } //#endregion export { clearSlotVersionCache, resolveSlotVersion }; //# sourceMappingURL=version-manifest-resolve.mjs.map