108 lines
3.0 KiB
JavaScript
108 lines
3.0 KiB
JavaScript
import { z } from "zod";
|
|
|
|
const version = z.string().regex(/^\d+(?:\.\d+){0,2}$/);
|
|
const releaseManifestSchema = z
|
|
.object({
|
|
schemaVersion: z.literal(1),
|
|
appVersion: z.string().min(1),
|
|
buildId: z.string().min(1),
|
|
commitSha: z.string().min(1),
|
|
configSchemaVersion: version,
|
|
apiContractVersion: version,
|
|
assetManifestHash: z.string().min(1),
|
|
releaseId: z.string().min(1),
|
|
builtAt: z.string().min(1),
|
|
})
|
|
.strict();
|
|
|
|
export class ReleaseManifestError extends Error {
|
|
/** @param {string} code @param {{buildId: string, releaseId?: string}} safe */
|
|
constructor(code, safe) {
|
|
super("Release manifest could not be loaded");
|
|
this.name = "ReleaseManifestError";
|
|
this.kind = "RELEASE_MANIFEST_FAILURE";
|
|
this.code = code;
|
|
this.safe = Object.freeze({
|
|
kind: this.kind,
|
|
code,
|
|
buildId: safe.buildId,
|
|
releaseId: safe.releaseId,
|
|
supportReference: `${safe.buildId}:${code}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Awaited<ReturnType<typeof import("./load-runtime-config.js").loadRuntimeConfig>>} runtime
|
|
* @param {{fetcher?: typeof fetch}} [options]
|
|
*/
|
|
export async function loadReleaseManifest(runtime, options = {}) {
|
|
const fetcher = options.fetcher ?? fetch;
|
|
let response;
|
|
try {
|
|
response = await fetcher(runtime.config.RELEASE_MANIFEST_URL, {
|
|
cache: "no-store",
|
|
headers: { Accept: "application/json" },
|
|
});
|
|
} catch {
|
|
throw new ReleaseManifestError("MANIFEST_FETCH_FAILED", {
|
|
buildId: runtime.build.buildId,
|
|
releaseId: runtime.config.RELEASE_ID,
|
|
});
|
|
}
|
|
if (!response.ok) {
|
|
throw new ReleaseManifestError("MANIFEST_HTTP_FAILED", {
|
|
buildId: runtime.build.buildId,
|
|
releaseId: runtime.config.RELEASE_ID,
|
|
});
|
|
}
|
|
|
|
let raw;
|
|
try {
|
|
raw = await response.json();
|
|
} catch {
|
|
throw new ReleaseManifestError("MANIFEST_JSON_INVALID", {
|
|
buildId: runtime.build.buildId,
|
|
releaseId: runtime.config.RELEASE_ID,
|
|
});
|
|
}
|
|
const parsed = releaseManifestSchema.safeParse(raw);
|
|
if (!parsed.success) {
|
|
throw new ReleaseManifestError("MANIFEST_SCHEMA_INVALID", {
|
|
buildId: runtime.build.buildId,
|
|
releaseId: runtime.config.RELEASE_ID,
|
|
});
|
|
}
|
|
|
|
const manifest = parsed.data;
|
|
const mismatches = [];
|
|
if (manifest.buildId !== runtime.build.buildId) mismatches.push("buildId");
|
|
if (
|
|
runtime.config.BUILD_ID &&
|
|
manifest.buildId !== runtime.config.BUILD_ID
|
|
) {
|
|
mismatches.push("runtimeBuildId");
|
|
}
|
|
if (
|
|
manifest.configSchemaVersion !== runtime.config.CONFIG_SCHEMA_VERSION
|
|
) {
|
|
mismatches.push("configSchemaVersion");
|
|
}
|
|
if (manifest.apiContractVersion !== runtime.config.API_CONTRACT_VERSION) {
|
|
mismatches.push("apiContractVersion");
|
|
}
|
|
if (
|
|
runtime.config.RELEASE_ID &&
|
|
manifest.releaseId !== runtime.config.RELEASE_ID
|
|
) {
|
|
mismatches.push("releaseId");
|
|
}
|
|
if (mismatches.length > 0) {
|
|
throw new ReleaseManifestError("MANIFEST_RUNTIME_MISMATCH", {
|
|
buildId: runtime.build.buildId,
|
|
releaseId: runtime.config.RELEASE_ID,
|
|
});
|
|
}
|
|
return Object.freeze(structuredClone(manifest));
|
|
}
|