add auto-upgrade settings, split into formulae and casks
Two independent toggles, both off by default. When a scheduled fetch finds outdated packages, runAutoUpgrade runs "upgrade --formula" and/or "upgrade --cask" (with --greedy if that setting is on), then refetches. The refetch passes allowAutoUpgrade: false — packages can legitimately stay outdated after an upgrade (pinned formulae, greedy casks brew declines), and without the guard that would trigger upgrade → fetch → upgrade forever. Manual Upgrade All's final fetch gets the same guard. The casks checkbox carries a warning note in the settings window: a cask upgrade can close and replace the running app without warning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
bd19bee52e
commit
7c53413e40
|
|
@ -213,7 +213,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
|
||||
runBrew("update") { _ in
|
||||
self.runBrew("upgrade") { _ in
|
||||
self.fetchOutdated()
|
||||
// everything was just upgraded; don't chain an auto-upgrade
|
||||
self.fetchOutdated(allowAutoUpgrade: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -242,7 +243,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
return alert.runModal() == .alertFirstButtonReturn
|
||||
}
|
||||
|
||||
func fetchOutdated() {
|
||||
/// allowAutoUpgrade guards against an upgrade loop: packages can stay
|
||||
/// outdated after an upgrade (pinned formulae, greedy casks brew declines),
|
||||
/// so the fetch that follows an auto-upgrade must not trigger another one.
|
||||
func fetchOutdated(allowAutoUpgrade: Bool = true) {
|
||||
setMenuBarIcon("brewbar-updating")
|
||||
|
||||
// --greedy also lists casks that self-update (brew skips them by default)
|
||||
|
|
@ -253,6 +257,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
|
||||
self.cachedOutdated = lines
|
||||
|
||||
if allowAutoUpgrade, !lines.isEmpty,
|
||||
Settings.autoUpgradeFormulae || Settings.autoUpgradeCasks
|
||||
{
|
||||
self.runAutoUpgrade()
|
||||
return
|
||||
}
|
||||
|
||||
self.updateOutdatedMenu(with: lines)
|
||||
self.updateStatus(count: lines.count)
|
||||
if lines.isEmpty {
|
||||
|
|
@ -263,6 +274,29 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func runAutoUpgrade() {
|
||||
statusMenuItem.title = "Auto-upgrading..."
|
||||
|
||||
let upgradeCasksThenRefresh = {
|
||||
if Settings.autoUpgradeCasks {
|
||||
let command = Settings.includeGreedyCasks ? "upgrade --cask --greedy" : "upgrade --cask"
|
||||
self.runBrew(command) { _ in
|
||||
self.fetchOutdated(allowAutoUpgrade: false)
|
||||
}
|
||||
} else {
|
||||
self.fetchOutdated(allowAutoUpgrade: false)
|
||||
}
|
||||
}
|
||||
|
||||
if Settings.autoUpgradeFormulae {
|
||||
runBrew("upgrade --formula") { _ in
|
||||
upgradeCasksThenRefresh()
|
||||
}
|
||||
} else {
|
||||
upgradeCasksThenRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
func updateOutdatedMenu(with lines: [String]) {
|
||||
outdatedSubmenu.removeAllItems()
|
||||
outdatedItem.isHidden = lines.isEmpty
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ class SettingsWindowController: NSWindowController {
|
|||
var customMinutesField: NSTextField!
|
||||
var launchAtLoginCheckbox: NSButton!
|
||||
var greedyCheckbox: NSButton!
|
||||
var autoUpgradeFormulaeCheckbox: NSButton!
|
||||
var autoUpgradeCasksCheckbox: NSButton!
|
||||
|
||||
convenience init(appDelegate: AppDelegate) {
|
||||
let window = NSWindow(
|
||||
|
|
@ -81,11 +83,40 @@ class SettingsWindowController: NSWindowController {
|
|||
action: #selector(toggleGreedy(_:))
|
||||
)
|
||||
|
||||
autoUpgradeFormulaeCheckbox = NSButton(
|
||||
checkboxWithTitle: "Automatically upgrade formulae (command-line tools)",
|
||||
target: self,
|
||||
action: #selector(toggleAutoUpgradeFormulae(_:))
|
||||
)
|
||||
|
||||
autoUpgradeCasksCheckbox = NSButton(
|
||||
checkboxWithTitle: "Automatically upgrade casks (apps)",
|
||||
target: self,
|
||||
action: #selector(toggleAutoUpgradeCasks(_:))
|
||||
)
|
||||
|
||||
let caskWarning = NSTextField(
|
||||
wrappingLabelWithString: "⚠️ Upgrading a cask can close and replace the app while it is running (e.g. your browser), without warning."
|
||||
)
|
||||
caskWarning.font = .systemFont(ofSize: NSFont.smallSystemFontSize)
|
||||
caskWarning.textColor = .secondaryLabelColor
|
||||
caskWarning.preferredMaxLayoutWidth = 320
|
||||
|
||||
// indent the note so it reads as belonging to the checkbox above
|
||||
let spacer = NSView()
|
||||
spacer.widthAnchor.constraint(equalToConstant: 18).isActive = true
|
||||
let caskWarningRow = NSStackView(views: [spacer, caskWarning])
|
||||
caskWarningRow.orientation = .horizontal
|
||||
|
||||
let stack = NSStackView(views: [
|
||||
frequencyRow,
|
||||
customRow,
|
||||
greedyCheckbox,
|
||||
separator(),
|
||||
autoUpgradeFormulaeCheckbox,
|
||||
autoUpgradeCasksCheckbox,
|
||||
caskWarningRow,
|
||||
separator(),
|
||||
launchAtLoginCheckbox,
|
||||
])
|
||||
stack.orientation = .vertical
|
||||
|
|
@ -122,6 +153,8 @@ class SettingsWindowController: NSWindowController {
|
|||
// this in System Settings > Login Items while we weren't looking
|
||||
launchAtLoginCheckbox.state = SMAppService.mainApp.status == .enabled ? .on : .off
|
||||
greedyCheckbox.state = Settings.includeGreedyCasks ? .on : .off
|
||||
autoUpgradeFormulaeCheckbox.state = Settings.autoUpgradeFormulae ? .on : .off
|
||||
autoUpgradeCasksCheckbox.state = Settings.autoUpgradeCasks ? .on : .off
|
||||
}
|
||||
|
||||
func separator() -> NSBox {
|
||||
|
|
@ -159,6 +192,14 @@ class SettingsWindowController: NSWindowController {
|
|||
appDelegate?.fetchOutdated() // the list contents just changed meaning
|
||||
}
|
||||
|
||||
@objc func toggleAutoUpgradeFormulae(_ sender: NSButton) {
|
||||
Settings.autoUpgradeFormulae = sender.state == .on
|
||||
}
|
||||
|
||||
@objc func toggleAutoUpgradeCasks(_ sender: NSButton) {
|
||||
Settings.autoUpgradeCasks = sender.state == .on
|
||||
}
|
||||
|
||||
@objc func toggleLaunchAtLogin(_ sender: NSButton) {
|
||||
do {
|
||||
if sender.state == .on {
|
||||
|
|
|
|||
Loading…
Reference in a new issue