修好:Mac 自我更新斷七代(dmg 餵給解 zip 的 ditto)+殘留 staged 旗標騙人重啟
症狀一:manifest.daemon.mac.file 出貨格式從 zip 換成 dmg(build-dmg.sh), 但 applyStagedAndRestart 只認 zip(ditto -x -k)⇒ ditto: Couldn't read PKZip signature,Mac「檢查更新」從 v0.18.5 斷到 v0.18.22(leo 真機截圖實測)。 症狀二:手動裝好新版後仍顯示「已下載完成,重新啟動就會套用」——staged.json 判準是「有沒有下載過」而非「目前版本 vs 最新版本」。 - extractAppFrom 依副檔名分流 .dmg(hdiutil attach -mountpoint)/.zip; Windows 補上真正的自我更新(rename 正在跑的 exe → 搬新版 → 原路徑重啟, 併檔後 Windows 只有單一 exe,「開資料夾」是併檔前的殘留假設)。 - CheckUpdate 加 stagedDecision 純函式:已追上或 staged 版本過期就清掉 殘留(連磁碟檔案一起清,不只清記憶體)。 - 新增 selfupdate_test.go:用真的 hdiutil 造 dmg 驗證解壓(不是 mock)。 Mac 端到端已用真的線上 manifest/真 v0.18.22 dmg 驗證整條鏈路(含用舊寫法 對同一份真檔案重現原始錯誤,證明修法對症)。Windows 只驗到 GOOS=windows 交叉編譯成功,真機行為待 leo 用 VMware Fusion 驗證——詳見 status.md。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ package main
|
||||
// ② DownloadUpdate 下載+sha256 校驗 → 備妥
|
||||
// ③ ApplyUpdate 覆蓋正在跑的 .app 並重啟
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
@@ -35,12 +36,46 @@ func (a *App) CheckUpdate() UpdateInfo {
|
||||
info.Latest = rel.Version
|
||||
info.Notes = rel.Notes
|
||||
info.Available = newerThanCurrent(rel.Version)
|
||||
if s := loadStaged(); s.Ready && s.Version == rel.Version {
|
||||
info.Staged = true
|
||||
// 🔴 2026-08-08(leo 真機實測):手動裝好新版後按「檢查更新」,
|
||||
// 仍顯示「新版已下載完成,重新啟動就會套用」——因為背景/上次曾自動下載備妥過
|
||||
// 同一版,使用者卻是自己手動裝的,staged.json 沒人清,殘留狀態一直騙下去。
|
||||
// 判準要是「當前版本 vs 最新版本」,不是「有沒有下載過」——見 stagedDecision。
|
||||
if s := loadStaged(); s.Ready {
|
||||
report, clear := stagedDecision(info.Available, s.Version, rel.Version)
|
||||
if clear {
|
||||
clearStaleStaged()
|
||||
}
|
||||
info.Staged = report
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// stagedDecision 決定「已備妥」的殘留旗標該回報成「已就緒,可重啟套用」還是該被清掉。
|
||||
// 抽成純函式(無 IO)方便單元測試涵蓋 08-08 那個真機撞到的殘留旗標病,不必真的碰磁碟/網路。
|
||||
//
|
||||
// available=目前版本是否落後 latest(newerThanCurrent 的結果)
|
||||
// stagedVersion/latestVersion=已備妥的版本 vs 這次查到的最新版本
|
||||
//
|
||||
// 只有「目前版本落後、且備妥的正是這次的最新版」才回報 Staged=true;
|
||||
// 其餘情況(已經追上/備妥的是舊版)都是殘留,要清掉不能再誤導使用者。
|
||||
func stagedDecision(available bool, stagedVersion, latestVersion string) (report bool, clear bool) {
|
||||
if !available || stagedVersion != latestVersion {
|
||||
return false, true
|
||||
}
|
||||
return true, false
|
||||
}
|
||||
|
||||
// clearStaleStaged 清掉「已備妥但已經不需要套用」的殘留(暫存檔+標記),
|
||||
// 不只是記憶體裡的 currentStaged——否則下次啟動 loadStaged() 又會撿回同一個誤導。
|
||||
func clearStaleStaged() {
|
||||
s := loadStaged()
|
||||
if s.ZipPath != "" {
|
||||
_ = os.Remove(s.ZipPath)
|
||||
}
|
||||
_ = os.Remove(stagedMarkerPath())
|
||||
currentStaged = stagedUpdate{}
|
||||
}
|
||||
|
||||
func (a *App) DownloadUpdate() UpdateInfo {
|
||||
info := UpdateInfo{Current: version}
|
||||
rel, err := fetchLatestRelease()
|
||||
|
||||
Reference in New Issue
Block a user