import { mkdir, readFile, readdir } from "node:fs/promises"; import { hostingHeadersArtifactSchema } from "./contracts/release-artifacts.ts"; import { classifyLiveHostingBaseUrl } from "./lib/hosting-probe.ts"; import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts"; type Document = Record; type ResponseHeaders = Record>; type HostingMode = "live" | "invalid-live" | "fixture"; type ProbeResult = Readonly<{ surface: string; header: string; expected: unknown; observed: unknown; reason?: string; passed: boolean; }>; function isRecord(value: unknown): value is Document { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function recordValue(value: unknown): Document { return isRecord(value) ? value : {}; } function strings(value: unknown): string[] { return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string") : []; } function stringRecord(value: unknown): Record { return Object.fromEntries( Object.entries(recordValue(value)).filter( (entry): entry is [string, string] => typeof entry[1] === "string", ), ); } function responseHeaders(value: unknown): ResponseHeaders { return Object.fromEntries( Object.entries(recordValue(value)).map(([surface, headers]) => [ surface, stringRecord(headers), ]), ); } async function readDocument(file: string): Promise { const parsed: unknown = JSON.parse(await readFile(file, "utf8")); if (!isRecord(parsed)) throw new Error(`${file} must be a JSON object`); return parsed; } const cachePolicy = await readDocument("config/hosting/cache-policy.json"); const cacheSurfaces = recordValue(cachePolicy.surfaces); const securityPolicy = await readDocument( "config/hosting/security-headers.json", ); const securityHeaders = stringRecord(securityPolicy.headers); const baseUrl = process.env.HOSTING_BASE_URL; const liveTarget = baseUrl ? classifyLiveHostingBaseUrl(baseUrl) : null; const distFiles = (await readdir("dist", { recursive: true })).map(String); const publicSourceMaps = distFiles.filter((file) => file.endsWith(".map")); const publicServiceWorkers = distFiles.filter((file) => /(?:^|\/)(?:service-worker|sw)(?:[.-][^/]*)?\.js$/i.test(file), ); let responses: ResponseHeaders = {}; let mode: HostingMode; const probeResults: ProbeResult[] = []; if (liveTarget?.passed) { mode = "live"; const assets = await readdir("dist/assets"); const hashedJavaScript = assets.find((file) => file.endsWith(".js")); if (!hashedJavaScript) throw new Error("No built hashed JavaScript found."); const paths = { index: "/", runtimeConfig: "/config.json", releaseManifest: "/release-manifest.json", hashedAsset: `/assets/${hashedJavaScript}`, }; for (const [surface, pathname] of Object.entries(paths)) { const requestedUrl = new URL(pathname, liveTarget.url); try { const response = await fetch(requestedUrl, { redirect: "follow" }); const finalUrl = new URL(response.url); probeResults.push( { surface, header: "http-status", expected: 200, observed: response.status, passed: response.status === 200, }, { surface, header: "final-origin", expected: liveTarget.url.origin, observed: finalUrl.origin, passed: finalUrl.origin === liveTarget.url.origin, }, ); responses[surface] = Object.fromEntries( [...response.headers.entries()].map(([name, value]) => [ name.toLowerCase(), value, ]), ); } catch (error: unknown) { probeResults.push({ surface, header: "transport", expected: "reachable", observed: error instanceof Error ? error.name : "UnknownError", passed: false, }); } } } else if (liveTarget) { mode = "invalid-live"; probeResults.push({ surface: "deployment", header: "base-url", expected: "canonical non-loopback HTTPS root URL", observed: liveTarget.observedOrigin, reason: liveTarget.reason, passed: false, }); } else { mode = "fixture"; const fixture = await readDocument( "config/hosting/response-headers.fixture.json", ); responses = responseHeaders(fixture.responses); } const results: ProbeResult[] = [...probeResults]; for (const [surface, rawPolicy] of Object.entries(cacheSurfaces)) { const policy = recordValue(rawPolicy); if (typeof policy.cacheControl !== "string") continue; const contentTypes = strings(policy.contentTypes); const observed = responses[surface]?.["cache-control"]; results.push({ surface, header: "cache-control", expected: policy.cacheControl, observed, passed: observed === policy.cacheControl, }); const observedContentType = responses[surface]?.["content-type"]; const observedMime = observedContentType ?.split(";", 1)[0] ?.trim() .toLowerCase(); results.push({ surface, header: "content-type", expected: contentTypes, observed: observedContentType, passed: observedMime !== undefined && contentTypes.includes(observedMime), }); if (policy.securityHeaders === true) { for (const [header, expected] of Object.entries(securityHeaders)) { const observedSecurity = responses[surface]?.[header.toLowerCase()]; results.push({ surface, header: header.toLowerCase(), expected, observed: observedSecurity, passed: observedSecurity === expected, }); } } } const sourceMapPolicy = recordValue(cacheSurfaces.sourceMap); results.push({ surface: "sourceMap", header: "public", expected: false, observed: publicSourceMaps.length > 0, passed: sourceMapPolicy.public === false && publicSourceMaps.length === 0, }); const serviceWorkerPolicy = recordValue(cacheSurfaces.serviceWorker); results.push({ surface: "serviceWorker", header: "enabled", expected: false, observed: publicServiceWorkers.length > 0, passed: serviceWorkerPolicy.enabled === false && publicServiceWorkers.length === 0, }); const passed = results.every((result) => result.passed); await mkdir("artifacts/release", { recursive: true }); await writeValidatedJsonArtifact({ path: "artifacts/release/hosting-headers.json", schema: hostingHeadersArtifactSchema, value: { schemaVersion: 1, generatedAt: new Date().toISOString(), mode, baseUrl: liveTarget?.observedOrigin ?? null, providerVerificationRequired: mode !== "live", results, passed, }, }); if (!passed) { process.stderr.write( "Hosting cache/content-type/security header verification failed.\n", ); process.exit(1); } process.stdout.write( `Hosting header contract: PASS (${mode}; live verification ${ mode === "live" ? "complete" : "required before promotion" })\n`, );