import { access, readFile } from "node:fs/promises"; import { basename, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const frontendDir = fileURLToPath(new URL("../", import.meta.url)); const packageInfo = JSON.parse(await readFile(resolve(frontendDir, "package.json"), "utf8")); const failures = []; const args = process.argv.slice(2); const optionValue = (name) => { const index = args.indexOf(name); return index >= 0 ? args[index + 1] : undefined; }; const feedPath = resolve( frontendDir, optionValue("--feed") || process.env.DESKTOP_UPDATE_FEED || "src-tauri/target/release/bundle/latest.json", ); const artifactDir = optionValue("--artifacts-dir") || process.env.DESKTOP_UPDATE_ARTIFACTS_DIR; const expectedBaseUrl = optionValue("--base-url") || process.env.DESKTOP_UPDATE_BASE_URL; const fail = (message) => failures.push(message); const assert = (condition, message) => { if (!condition) fail(message); }; const assertFileExists = async (path, description) => { try { await access(path); } catch { fail(`${description} does not exist: ${path}`); } }; let feed; try { feed = JSON.parse(await readFile(feedPath, "utf8")); } catch (error) { fail(`Cannot read desktop update feed ${feedPath}: ${error.message}`); } if (feed) { const normalizedFeedVersion = String(feed.version || "").replace(/^v/, ""); const platforms = feed.platforms || {}; const darwinArm = platforms["darwin-aarch64"]; const darwinIntel = platforms["darwin-x86_64"]; assert(normalizedFeedVersion === packageInfo.version, `latest.json version must match package version ${packageInfo.version}.`); assert(Boolean(feed.pub_date || feed.pubDate), "latest.json must include a publication date."); assert(Boolean(darwinArm), "latest.json must include darwin-aarch64."); assert(Boolean(darwinIntel), "latest.json must include darwin-x86_64."); const entries = [ ["darwin-aarch64", darwinArm], ["darwin-x86_64", darwinIntel], ]; for (const [platform, entry] of entries) { const rawUrl = entry?.url; const signature = entry?.signature; assert(typeof signature === "string" && signature.length > 80, `${platform} must include an updater signature.`); assert(typeof rawUrl === "string" && rawUrl.length > 0, `${platform} must include an artifact URL.`); if (!rawUrl) continue; let url; try { url = new URL(rawUrl); } catch { fail(`${platform} artifact URL is invalid: ${rawUrl}`); continue; } assert(url.protocol === "https:", `${platform} artifact URL must use HTTPS.`); assert(!/[?&]token=/i.test(url.search), `${platform} artifact URL must not contain token query parameters.`); assert(!url.pathname.endsWith("/latest.json"), `${platform} artifact URL must point to an immutable artifact, not latest.json.`); assert(url.pathname.includes(packageInfo.version), `${platform} artifact URL must include version ${packageInfo.version}.`); if (expectedBaseUrl) { assert(rawUrl.startsWith(expectedBaseUrl), `${platform} artifact URL must start with ${expectedBaseUrl}.`); } if (artifactDir) { const artifactPath = resolve(artifactDir, basename(url.pathname)); await assertFileExists(artifactPath, `${platform} updater artifact`); await assertFileExists(`${artifactPath}.sig`, `${platform} updater artifact signature`); } } if (darwinArm?.url && darwinIntel?.url) { assert(darwinArm.url === darwinIntel.url, "Universal macOS latest.json must point both darwin architectures at the same artifact."); } } if (failures.length > 0) { console.error(`Desktop update feed check failed:\n${failures.map((item) => ` - ${item}`).join("\n")}`); process.exitCode = 1; } else { console.log("Desktop update feed check passed."); }