diff --git a/Sources/DefaultAppsCore/Homebrew.swift b/Sources/DefaultAppsCore/Homebrew.swift index 500ff8b..b196299 100644 --- a/Sources/DefaultAppsCore/Homebrew.swift +++ b/Sources/DefaultAppsCore/Homebrew.swift @@ -3,11 +3,20 @@ import Foundation public enum Homebrew { public static let standardPrefixes = [URL(filePath: "/opt/homebrew"), URL(filePath: "/usr/local")] - /// True when the app came in through `brew install --cask`, so - /// `brew upgrade` already covers updates. - public static func managesApp(prefixes: [URL] = standardPrefixes) -> Bool { - prefixes.contains { - FileManager.default.fileExists(atPath: $0.appending(path: "Caskroom/macosdefaultapps").path) + /// True when `bundle` is the app `brew install --cask` put in place, so + /// `brew upgrade` already covers updates. The cask existing is not enough: + /// a dev build or a zip copy can run next to it. + public static func managesApp(at bundle: URL = Bundle.main.bundleURL, prefixes: [URL] = standardPrefixes) + -> Bool + { + let running = bundle.resolvingSymlinksInPath() + let fileManager = FileManager.default + return prefixes.contains { prefix in + let cask = prefix.appending(path: "Caskroom/macosdefaultapps") + let versions = (try? fileManager.contentsOfDirectory(atPath: cask.path)) ?? [] + return versions.contains { version in + cask.appending(path: "\(version)/macOSDefaultApps.app").resolvingSymlinksInPath() == running + } } } } diff --git a/Tests/DefaultAppsCoreTests/HomebrewTests.swift b/Tests/DefaultAppsCoreTests/HomebrewTests.swift index bf32c3f..64935cd 100644 --- a/Tests/DefaultAppsCoreTests/HomebrewTests.swift +++ b/Tests/DefaultAppsCoreTests/HomebrewTests.swift @@ -7,20 +7,34 @@ struct HomebrewTests { let root = FileManager.default.temporaryDirectory .appending(path: "mda-homebrew-tests-\(UUID().uuidString)") - @Test("an installed cask means brew owns updates") - func caskInstalled() throws { - try FileManager.default.createDirectory( - at: root.appending(path: "Caskroom/macosdefaultapps/0.7.1"), withIntermediateDirectories: true) + var installed: URL { root.appending(path: "Applications/macOSDefaultApps.app") } - #expect(Homebrew.managesApp(prefixes: [root.appending(path: "missing"), root])) + /// What `brew install --cask` leaves behind: a versioned symlink to the app it moved. + func installCask(version: String = "0.7.1") throws { + let versionDir = root.appending(path: "brew/Caskroom/macosdefaultapps/\(version)") + try FileManager.default.createDirectory(at: versionDir, withIntermediateDirectories: true) + try FileManager.default.createDirectory(at: installed, withIntermediateDirectories: true) + try FileManager.default.createSymbolicLink( + at: versionDir.appending(path: "macOSDefaultApps.app"), withDestinationURL: installed) } - @Test("no cask, or no homebrew at all, means nobody updates it for us") - func noCask() throws { - try FileManager.default.createDirectory( - at: root.appending(path: "Caskroom/something-else"), withIntermediateDirectories: true) + @Test("the running app is the one the cask installed") + func runningTheCask() throws { + try installCask() - #expect(!Homebrew.managesApp(prefixes: [root])) - #expect(!Homebrew.managesApp(prefixes: [root.appending(path: "missing")])) + #expect(Homebrew.managesApp(at: installed, prefixes: [root.appending(path: "missing"), root.appending(path: "brew")])) + } + + @Test("a cask is installed, but this copy came from somewhere else") + func runningAnotherCopy() throws { + try installCask() + + #expect(!Homebrew.managesApp(at: root.appending(path: "Downloads/macOSDefaultApps.app"), prefixes: [root.appending(path: "brew")])) + #expect(!Homebrew.managesApp(at: root.appending(path: ".build/debug"), prefixes: [root.appending(path: "brew")])) + } + + @Test("no homebrew at all means nobody updates it for us") + func noHomebrew() { + #expect(!Homebrew.managesApp(at: installed, prefixes: [root.appending(path: "missing")])) } }