MUL-4164: support Intel macOS Desktop packages (#5436)

* feat(desktop): support Intel macOS packages

Co-authored-by: multica-agent <github@multica.ai>

* fix(desktop): scope Intel macOS rollout

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Multica Eve
2026-07-15 12:58:47 +08:00
committed by GitHub
parent 2c482ab366
commit 5b26b99722
4 changed files with 123 additions and 10 deletions

View File

@@ -66,6 +66,7 @@ const ARCH_FLAGS = new Map([
const SUPPORTED_CLI_ARCHS = new Set(["x64", "arm64"]);
const MAC_ALL_PLATFORM_TARGETS = [
{ platform: "mac", arch: "arm64" },
{ platform: "mac", arch: "x64" },
{ platform: "win", arch: "x64" },
{ platform: "win", arch: "arm64" },
{ platform: "linux", arch: "x64" },
@@ -338,18 +339,21 @@ export function builderArgsForTarget(
`-c.directories.output=dist/${target.platform}-${target.arch}`,
);
}
// electron-builder's update metadata file is `latest.yml` for Windows
// regardless of arch (only Linux gets an arch suffix automatically — see
// app-builder-lib's getArchPrefixForUpdateFile). Without an explicit
// channel override, building Windows x64 and arm64 in two invocations
// makes both publish `latest.yml` to the same GitHub Release, so the
// second upload overwrites the first and one of the two architectures
// ends up with no auto-update metadata. Route Windows arm64 to its own
// channel so x64 keeps `latest.yml` and arm64 ships `latest-arm64.yml`;
// the renderer-side updater pins the matching channel per arch.
// electron-builder only adds an architecture suffix to Linux update
// metadata. Windows x64/arm64 would both publish `latest.yml`, while macOS
// arm64/x64 would both publish `latest-mac.yml`. Keep the established x64
// Windows and arm64 macOS feeds unchanged for installed clients, and route
// the additional architectures to explicit channels. updater.ts pins the
// matching channel at runtime.
if (target.platform === "win" && target.arch === "arm64") {
builderArgs.push("-c.publish.channel=latest-arm64");
}
if (target.platform === "mac" && target.arch === "x64") {
// Scope the Electron 39 platform floor to the new Intel package so this
// change does not rewrite established Apple Silicon bundle metadata.
builderArgs.push("-c.mac.minimumSystemVersion=12.0.0");
builderArgs.push("-c.publish.channel=latest-x64");
}
return builderArgs;
}

View File

@@ -241,6 +241,7 @@ describe("resolveBuildMatrix", () => {
),
).toEqual([
{ platform: "mac", arch: "arm64" },
{ platform: "mac", arch: "x64" },
{ platform: "win", arch: "x64" },
{ platform: "win", arch: "arm64" },
{ platform: "linux", arch: "x64" },
@@ -322,6 +323,58 @@ describe("builderArgsForTarget", () => {
]);
});
it("isolates the macOS x64 feed and platform floor", () => {
expect(
builderArgsForTarget(
{ platform: "mac", arch: "x64" },
{
allPlatforms: false,
sharedArgs: ["--publish", "always"],
platformTargets: { mac: ["dmg", "zip"], win: [], linux: [] },
requestedPlatforms: ["mac"],
requestedArchs: ["x64"],
},
"1.2.3",
{ hostPlatform: "darwin", useScopedOutputDir: true },
),
).toEqual([
"-c.extraMetadata.version=1.2.3",
"--mac",
"dmg",
"zip",
"--x64",
"--publish",
"always",
"-c.directories.output=dist/mac-x64",
"-c.mac.minimumSystemVersion=12.0.0",
"-c.publish.channel=latest-x64",
]);
});
it("keeps macOS arm64 on the existing latest-mac update channel", () => {
expect(
builderArgsForTarget(
{ platform: "mac", arch: "arm64" },
{
allPlatforms: false,
sharedArgs: ["--publish", "always"],
platformTargets: { mac: [], win: [], linux: [] },
requestedPlatforms: ["mac"],
requestedArchs: ["arm64"],
},
"1.2.3",
{ hostPlatform: "darwin", useScopedOutputDir: true },
),
).toEqual([
"-c.extraMetadata.version=1.2.3",
"--mac",
"--arm64",
"--publish",
"always",
"-c.directories.output=dist/mac-arm64",
]);
});
it("defaults linux cross-builds to AppImage on non-Linux hosts", () => {
expect(
builderArgsForTarget(

View File

@@ -26,6 +26,7 @@ vi.mock("electron-updater", () => {
autoDownload: false,
autoInstallOnAppQuit: false,
channel: undefined as string | undefined,
allowDowngrade: false,
on: vi.fn((event: string, handler: Handler) => {
const handlers = ctx.handlers.get(event) ?? [];
handlers.push(handler);
@@ -50,9 +51,40 @@ vi.mock("electron", () => ({
},
}));
import { setupAutoUpdater } from "./updater";
import {
configureMacX64UpdateChannel,
setupAutoUpdater,
} from "./updater";
import { updaterPreferencesPath } from "./updater-preferences";
describe("macOS x64 update channel", () => {
it("does not touch established architecture paths", () => {
for (const [platform, arch] of [
["darwin", "arm64"],
["win32", "x64"],
["win32", "arm64"],
["linux", "arm64"],
] as const) {
const updater = { channel: null, allowDowngrade: true };
configureMacX64UpdateChannel(updater, platform, arch);
expect(updater).toEqual({ channel: null, allowDowngrade: true });
}
});
it("does not enable downgrades when selecting an architecture feed", () => {
const updater = { channel: null, allowDowngrade: true };
configureMacX64UpdateChannel(updater, "darwin", "x64");
expect(updater).toEqual({
channel: "latest-x64",
allowDowngrade: false,
});
});
});
function emitUpdater(event: string, ...args: unknown[]) {
for (const handler of ctx.handlers.get(event) ?? []) {
handler(...args);

View File

@@ -27,6 +27,30 @@ if (process.platform === "win32" && process.arch === "arm64") {
autoUpdater.channel = "latest-arm64";
}
interface ChannelConfigurableUpdater {
channel: string | null;
allowDowngrade: boolean;
}
export function configureMacX64UpdateChannel(
updater: ChannelConfigurableUpdater,
platform: NodeJS.Platform = process.platform,
arch: string = process.arch,
): void {
if (platform !== "darwin" || arch !== "x64") return;
// AppUpdater.channel enables allowDowngrade as a side effect. This channel
// isolates a CPU architecture, not a release train, so preserve normal
// monotonic version behavior after selecting the architecture feed.
updater.channel = "latest-x64";
updater.allowDowngrade = false;
}
// electron-builder does not architecture-suffix macOS update metadata.
// package.mjs publishes macOS x64 as `latest-x64-mac.yml`; the established
// arm64 feed and runtime path remain unchanged.
configureMacX64UpdateChannel(autoUpdater);
const STARTUP_CHECK_DELAY_MS = 5_000;
const PERIODIC_CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour