import { execFileSync } from "node:child_process"; import { readFile } from "node:fs/promises"; import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { loadDesktopReleasePolicy, resolveDesktopReleasePolicy } from "./desktop-release-policy.mjs"; const frontendDir = fileURLToPath(new URL("../", import.meta.url)); const rootDir = resolve(frontendDir, ".."); const packageInfo = JSON.parse(await readFile(resolve(frontendDir, "package.json"), "utf8")); const desktopReleasePolicy = await loadDesktopReleasePolicy(); const desktopReleaseResolution = resolveDesktopReleasePolicy(packageInfo.version, desktopReleasePolicy); const failures = []; const env = process.env; const fullShaPattern = /^[0-9a-f]{40}$/i; const expectedTag = `v${packageInfo.version}`; const requiresMacosSigning = env.REQUIRE_DESKTOP_SIGNING === "true"; const requiresWindowsSigning = env.REQUIRE_WINDOWS_SIGNING === "true"; const requiresUpdaterSigning = env.REQUIRE_UPDATER_SIGNING === "true"; const allowsUnsignedPlatformRelease = env.ALLOW_UNSIGNED_PLATFORM_RELEASE === "true"; const desktopPlatformSigningMode = env.DESKTOP_PLATFORM_SIGNING_MODE; const desktopReleasePlatform = env.DESKTOP_RELEASE_PLATFORM; const requiredUpdaterEnv = ["TAURI_SIGNING_PRIVATE_KEY", "TAURI_SIGNING_PRIVATE_KEY_PASSWORD"]; const requiredMacosEnv = ["APPLE_ID", "APPLE_PASSWORD", "APPLE_TEAM_ID"]; const requiredWindowsEnv = ["WINDOWS_CERTIFICATE", "WINDOWS_CERTIFICATE_PASSWORD"]; const fail = (message) => failures.push(message); const assert = (condition, message) => { if (!condition) fail(message); }; const git = (args) => execFileSync("git", args, { cwd: rootDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], }).trim(); const gitMaybe = (args) => { try { return git(args); } catch { return undefined; } }; const requireEnv = (name) => { assert(Boolean(env[name]), `${name} must be configured for desktop release readiness.`); }; const validateBaseUrl = () => { const raw = env.DESKTOP_UPDATE_BASE_URL; requireEnv("DESKTOP_UPDATE_BASE_URL"); if (!raw) return; let url; try { url = new URL(raw.endsWith("/") ? raw : `${raw}/`); } catch (error) { fail(`DESKTOP_UPDATE_BASE_URL is invalid: ${error.message}`); return; } assert(url.protocol === "https:", "DESKTOP_UPDATE_BASE_URL must use HTTPS."); assert(url.username === "" && url.password === "", "DESKTOP_UPDATE_BASE_URL must not include credentials."); assert(!/[?&]token=/i.test(url.search), "DESKTOP_UPDATE_BASE_URL must not include token query parameters."); assert( url.pathname.includes(packageInfo.version), `DESKTOP_UPDATE_BASE_URL must include the immutable version segment ${packageInfo.version}.`, ); }; const validateWindowsTimestampUrl = () => { const raw = env.WINDOWS_TIMESTAMP_URL; requireEnv("WINDOWS_TIMESTAMP_URL"); if (!raw) return; let url; try { url = new URL(raw); } catch (error) { fail(`WINDOWS_TIMESTAMP_URL is invalid: ${error.message}`); return; } assert(["http:", "https:"].includes(url.protocol), "WINDOWS_TIMESTAMP_URL must use HTTP or HTTPS."); assert(url.username === "" && url.password === "", "WINDOWS_TIMESTAMP_URL must not include credentials."); assert(!/[?&]token=/i.test(url.search), "WINDOWS_TIMESTAMP_URL must not include token query parameters."); }; const headSha = gitMaybe(["rev-parse", "HEAD"]); const exactTag = gitMaybe(["describe", "--tags", "--exact-match", "HEAD"]); const status = gitMaybe(["status", "--porcelain"]); assert(Boolean(headSha), "Current Git commit cannot be resolved."); assert(exactTag === expectedTag, `Current commit must be exactly tagged ${expectedTag}; found ${exactTag || ""}.`); assert(status === "", "Release readiness requires a clean working tree."); assert(env.VITE_BUILD_CHANNEL === "release", "VITE_BUILD_CHANNEL must be release."); assert(fullShaPattern.test(env.VITE_BUILD_COMMIT || ""), "VITE_BUILD_COMMIT must be the full release commit SHA."); if (headSha && env.VITE_BUILD_COMMIT) { assert(env.VITE_BUILD_COMMIT === headSha, "VITE_BUILD_COMMIT must match the current release commit."); } assert( !(requiresMacosSigning && requiresWindowsSigning), "macOS and Windows signing readiness must be checked in their native jobs.", ); assert( desktopReleasePlatform === "macos" || desktopReleasePlatform === "windows", "DESKTOP_RELEASE_PLATFORM must be macos or windows.", ); assert( desktopPlatformSigningMode === desktopReleaseResolution.platformSigningMode, `DESKTOP_PLATFORM_SIGNING_MODE must be ${desktopReleaseResolution.platformSigningMode} for v${packageInfo.version}.`, ); assert( desktopReleasePolicy.updaterSigningRequired === true && requiresUpdaterSigning, "Release readiness requires REQUIRE_UPDATER_SIGNING=true; updater signing cannot be disabled.", ); for (const name of requiredUpdaterEnv) { requireEnv(name); } if (desktopPlatformSigningMode === "signed") { assert(!allowsUnsignedPlatformRelease, "Signed releases must not enable ALLOW_UNSIGNED_PLATFORM_RELEASE."); assert( (desktopReleasePlatform === "macos" && requiresMacosSigning && !requiresWindowsSigning) || (desktopReleasePlatform === "windows" && requiresWindowsSigning && !requiresMacosSigning), "Signed readiness must enable only the matching native platform signing requirement.", ); } if (desktopPlatformSigningMode === "unsigned-exception") { assert( desktopReleaseResolution.platformSigningMode === "unsigned-exception", `v${packageInfo.version} has no approved unsigned platform release exception.`, ); assert(allowsUnsignedPlatformRelease, "The approved exception requires ALLOW_UNSIGNED_PLATFORM_RELEASE=true."); assert( !requiresMacosSigning && !requiresWindowsSigning, "Unsigned platform readiness must not claim Apple or Windows platform signing.", ); if (desktopReleasePlatform === "macos") { assert(process.platform === "darwin", "The macOS ad-hoc readiness check must run on macOS."); } if (desktopReleasePlatform === "windows") { assert(process.platform === "win32", "The unsigned Windows readiness check must run on Windows."); } } if (requiresMacosSigning) { assert(process.platform === "darwin", "Signed macOS desktop release readiness must run on macOS."); for (const name of requiredMacosEnv) { requireEnv(name); } assert( Boolean(env.APPLE_CERTIFICATE || env.APPLE_SIGNING_IDENTITY), "APPLE_CERTIFICATE or APPLE_SIGNING_IDENTITY must be configured for macOS signing.", ); if (env.APPLE_CERTIFICATE) { requireEnv("APPLE_CERTIFICATE_PASSWORD"); } } if (requiresWindowsSigning) { assert(process.platform === "win32", "Signed Windows desktop release readiness must run on Windows."); for (const name of requiredWindowsEnv) { requireEnv(name); } validateWindowsTimestampUrl(); } validateBaseUrl(); if (failures.length > 0) { console.error(`Desktop release readiness check failed:\n${failures.map((item) => ` - ${item}`).join("\n")}`); process.exitCode = 1; } else { console.log("Desktop release readiness check passed."); }