Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Sources/LockIME/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,12 @@ final class AppState {
alert.alertStyle = .warning
alert.messageText = loc("Update failed")
alert.informativeText = loc(failure.messageKey)
#if DEBUG
case .disabledInDevelopment:
alert.alertStyle = .informational
alert.messageText = loc("Development build")
alert.informativeText = loc("Automatic updates are disabled in development builds. To test the update flow, use the make update-test-* lab.")
#endif
}
alert.addButton(withTitle: loc("OK"))
NSApp.activate(ignoringOtherApps: true)
Expand Down
104 changes: 104 additions & 0 deletions Sources/LockIME/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -6345,6 +6345,110 @@
}
}
}
},
"Development build": {
"localizations": {
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "开发版本"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "開發版本"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "開発ビルド"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Version de développement"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Entwicklungs-Build"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Compilación de desarrollo"
}
},
"pt": {
"stringUnit": {
"state": "translated",
"value": "Compilação de desenvolvimento"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Сборка для разработки"
}
}
}
},
"Automatic updates are disabled in development builds. To test the update flow, use the make update-test-* lab.": {
"localizations": {
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "开发构建已禁用自动更新。如需测试更新流程,请使用 make update-test-* 实验链路。"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "開發建置已停用自動更新。如需測試更新流程,請使用 make update-test-* 實驗鏈路。"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "開発ビルドでは自動更新が無効になっています。更新フローをテストするには、make update-test-* のラボを使用してください。"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "Les mises à jour automatiques sont désactivées dans les versions de développement. Pour tester le flux de mise à jour, utilisez le banc d'essai make update-test-*."
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "Automatische Updates sind in Entwicklungs-Builds deaktiviert. Um den Update-Ablauf zu testen, verwende das make update-test-*-Labor."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "Las actualizaciones automáticas están deshabilitadas en las compilaciones de desarrollo. Para probar el flujo de actualización, usa el laboratorio make update-test-*."
}
},
"pt": {
"stringUnit": {
"state": "translated",
"value": "As atualizações automáticas estão desativadas em compilações de desenvolvimento. Para testar o fluxo de atualização, use o laboratório make update-test-*."
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Автоматические обновления отключены в сборках для разработки. Чтобы протестировать процесс обновления, используйте лабораторию make update-test-*."
}
}
}
}
}
}
35 changes: 35 additions & 0 deletions Sources/LockIME/Updates/UpdateController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import Sparkle
enum UpdateCheckOutcome {
case upToDate
case failed(UpdateFailure)
#if DEBUG
/// A bare `make run` dev build declined to contact the real production feed
/// (see `UpdateController.updatesDisabledForDevelopment`).
case disabledInDevelopment
#endif
}

/// Owns the Sparkle updater wired to our custom user driver.
Expand Down Expand Up @@ -40,6 +45,20 @@ final class UpdateController {

private(set) var canCheckForUpdates = false

#if DEBUG
/// A bare `make run` dev build must never reach the real production feed or
/// install a stable release over the local build: its version is always
/// `0.0.0-development`, so every check would "find" the newest stable and
/// could replace the build under test. The update lab (`make update-test-*`)
/// is the one exception — it redirects the feed to a loopback server via
/// `LOCKIME_UPDATE_FEED` and deliberately exercises real download/install,
/// so the presence of that env var is exactly what tells the lab apart from
/// a plain run. Release builds never reach this property (compiled out).
private var updatesDisabledForDevelopment: Bool {
(ProcessInfo.processInfo.environment["LOCKIME_UPDATE_FEED"] ?? "").isEmpty
}
#endif

init() {
driver = LockIMEUserDriver(model: model)
driver.onUpdateAvailable = { [weak self] in
Expand All @@ -56,6 +75,16 @@ final class UpdateController {
/// Build and start the updater. Fails gracefully if `SUPublicEDKey` is
/// missing/invalid (updates simply stay unavailable).
func start() {
#if DEBUG
if updatesDisabledForDevelopment {
// Never start Sparkle in a plain dev build: no scheduled check can
// fire and the production feed is never contacted. The manual
// "Check for Updates…" button stays enabled and surfaces a
// "disabled in development" notice instead (see `checkForUpdates`).
canCheckForUpdates = true
return
}
#endif
let updater = SPUUpdater(
hostBundle: .main,
applicationBundle: .main,
Expand Down Expand Up @@ -86,6 +115,12 @@ final class UpdateController {
/// check — nothing is shown until the result is known (an available update
/// opens the window; "up to date"/errors surface as a native alert).
func checkForUpdates() {
#if DEBUG
if updatesDisabledForDevelopment {
onCheckOutcome?(.disabledInDevelopment)
return
}
#endif
if pendingUpdateVersion != nil {
onPresentUpdateWindow?()
} else {
Expand Down
Loading