everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
128 lines (126 loc) • 3.49 kB
JavaScript
import { createHash } from "node:crypto";
//#region src/rollback.ts
function collectPinnedSlots(config) {
const slots = [];
const app = config.app;
const plugins = config.plugins;
const push = (slot, ref) => {
if (ref && typeof ref === "object") slots.push({
slot,
ref
});
};
if (app) {
for (const key of [
"host",
"ui",
"api",
"auth"
]) push(`app.${key}`, app[key]);
const auth = app.auth;
if (auth?.ui) push("app.auth.ui", auth.ui);
}
if (plugins) for (const [key, plugin] of Object.entries(plugins)) {
if (typeof plugin !== "object" || plugin === null) continue;
push(key, plugin);
const ui = plugin.ui;
if (ui) push(`${key}.ui`, ui);
}
return slots;
}
function pinOf(ref) {
const manifest = typeof ref.pin?.manifest === "string" ? ref.pin.manifest : void 0;
const integrity = typeof ref.pin?.integrity === "string" ? ref.pin.integrity : void 0;
return manifest && integrity ? {
manifest,
integrity
} : null;
}
/** Every pinned slot with both a production base and a verifiable pin. */
function collectSlotPinRefs(config) {
return collectPinnedSlots(config).map(({ slot, ref }) => {
const pin = pinOf(ref);
const base = typeof ref.production === "string" ? ref.production : void 0;
return pin && base ? {
slot,
base,
pin
} : null;
}).filter((entry) => entry !== null);
}
async function verifySlot(slot, ref, fetchImpl) {
const base = typeof ref.production === "string" ? ref.production : void 0;
const pin = pinOf(ref);
if (!pin) return {
slot,
ok: true
};
const { manifest, integrity } = pin;
if (!base) return {
slot,
manifest,
ok: false,
reason: "no production URL to fetch the manifest from"
};
const manifestUrl = `${base.replace(/\/$/, "")}/${manifest.replace(/^\//, "")}`;
try {
const response = await fetchImpl(manifestUrl);
if (!response.ok) return {
slot,
manifest,
ok: false,
reason: `manifest fetch failed: ${response.status}`
};
const body = await response.text();
const computed = `sha384-${createHash("sha384").update(body).digest("base64")}`;
if (computed !== integrity) return {
slot,
manifest,
ok: false,
reason: `integrity mismatch for ${manifestUrl} (expected ${integrity}, computed ${computed})`
};
return {
slot,
manifest,
ok: true
};
} catch (error) {
return {
slot,
manifest,
ok: false,
reason: `manifest fetch failed: ${error instanceof Error ? error.message : String(error)}`
};
}
}
async function verifyRollbackSnapshot(config, opts) {
const fetchImpl = opts?.fetchImpl ?? fetch;
const pinned = collectPinnedSlots(config).filter((entry) => pinOf(entry.ref) !== null);
if (pinned.length === 0) return {
verifiable: false,
ok: false,
slots: []
};
const slots = await Promise.all(pinned.map((entry) => verifySlot(entry.slot, entry.ref, fetchImpl)));
return {
verifiable: true,
ok: slots.every((check) => check.ok),
slots
};
}
function summarizeSlotPins(config) {
const pinned = collectPinnedSlots(config).map((entry) => {
const pin = pinOf(entry.ref);
return pin ? `${entry.slot}=${pin.manifest}` : null;
}).filter(Boolean);
return pinned.length > 0 ? pinned.join(" ") : "no version pins (pre-pin snapshot)";
}
function buildRollbackPayload(targetConfig, rolledBackFrom) {
return {
...targetConfig,
rolledBackFrom
};
}
//#endregion
export { buildRollbackPayload, collectSlotPinRefs, summarizeSlotPins, verifyRollbackSnapshot };
//# sourceMappingURL=rollback.mjs.map