Skip to content

Commit db86644

Browse files
7418claude
andcommitted
feat: macOS 双架构分离构建 — x64 只签名,arm64 签名+条件公证
CI: - 拆分为 x64 和 arm64 两个独立构建步骤 - x64: 签名但不公证(-c.mac.notarize=false),timeout 15min - arm64: 签名 + 按版本号条件公证(vX.Y.0 公证,vX.Y.Z 只签名),timeout 35min - 先跑 x64,stash 产物后跑 arm64,确保 latest-mac.yml 来自 arm64 Electron: - electron/main.ts: darwin+x64 跳过 initAutoUpdater - electron/preload.ts: darwin+x64 不暴露 updater bridge - 前端 AppShell 已有运行时检测(!!window.electronAPI?.updater), x64 自动 fallback 到 browser 模式更新检查 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2c76b04 commit db86644

File tree

3 files changed

+56
-21
lines changed

3 files changed

+56
-21
lines changed

.github/workflows/build.yml

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ on:
1414
- macos
1515
- linux
1616
mac_notarize:
17-
description: "mac notarization mode"
17+
description: "mac notarization mode (arm64 only)"
1818
required: false
1919
default: "auto"
2020
type: choice
@@ -88,7 +88,7 @@ jobs:
8888
printf '%s' "$MAC_CERT_P12_BASE64" | tr -d '\r\n' | base64 --decode > /tmp/cert.p12
8989
openssl pkcs12 -in /tmp/cert.p12 -passin pass:"$MAC_CERT_PASSWORD" -noout
9090
91-
- name: Resolve mac notarization strategy
91+
- name: Resolve arm64 notarization strategy
9292
id: mac_mode
9393
shell: bash
9494
env:
@@ -115,7 +115,7 @@ jobs:
115115
116116
echo "mode=$MODE" >> "$GITHUB_OUTPUT"
117117
echo "notarize=$NOTARIZE" >> "$GITHUB_OUTPUT"
118-
echo "Resolved mac notarization: $NOTARIZE (mode=$MODE, ref=$REF_NAME)"
118+
echo "Resolved arm64 notarization: $NOTARIZE (mode=$MODE, ref=$REF_NAME)"
119119
120120
- name: Validate notarization credentials
121121
if: ${{ env.HAS_CERT == 'true' && steps.mac_mode.outputs.notarize == 'true' }}
@@ -130,7 +130,32 @@ jobs:
130130
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
131131
--team-id "$APPLE_TEAM_ID" >/dev/null
132132
133-
- name: Package for macOS (x64 + arm64)
133+
# ── x64: sign only, no notarization ──────────────────────────────────
134+
- name: Package for macOS (x64, sign only)
135+
timeout-minutes: 15
136+
env:
137+
CSC_LINK: ${{ env.HAS_CERT == 'true' && '/tmp/cert.p12' || '' }}
138+
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }}
139+
run: |
140+
set -euo pipefail
141+
for i in 1 2 3; do
142+
echo "electron-builder x64 attempt $i/3 (notarize=false)"
143+
if npx electron-builder --mac --x64 --config electron-builder.yml -c.mac.notarize=false --publish never; then
144+
exit 0
145+
fi
146+
if [ "$i" -lt 3 ]; then sleep $((i * 30)); fi
147+
done
148+
exit 1
149+
150+
# Move x64 artifacts aside so arm64 build produces clean latest-mac.yml
151+
- name: Stash x64 artifacts
152+
run: |
153+
mkdir -p release/x64-stash
154+
mv release/*.dmg release/*.zip release/*.blockmap release/x64-stash/ 2>/dev/null || true
155+
rm -f release/latest-mac.yml
156+
157+
# ── arm64: sign + conditional notarization ───────────────────────────
158+
- name: Package for macOS (arm64)
134159
timeout-minutes: 35
135160
env:
136161
CSC_LINK: ${{ env.HAS_CERT == 'true' && '/tmp/cert.p12' || '' }}
@@ -148,16 +173,18 @@ jobs:
148173
fi
149174
150175
for i in 1 2 3; do
151-
echo "electron-builder attempt $i/3 (notarize=${{ steps.mac_mode.outputs.notarize }})"
152-
if npx electron-builder --mac --config electron-builder.yml "${EXTRA_ARGS[@]}" --publish never; then
176+
echo "electron-builder arm64 attempt $i/3 (notarize=${{ steps.mac_mode.outputs.notarize }})"
177+
if npx electron-builder --mac --arm64 --config electron-builder.yml "${EXTRA_ARGS[@]}" --publish never; then
153178
exit 0
154179
fi
155-
if [ "$i" -lt 3 ]; then
156-
sleep $((i * 60))
157-
fi
180+
if [ "$i" -lt 3 ]; then sleep $((i * 60)); fi
158181
done
159182
exit 1
160183
184+
# Restore x64 artifacts alongside arm64 output
185+
- name: Restore x64 artifacts
186+
run: mv release/x64-stash/* release/ && rmdir release/x64-stash
187+
161188
- name: Verify code signature
162189
if: ${{ env.HAS_CERT == 'true' }}
163190
run: |
@@ -206,7 +233,9 @@ jobs:
206233
- name: Print mac release mode
207234
run: |
208235
echo "mac_notarize_mode=${{ steps.mac_mode.outputs.mode }}"
209-
echo "mac_notarize=${{ steps.mac_mode.outputs.notarize }}"
236+
echo "arm64_notarize=${{ steps.mac_mode.outputs.notarize }}"
237+
echo "x64_notarize=false (always sign-only)"
238+
echo "latest-mac.yml comes from arm64 build"
210239
211240
- name: Upload artifacts
212241
uses: actions/upload-artifact@v4

electron/main.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -938,8 +938,10 @@ app.whenReady().then(async () => {
938938
autoStartReq.end();
939939
}
940940

941-
// Initialize auto-updater in packaged mode only
942-
if (!isDev && mainWindow) {
941+
// Initialize auto-updater in packaged mode only.
942+
// Disabled on macOS x64 (Intel) — those users get browser-mode update check.
943+
const skipNativeUpdater = process.platform === 'darwin' && process.arch === 'x64';
944+
if (!isDev && mainWindow && !skipNativeUpdater) {
943945
initAutoUpdater(mainWindow);
944946
}
945947
} catch (err) {

electron/preload.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,18 @@ contextBridge.exposeInMainWorld('electronAPI', {
2828
bridge: {
2929
isActive: () => ipcRenderer.invoke('bridge:is-active'),
3030
},
31-
updater: {
32-
checkForUpdates: () => ipcRenderer.invoke('updater:check'),
33-
downloadUpdate: () => ipcRenderer.invoke('updater:download'),
34-
quitAndInstall: () => ipcRenderer.invoke('updater:quit-and-install'),
35-
onStatus: (callback: (data: unknown) => void) => {
36-
const listener = (_event: unknown, data: unknown) => callback(data);
37-
ipcRenderer.on('updater:status', listener);
38-
return () => { ipcRenderer.removeListener('updater:status', listener); };
31+
// Native updater: disabled on macOS x64 (Intel) — those users get browser-mode
32+
// update check (download link to GitHub Releases) instead.
33+
...(process.platform === 'darwin' && process.arch === 'x64' ? {} : {
34+
updater: {
35+
checkForUpdates: () => ipcRenderer.invoke('updater:check'),
36+
downloadUpdate: () => ipcRenderer.invoke('updater:download'),
37+
quitAndInstall: () => ipcRenderer.invoke('updater:quit-and-install'),
38+
onStatus: (callback: (data: unknown) => void) => {
39+
const listener = (_event: unknown, data: unknown) => callback(data);
40+
ipcRenderer.on('updater:status', listener);
41+
return () => { ipcRenderer.removeListener('updater:status', listener); };
42+
},
3943
},
40-
},
44+
}),
4145
});

0 commit comments

Comments
 (0)