Compare commits
7 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c565507a3 | ||
|
|
1f90ae20ad | ||
|
|
af220a4ee6 | ||
|
|
210a8167b8 | ||
|
|
778671e751 | ||
|
|
390e7e526b | ||
|
|
9131c88a64 |
|
|
@ -352,7 +352,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.2;
|
MARKETING_VERSION = 0.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.MaxSoch.BrewBar;
|
PRODUCT_BUNDLE_IDENTIFIER = com.MaxSoch.BrewBar;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
|
|
@ -400,7 +400,7 @@
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/../Frameworks",
|
"@executable_path/../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 0.2;
|
MARKETING_VERSION = 0.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.MaxSoch.BrewBar;
|
PRODUCT_BUNDLE_IDENTIFIER = com.MaxSoch.BrewBar;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
|
|
@ -428,7 +428,7 @@
|
||||||
DEVELOPMENT_TEAM = GLVUYYHFD9;
|
DEVELOPMENT_TEAM = GLVUYYHFD9;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 0.2;
|
MARKETING_VERSION = 0.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.MaxSoch.BrewBarTests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.MaxSoch.BrewBarTests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
|
|
@ -449,7 +449,7 @@
|
||||||
DEVELOPMENT_TEAM = GLVUYYHFD9;
|
DEVELOPMENT_TEAM = GLVUYYHFD9;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 0.2;
|
MARKETING_VERSION = 0.3;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.MaxSoch.BrewBarTests";
|
PRODUCT_BUNDLE_IDENTIFIER = "com.MaxSoch.BrewBarTests";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
menu.addItem(upgradeAllItem)
|
menu.addItem(upgradeAllItem)
|
||||||
upgradeOnlyItem = NSMenuItem(title: "🎯 Upgrade only…", action: nil, keyEquivalent: "")
|
upgradeOnlyItem = NSMenuItem(title: "🎯 Upgrade only…", action: nil, keyEquivalent: "")
|
||||||
let upgradeOnlySubmenu = NSMenu()
|
let upgradeOnlySubmenu = NSMenu()
|
||||||
|
// manual isEnabled control below; otherwise AppKit re-enables the
|
||||||
|
// items from the responder chain every time the menu opens
|
||||||
|
upgradeOnlySubmenu.autoenablesItems = false
|
||||||
upgradeFormulaeItem = NSMenuItem(title: "Formulae", action: #selector(upgradeFormulaeOnly), keyEquivalent: "")
|
upgradeFormulaeItem = NSMenuItem(title: "Formulae", action: #selector(upgradeFormulaeOnly), keyEquivalent: "")
|
||||||
upgradeCasksItem = NSMenuItem(title: "Casks", action: #selector(upgradeCasksOnly), keyEquivalent: "")
|
upgradeCasksItem = NSMenuItem(title: "Casks", action: #selector(upgradeCasksOnly), keyEquivalent: "")
|
||||||
upgradeOnlySubmenu.addItem(upgradeFormulaeItem)
|
upgradeOnlySubmenu.addItem(upgradeFormulaeItem)
|
||||||
|
|
@ -363,7 +366,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
// packages that turn outdated only after this brew update are not
|
// packages that turn outdated only after this brew update are not
|
||||||
// in cachedOutdated yet; the final refetch will surface them
|
// in cachedOutdated yet; the final refetch will surface them
|
||||||
let formulaNames = formulae ? self.cachedOutdated.formulae.map(\.name) : []
|
let formulaNames = formulae ? self.cachedOutdated.formulae.map(\.name) : []
|
||||||
let caskNames = casks ? self.cachedOutdated.casks.map(\.name) : []
|
var caskNames = casks ? self.cachedOutdated.casks.map(\.name) : []
|
||||||
|
if Settings.skipRunningCasksManual, !caskNames.isEmpty {
|
||||||
|
let running = RunningApps.runningCasks(caskNames, brewPath: self.resolveBrewPath())
|
||||||
|
if !running.isEmpty {
|
||||||
|
self.log("upgrade: skipping running app\(running.count == 1 ? "" : "s"): \(running.joined(separator: ", "))\n")
|
||||||
|
caskNames.removeAll(where: running.contains)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.upgradeFormulaeSequentially(formulaNames) {
|
self.upgradeFormulaeSequentially(formulaNames) {
|
||||||
self.upgradeCasksSequentially(caskNames) {
|
self.upgradeCasksSequentially(caskNames) {
|
||||||
|
|
@ -443,10 +453,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
alert.messageText = "Upgrade \(packages.count) package\(packages.count == 1 ? "" : "s")?"
|
alert.messageText = "Upgrade \(packages.count) package\(packages.count == 1 ? "" : "s")?"
|
||||||
}
|
}
|
||||||
|
|
||||||
// the running-app warning only applies when casks are being upgraded
|
// warn only about apps that are actually running right now, by name
|
||||||
var lines: [String] = []
|
var lines: [String] = []
|
||||||
if casks {
|
if casks {
|
||||||
lines.append("Apps upgraded as casks (like a web browser) may be closed and replaced while running, without further warning. Save your work first.")
|
let running = RunningApps.runningCasks(cachedOutdated.casks.map(\.name), brewPath: resolveBrewPath())
|
||||||
|
if !running.isEmpty {
|
||||||
|
let list = running.joined(separator: ", ")
|
||||||
|
if Settings.skipRunningCasksManual {
|
||||||
|
lines.append("Currently running, will be skipped: \(list).")
|
||||||
|
} else {
|
||||||
|
lines.append("Currently running: \(list). These apps may be closed and replaced mid-use, without further warning. Save your work first.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !packages.isEmpty {
|
if !packages.isEmpty {
|
||||||
lines.append(packages.map(\.label).joined(separator: "\n"))
|
lines.append(packages.map(\.label).joined(separator: "\n"))
|
||||||
|
|
@ -492,9 +510,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
// only trigger for the kinds that are both enabled AND outdated —
|
// only trigger for the kinds that are both enabled AND outdated —
|
||||||
// e.g. an outdated cask must not start a pointless formulae upgrade
|
// e.g. an outdated cask must not start a pointless formulae upgrade
|
||||||
let upgradeFormulae = Settings.autoUpgradeFormulae && !outdated.formulae.isEmpty
|
let upgradeFormulae = Settings.autoUpgradeFormulae && !outdated.formulae.isEmpty
|
||||||
let upgradeCasks = Settings.autoUpgradeCasks && !outdated.casks.isEmpty
|
var autoCaskNames = Settings.autoUpgradeCasks ? outdated.casks.map(\.name) : []
|
||||||
if allowAutoUpgrade, upgradeFormulae || upgradeCasks {
|
// filtered before the trigger decision: a permanently running app
|
||||||
self.runAutoUpgrade(formulae: upgradeFormulae, casks: upgradeCasks)
|
// must not start an upgrade cycle that would skip it anyway
|
||||||
|
if Settings.skipRunningCasksAuto, !autoCaskNames.isEmpty {
|
||||||
|
let running = RunningApps.runningCasks(autoCaskNames, brewPath: self.resolveBrewPath())
|
||||||
|
if !running.isEmpty {
|
||||||
|
self.log("auto-upgrade: skipping running app\(running.count == 1 ? "" : "s"): \(running.joined(separator: ", "))\n")
|
||||||
|
autoCaskNames.removeAll(where: running.contains)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allowAutoUpgrade, upgradeFormulae || !autoCaskNames.isEmpty {
|
||||||
|
self.runAutoUpgrade(formulae: upgradeFormulae, caskNames: autoCaskNames)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,13 +553,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
UNUserNotificationCenter.current().add(request)
|
UNUserNotificationCenter.current().add(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAutoUpgrade(formulae: Bool, casks: Bool) {
|
func runAutoUpgrade(formulae: Bool, caskNames: [String]) {
|
||||||
statusMenuItem.title = "Auto-upgrading..."
|
statusMenuItem.title = "Auto-upgrading..."
|
||||||
setMenuBarIcon("brewbar-updating")
|
setMenuBarIcon("brewbar-updating")
|
||||||
failedUpgrades = []
|
failedUpgrades = []
|
||||||
|
|
||||||
let formulaNames = formulae ? cachedOutdated.formulae.map(\.name) : []
|
let formulaNames = formulae ? cachedOutdated.formulae.map(\.name) : []
|
||||||
let caskNames = casks ? cachedOutdated.casks.map(\.name) : []
|
|
||||||
|
|
||||||
upgradeFormulaeSequentially(formulaNames) {
|
upgradeFormulaeSequentially(formulaNames) {
|
||||||
self.upgradeCasksSequentially(caskNames) {
|
self.upgradeCasksSequentially(caskNames) {
|
||||||
|
|
@ -547,11 +573,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
outdatedSubmenu.removeAllItems()
|
outdatedSubmenu.removeAllItems()
|
||||||
outdatedItem.isHidden = outdated.isEmpty
|
outdatedItem.isHidden = outdated.isEmpty
|
||||||
upgradeAllItem.isHidden = outdated.isEmpty
|
upgradeAllItem.isHidden = outdated.isEmpty
|
||||||
// "only" is meaningful only when both kinds are outdated; with a
|
// visible whenever anything is outdated; a kind with nothing to
|
||||||
// single kind, the main item already upgrades exactly what there is
|
// upgrade stays listed but greyed out
|
||||||
upgradeOnlyItem.isHidden = outdated.formulae.isEmpty || outdated.casks.isEmpty
|
upgradeOnlyItem.isHidden = outdated.isEmpty
|
||||||
upgradeFormulaeItem.title = "Formulae (\(outdated.formulae.count))"
|
upgradeFormulaeItem.title = outdated.formulae.isEmpty ? "Formulae" : "Formulae (\(outdated.formulae.count))"
|
||||||
upgradeCasksItem.title = "Casks (\(outdated.casks.count))"
|
upgradeFormulaeItem.isEnabled = !outdated.formulae.isEmpty
|
||||||
|
upgradeCasksItem.title = outdated.casks.isEmpty ? "Casks" : "Casks (\(outdated.casks.count))"
|
||||||
|
upgradeCasksItem.isEnabled = !outdated.casks.isEmpty
|
||||||
|
|
||||||
for package in outdated.all {
|
for package in outdated.all {
|
||||||
let item = NSMenuItem(title: package.label, action: #selector(upgradeSingle(_:)), keyEquivalent: "")
|
let item = NSMenuItem(title: package.label, action: #selector(upgradeSingle(_:)), keyEquivalent: "")
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,15 @@ struct OutdatedPackage: Codable, Equatable {
|
||||||
|
|
||||||
/// Menu label, e.g. "wget 1.21 → 1.22"
|
/// Menu label, e.g. "wget 1.21 → 1.22"
|
||||||
var label: String {
|
var label: String {
|
||||||
let installed = installedVersions.joined(separator: ", ")
|
let installed = installedVersions.map(Self.displayVersion).joined(separator: ", ")
|
||||||
return "\(name) \(installed) → \(currentVersion)"
|
return "\(name) \(installed) → \(Self.displayVersion(currentVersion))"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some casks (e.g. claude: "1.20186.9,69f150a4c9…") version themselves
|
||||||
|
/// as "user-facing-version,build-id" — the part after the comma only
|
||||||
|
/// matters for brew's own download URL, never for display.
|
||||||
|
static func displayVersion(_ version: String) -> String {
|
||||||
|
String(version.split(separator: ",", maxSplits: 1)[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
46
BrewBar/RunningApps.swift
Normal file
46
BrewBar/RunningApps.swift
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
/// Maps installed casks to their .app bundles and checks them against the
|
||||||
|
/// apps currently running. The mapping comes from the Caskroom: brew keeps
|
||||||
|
/// a copy of every installed cask's artifacts there, so listing
|
||||||
|
/// <prefix>/Caskroom/<token>/<version>/*.app gives the app names without
|
||||||
|
/// spawning a brew process.
|
||||||
|
enum RunningApps {
|
||||||
|
/// Caskroom lives next to the brew binary: <prefix>/bin/brew → <prefix>/Caskroom
|
||||||
|
static func caskroomURL(brewPath: String) -> URL {
|
||||||
|
URL(fileURLWithPath: brewPath)
|
||||||
|
.deletingLastPathComponent() // <prefix>/bin
|
||||||
|
.deletingLastPathComponent() // <prefix>
|
||||||
|
.appendingPathComponent("Caskroom", isDirectory: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// .app bundle names (e.g. "Firefox.app") found in any installed version
|
||||||
|
/// of the cask. Casks without an app artifact (fonts, pkg-based
|
||||||
|
/// installers) return [] and are never considered running.
|
||||||
|
static func appNames(forCask token: String, brewPath: String) -> [String] {
|
||||||
|
let caskDir = caskroomURL(brewPath: brewPath).appendingPathComponent(token, isDirectory: true)
|
||||||
|
let fm = FileManager.default
|
||||||
|
guard let versions = try? fm.contentsOfDirectory(atPath: caskDir.path) else { return [] }
|
||||||
|
|
||||||
|
var names: Set<String> = []
|
||||||
|
for version in versions where !version.hasPrefix(".") {
|
||||||
|
let versionDir = caskDir.appendingPathComponent(version, isDirectory: true)
|
||||||
|
for entry in (try? fm.contentsOfDirectory(atPath: versionDir.path)) ?? [] where entry.hasSuffix(".app") {
|
||||||
|
names.insert(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array(names)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subset of the given cask tokens whose app is currently running.
|
||||||
|
/// Matches on the bundle name — the running copy lives in /Applications,
|
||||||
|
/// the Caskroom one is brew's, but both carry the same name.
|
||||||
|
static func runningCasks(_ tokens: [String], brewPath: String) -> [String] {
|
||||||
|
let running = Set(NSWorkspace.shared.runningApplications.compactMap {
|
||||||
|
$0.bundleURL?.lastPathComponent
|
||||||
|
})
|
||||||
|
return tokens.filter { token in
|
||||||
|
appNames(forCask: token, brewPath: brewPath).contains { running.contains($0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ enum Settings {
|
||||||
UserDefaults.standard.register(defaults: [
|
UserDefaults.standard.register(defaults: [
|
||||||
"includeGreedyCasks": true,
|
"includeGreedyCasks": true,
|
||||||
"confirmBeforeUpgradeAll": true,
|
"confirmBeforeUpgradeAll": true,
|
||||||
|
"skipRunningCasksAuto": true,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,4 +50,18 @@ enum Settings {
|
||||||
get { UserDefaults.standard.bool(forKey: "cleanupAfterUpgrade") }
|
get { UserDefaults.standard.bool(forKey: "cleanupAfterUpgrade") }
|
||||||
set { UserDefaults.standard.set(newValue, forKey: "cleanupAfterUpgrade") }
|
set { UserDefaults.standard.set(newValue, forKey: "cleanupAfterUpgrade") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// auto-upgrade never touches a cask whose app is running (on by default:
|
||||||
|
/// there is no dialog to warn anyone in that flow)
|
||||||
|
static var skipRunningCasksAuto: Bool {
|
||||||
|
get { UserDefaults.standard.bool(forKey: "skipRunningCasksAuto") }
|
||||||
|
set { UserDefaults.standard.set(newValue, forKey: "skipRunningCasksAuto") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// manual bulk upgrades skip running apps too (off by default: the
|
||||||
|
/// confirmation dialog already names them)
|
||||||
|
static var skipRunningCasksManual: Bool {
|
||||||
|
get { UserDefaults.standard.bool(forKey: "skipRunningCasksManual") }
|
||||||
|
set { UserDefaults.standard.set(newValue, forKey: "skipRunningCasksManual") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ class SettingsWindowController: NSWindowController {
|
||||||
var showCountCheckbox: NSButton!
|
var showCountCheckbox: NSButton!
|
||||||
var confirmCheckbox: NSButton!
|
var confirmCheckbox: NSButton!
|
||||||
var cleanupCheckbox: NSButton!
|
var cleanupCheckbox: NSButton!
|
||||||
|
var skipRunningAutoCheckbox: NSButton!
|
||||||
|
var skipRunningManualCheckbox: NSButton!
|
||||||
|
|
||||||
convenience init(appDelegate: AppDelegate) {
|
convenience init(appDelegate: AppDelegate) {
|
||||||
let window = NSWindow(
|
let window = NSWindow(
|
||||||
|
|
@ -108,6 +110,26 @@ class SettingsWindowController: NSWindowController {
|
||||||
"⚠️ Upgrading a cask can close and replace the app while it is running (e.g. your browser), without warning."
|
"⚠️ Upgrading a cask can close and replace the app while it is running (e.g. your browser), without warning."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let runningAppsHeader = NSTextField(labelWithString: "Running apps")
|
||||||
|
runningAppsHeader.font = .boldSystemFont(ofSize: NSFont.smallSystemFontSize)
|
||||||
|
runningAppsHeader.textColor = .secondaryLabelColor
|
||||||
|
|
||||||
|
skipRunningAutoCheckbox = NSButton(
|
||||||
|
checkboxWithTitle: "Auto-upgrade: skip casks whose app is running",
|
||||||
|
target: self,
|
||||||
|
action: #selector(toggleSkipRunningAuto(_:))
|
||||||
|
)
|
||||||
|
|
||||||
|
skipRunningManualCheckbox = NSButton(
|
||||||
|
checkboxWithTitle: "Manual upgrades: skip them too",
|
||||||
|
target: self,
|
||||||
|
action: #selector(toggleSkipRunningManual(_:))
|
||||||
|
)
|
||||||
|
|
||||||
|
let runningAppsNote = noteRow(
|
||||||
|
"Skipped apps stay in the outdated list and upgrade at the first check after they quit. Upgrading a single package from the Outdated menu always proceeds."
|
||||||
|
)
|
||||||
|
|
||||||
notifyCheckbox = NSButton(
|
notifyCheckbox = NSButton(
|
||||||
checkboxWithTitle: "Notify when new updates are found",
|
checkboxWithTitle: "Notify when new updates are found",
|
||||||
target: self,
|
target: self,
|
||||||
|
|
@ -142,6 +164,11 @@ class SettingsWindowController: NSWindowController {
|
||||||
autoUpgradeCasksCheckbox,
|
autoUpgradeCasksCheckbox,
|
||||||
caskWarningRow,
|
caskWarningRow,
|
||||||
separator(),
|
separator(),
|
||||||
|
runningAppsHeader,
|
||||||
|
skipRunningAutoCheckbox,
|
||||||
|
skipRunningManualCheckbox,
|
||||||
|
runningAppsNote,
|
||||||
|
separator(),
|
||||||
notifyCheckbox,
|
notifyCheckbox,
|
||||||
showCountCheckbox,
|
showCountCheckbox,
|
||||||
confirmCheckbox,
|
confirmCheckbox,
|
||||||
|
|
@ -188,6 +215,8 @@ class SettingsWindowController: NSWindowController {
|
||||||
showCountCheckbox.state = Settings.showCountInMenuBar ? .on : .off
|
showCountCheckbox.state = Settings.showCountInMenuBar ? .on : .off
|
||||||
confirmCheckbox.state = Settings.confirmBeforeUpgradeAll ? .on : .off
|
confirmCheckbox.state = Settings.confirmBeforeUpgradeAll ? .on : .off
|
||||||
cleanupCheckbox.state = Settings.cleanupAfterUpgrade ? .on : .off
|
cleanupCheckbox.state = Settings.cleanupAfterUpgrade ? .on : .off
|
||||||
|
skipRunningAutoCheckbox.state = Settings.skipRunningCasksAuto ? .on : .off
|
||||||
|
skipRunningManualCheckbox.state = Settings.skipRunningCasksManual ? .on : .off
|
||||||
}
|
}
|
||||||
|
|
||||||
func separator() -> NSBox {
|
func separator() -> NSBox {
|
||||||
|
|
@ -324,6 +353,14 @@ class SettingsWindowController: NSWindowController {
|
||||||
Settings.cleanupAfterUpgrade = sender.state == .on
|
Settings.cleanupAfterUpgrade = sender.state == .on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func toggleSkipRunningAuto(_ sender: NSButton) {
|
||||||
|
Settings.skipRunningCasksAuto = sender.state == .on
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func toggleSkipRunningManual(_ sender: NSButton) {
|
||||||
|
Settings.skipRunningCasksManual = sender.state == .on
|
||||||
|
}
|
||||||
|
|
||||||
@objc func toggleLaunchAtLogin(_ sender: NSButton) {
|
@objc func toggleLaunchAtLogin(_ sender: NSButton) {
|
||||||
do {
|
do {
|
||||||
if sender.state == .on {
|
if sender.state == .on {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,18 @@
|
||||||
@testable import BrewBar
|
@testable import BrewBar
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
|
final class RunningAppsTests: XCTestCase {
|
||||||
|
func testCaskroomURL_appleSilicon() {
|
||||||
|
let url = RunningApps.caskroomURL(brewPath: "/opt/homebrew/bin/brew")
|
||||||
|
XCTAssertEqual(url.path, "/opt/homebrew/Caskroom")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCaskroomURL_intel() {
|
||||||
|
let url = RunningApps.caskroomURL(brewPath: "/usr/local/bin/brew")
|
||||||
|
XCTAssertEqual(url.path, "/usr/local/Caskroom")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class BrewParserTests: XCTestCase {
|
final class BrewParserTests: XCTestCase {
|
||||||
// MARK: - parseOutdatedJSON
|
// MARK: - parseOutdatedJSON
|
||||||
|
|
||||||
|
|
@ -52,6 +64,15 @@ final class BrewParserTests: XCTestCase {
|
||||||
XCTAssertEqual(package.label, "wget 1.21.3 → 1.21.4")
|
XCTAssertEqual(package.label, "wget 1.21.3 → 1.21.4")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testParseOutdatedJSON_packageLabel_stripsBuildIdAfterComma() {
|
||||||
|
let package = OutdatedPackage(
|
||||||
|
name: "claude",
|
||||||
|
installedVersions: ["1.20185.0,5e8f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f"],
|
||||||
|
currentVersion: "1.20186.9,69f150a4c9316d5c8cd7b9f130ed583d15c0383e"
|
||||||
|
)
|
||||||
|
XCTAssertEqual(package.label, "claude 1.20185.0 → 1.20186.9")
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - parseVersion
|
// MARK: - parseVersion
|
||||||
|
|
||||||
func testParseVersion_picksFirstLine() {
|
func testParseVersion_picksFirstLine() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
cask "brewbar" do
|
cask "brewbar" do
|
||||||
version "0.2"
|
version "0.3"
|
||||||
sha256 "dc449ec93f44698acf5de671675b06fbe66bcd40639153c8fe8bae92f569469e"
|
sha256 "d87a744e034c2ce0f92a24c3ebba167533419f948337a2dbedd393152d08d13d"
|
||||||
|
|
||||||
url "https://forgejo.socheleau.fr/maxsoch/BrewBar/releases/download/v#{version}/BrewBar-#{version}.zip"
|
url "https://forgejo.socheleau.fr/maxsoch/BrewBar/releases/download/v#{version}/BrewBar-#{version}.zip"
|
||||||
name "BrewBar"
|
name "BrewBar"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue