everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
83 lines (81 loc) • 3.92 kB
JavaScript
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