#!/usr/bin/env bun /** * Merge per-arch electron-builder update manifests into one latest-mac.yml. * * The desktop publish workflow builds mac arm64 and x64 in separate matrix * legs, and each electron-builder invocation emits its own latest-mac.yml * listing only that arch's artifacts. electron-updater clients fetch the * single `latest-mac.yml` from the release, so publishing either leg's file * as-is points every Mac at one arch (arm64 users were being served x64 * zips). This script unions the `files` entries; electron-updater picks the * right one per client by matching the running arch against the file name. * * Usage: bun scripts/merge-latest-mac-yml.ts * The first input's top-level path/sha512 (the legacy single-file fields read * by very old updaters) are kept, so pass the x64 manifest first. */ interface UpdateFileEntry { readonly url: string; readonly sha512: string; readonly size: number; } interface UpdateManifest { readonly version: string; readonly files: readonly UpdateFileEntry[]; readonly path: string; readonly sha512: string; readonly releaseDate: string; } const isUpdateFileEntry = (value: unknown): value is UpdateFileEntry => { if (typeof value !== "object" || value === null) return false; const entry = value as Record; return ( typeof entry["url"] === "string" && typeof entry["sha512"] === "string" && typeof entry["size"] === "number" ); }; const parseManifest = (source: string, path: string): UpdateManifest => { const raw = Bun.YAML.parse(source) as Record; const files = Array.isArray(raw["files"]) ? raw["files"].filter(isUpdateFileEntry) : []; if ( typeof raw["version"] !== "string" || typeof raw["path"] !== "string" || typeof raw["sha512"] !== "string" || typeof raw["releaseDate"] !== "string" || files.length === 0 ) { throw new Error(`Unrecognized electron-builder update manifest at ${path}`); } return { version: raw["version"], files, path: raw["path"], sha512: raw["sha512"], releaseDate: raw["releaseDate"], }; }; const serializeManifest = (manifest: UpdateManifest): string => [ `version: ${manifest.version}`, "files:", ...manifest.files.flatMap((file) => [ ` - url: ${file.url}`, ` sha512: ${file.sha512}`, ` size: ${file.size}`, ]), `path: ${manifest.path}`, `sha512: ${manifest.sha512}`, `releaseDate: '${manifest.releaseDate}'`, "", ].join("\n"); const [primaryPath, secondaryPath, outPath] = process.argv.slice(2); if (!primaryPath || !secondaryPath || !outPath) { throw new Error("Usage: merge-latest-mac-yml.ts "); } const primary = parseManifest(await Bun.file(primaryPath).text(), primaryPath); const secondary = parseManifest(await Bun.file(secondaryPath).text(), secondaryPath); if (primary.version !== secondary.version) { throw new Error( `Refusing to merge mismatched versions: ${primary.version} (${primaryPath}) vs ${secondary.version} (${secondaryPath})`, ); } const seen = new Set(); const files = [...primary.files, ...secondary.files].filter((file) => { if (seen.has(file.url)) return false; seen.add(file.url); return true; }); await Bun.write(outPath, serializeManifest({ ...primary, files })); console.log(`Merged ${primaryPath} + ${secondaryPath} → ${outPath} (${files.length} files)`);