fall back to reinstall when a cask cannot be upgraded as-is

Self-updating apps (Zen, Chrome, …) drift from what brew installed;
brew then refuses to upgrade the cask in place, prints a warning and
does nothing, so the package stayed outdated forever. Detect the
warning and run the fix brew itself suggests (reinstall --cask
--force). Single-package upgrades now also route casks through
upgrade --cask instead of a bare brew upgrade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
maxsoch 2026-07-13 18:58:32 +02:00
parent c4baca221d
commit 2931b80074

View file

@ -368,12 +368,28 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
statusMenuItem.title = "Upgrading \(next)..." statusMenuItem.title = "Upgrading \(next)..."
// an explicitly named cask upgrades even when self-updating, no --greedy needed upgradeCask(next) {
runBrew("upgrade --cask \(next)", askpassMessage: "upgrade \(next)") { _, _ in
self.upgradeCasksSequentially(Array(names.dropFirst()), completion: completion) self.upgradeCasksSequentially(Array(names.dropFirst()), completion: completion)
} }
} }
/// Self-updating apps (Zen, Chrome, ) can drift from what brew
/// installed, and brew then refuses to upgrade the cask in place
/// it only prints a warning naming the fix. Run that fix for it.
/// (An explicitly named cask upgrades even when self-updating, no
/// --greedy needed.)
func upgradeCask(_ name: String, completion: @escaping () -> Void) {
runBrew("upgrade --cask \(name)", askpassMessage: "upgrade \(name)") { output, _ in
guard output.contains("cannot be upgraded as-is") else {
completion()
return
}
self.runBrew("reinstall --cask --force \(name)", askpassMessage: "reinstall \(name)") { _, _ in
completion()
}
}
}
func confirmUpgrade(formulae: Bool, casks: Bool) -> Bool { func confirmUpgrade(formulae: Bool, casks: Bool) -> Bool {
let packages = (formulae ? cachedOutdated.formulae : []) + (casks ? cachedOutdated.casks : []) let packages = (formulae ? cachedOutdated.formulae : []) + (casks ? cachedOutdated.casks : [])
@ -524,13 +540,21 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
@objc func upgradeSingle(_ sender: NSMenuItem) { @objc func upgradeSingle(_ sender: NSMenuItem) {
guard let formula = sender.representedObject as? String else { return } guard let name = sender.representedObject as? String else { return }
runBrew("upgrade \(formula)", askpassMessage: "upgrade \(formula)") { _, _ in let finish = {
self.cleanupAfterUpgradeIfEnabled { self.cleanupAfterUpgradeIfEnabled {
self.refreshAll() self.refreshAll()
} }
} }
// casks go through upgradeCask for the reinstall fallback; a bare
// "brew upgrade <name>" would hit the same warning and do nothing
if cachedOutdated.casks.contains(where: { $0.name == name }) {
upgradeCask(name, completion: finish)
} else {
runBrew("upgrade \(name)", askpassMessage: "upgrade \(name)") { _, _ in finish() }
}
} }
/// Refreshing offline would silently succeed on stale data: brew update /// Refreshing offline would silently succeed on stale data: brew update