Skip auto-update for dev builds

`make install` now stamps "dev" as the local SHA256, which tells the
auto-updater to skip update checks entirely. DMG-based installs
continue to work normally with real SHA256 comparison. Clicking
"Check for Updates" on a dev build shows a clear "Dev Build" message
instead of prompting to downgrade.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leopere 2026-02-08 15:51:09 -05:00
parent c14ab51724
commit 9746499231
Signed by: colin
SSH Key Fingerprint: SHA256:nRPCQTeMFLdGytxRQmPVK9VXY3/ePKQ5lGRyJhT5DY8
4 changed files with 28 additions and 15 deletions

View File

@ -53,7 +53,9 @@ install: bundle
@rm -rf /Applications/$(APP_NAME).app
@cp -R $(APP_BUNDLE) /Applications/$(APP_NAME).app
@xattr -cr /Applications/$(APP_NAME).app
@echo "Installed /Applications/$(APP_NAME).app"
@mkdir -p "$(HOME)/Library/Application Support/Pommedoro"
@echo "dev" > "$(HOME)/Library/Application Support/Pommedoro/current.sha256"
@echo "Installed /Applications/$(APP_NAME).app (dev build auto-update disabled)"
run: bundle
@open $(APP_BUNDLE)

View File

@ -9,6 +9,10 @@ enum AutoUpdater {
// MARK: - Configuration
/// Sentinel value written by `make install` to indicate a dev build.
/// The updater skips automatic updates when this is the local stamp.
private static let devSentinel = "dev"
/// Raw URL for the SHA256 file in the repo (Gitea raw endpoint)
private static let remoteSHA256URL = URL(
string: "https://git.nixc.us/colin/pommedoro/raw/branch/main/releases/Pommedoro.dmg.sha256"
@ -49,48 +53,56 @@ enum AutoUpdater {
// MARK: - Core Logic
private static func performCheck(silent: Bool) {
// 1. Fetch remote SHA256
// 1. Read local SHA256 (written at last install/build)
let localSHA = readLocalSHA()
// 2. Dev builds are stamped "dev" skip silent checks entirely
if localSHA == devSentinel {
if !silent {
showAlert(title: "Dev Build", message: "Auto-update is disabled for local dev builds.")
}
return
}
// 3. Fetch remote SHA256
guard let remoteSHA = fetchRemoteSHA() else {
if !silent { showAlert(title: "Update Check Failed", message: "Could not reach the update server.") }
return
}
// 2. Read local SHA256 (written at last install/build)
let localSHA = readLocalSHA()
// 3. Compare
// 4. Compare
if remoteSHA == localSHA {
if !silent { showAlert(title: "Up to Date", message: "You are running the latest version.") }
return
}
// 4. There's an update prompt user
// 5. There's an update prompt user
let shouldUpdate = promptForUpdate()
guard shouldUpdate else { return }
// 5. Download new DMG
// 6. Download new DMG
guard downloadDMG() else {
showAlert(title: "Update Failed", message: "Could not download the update.")
return
}
// 6. Verify downloaded DMG matches remote SHA
// 7. Verify downloaded DMG matches remote SHA
guard let downloadedSHA = sha256(of: downloadedDMGPath), downloadedSHA == remoteSHA else {
showAlert(title: "Update Failed", message: "Downloaded file integrity check failed.")
try? FileManager.default.removeItem(at: downloadedDMGPath)
return
}
// 7. Extract new app from DMG to staging area
// 8. Extract new app from DMG to staging area
guard stageFromDMG() else {
showAlert(title: "Update Failed", message: "Could not extract the update.")
return
}
// 8. Save new SHA so we don't re-update next launch
// 9. Save new SHA so we don't re-update next launch
try? remoteSHA.write(to: localSHAPath, atomically: true, encoding: .utf8)
// 9. Spawn swap script, then terminate so applicationWillTerminate saves state
// 10. Spawn swap script, then terminate so applicationWillTerminate saves state
spawnSwapAndRelaunch()
DispatchQueue.main.async {
@ -142,7 +154,7 @@ enum AutoUpdater {
// MARK: - SHA256 Computation
private static func sha256(of fileURL: URL) -> String? {
static func sha256(of fileURL: URL) -> String? {
guard let handle = try? FileHandle(forReadingFrom: fileURL) else { return nil }
defer { handle.closeFile() }
@ -242,7 +254,6 @@ enum AutoUpdater {
proc.arguments = [scriptPath]
proc.standardOutput = FileHandle.nullDevice
proc.standardError = FileHandle.nullDevice
// Detach from our process group so it survives our exit
proc.qualityOfService = .utility
try? proc.run()
}

Binary file not shown.

View File

@ -1 +1 @@
7e15ab9b42481c28c9566c7d71bad03558bb2cdec7dcceb956443caf5cb1724a
98fa348545458a05d36e15656fdd788564e309784c4536a060499f0827141260