diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f56b398b..2b5b37db9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] - 🍼 Formula updates from 1.8 release #319 +- 🎨 Fix `mas outdated` & `mas upgrade` commands #312 + thanks, [@rgoldberg](https://github.com/rgoldberg)! +- 🎨 Fix errors being output with "Warning" when not on TTY #312 + thanks, [@rgoldberg](https://github.com/rgoldberg)! ## [v1.8.0] 💪🏼 arm64 support for M1 Macs - 2021-02-12 diff --git a/MasKit/Commands/Outdated.swift b/MasKit/Commands/Outdated.swift index 745ea40b7..8c04b0075 100644 --- a/MasKit/Commands/Outdated.swift +++ b/MasKit/Commands/Outdated.swift @@ -7,7 +7,6 @@ // import Commandant -import CommerceKit /// Command which displays a list of installed apps which have available updates /// ready to be installed from the Mac App Store. @@ -17,31 +16,41 @@ public struct OutdatedCommand: CommandProtocol { public let function = "Lists pending updates from the Mac App Store" private let appLibrary: AppLibrary + private let storeSearch: StoreSearch /// Public initializer. - /// - Parameter appLibrary: AppLibrary manager. public init() { self.init(appLibrary: MasAppLibrary()) } /// Internal initializer. /// - Parameter appLibrary: AppLibrary manager. - init(appLibrary: AppLibrary = MasAppLibrary()) { + /// - Parameter storeSearch: StoreSearch manager. + init(appLibrary: AppLibrary = MasAppLibrary(), storeSearch: StoreSearch = MasStoreSearch()) { self.appLibrary = appLibrary + self.storeSearch = storeSearch } /// Runs the command. public func run(_: Options) -> Result<(), MASError> { - let updateController = CKUpdateController.shared() - let updates = updateController?.availableUpdates() - for update in updates! { - if let installed = appLibrary.installedApp(forBundleId: update.bundleID) { - // Display version of installed app compared to available update. - print(""" - \(update.itemIdentifier) \(update.title) (\(installed.bundleVersion) -> \(update.bundleVersion)) - """) - } else { - print("\(update.itemIdentifier) \(update.title) (unknown -> \(update.bundleVersion))") + for installedApp in appLibrary.installedApps { + do { + if let storeApp = try storeSearch.lookup(app: installedApp.itemIdentifier.intValue) { + if installedApp.bundleVersion != storeApp.version { + print(""" +\(installedApp.itemIdentifier) \(installedApp.appName) (\(installedApp.bundleVersion) -> \(storeApp.version)) +""") + } + } else { + printWarning(""" +Identifier \(installedApp.itemIdentifier) not found in store. Was expected to identify \(installedApp.appName). +""") + } + } catch { + // Bubble up MASErrors + // swiftlint:disable force_cast + return .failure(error is MASError ? error as! MASError : .searchFailed) + // swiftlint:enable force_cast } } return .success(()) diff --git a/MasKit/Commands/Upgrade.swift b/MasKit/Commands/Upgrade.swift index 035999ddf..f9bbd439b 100644 --- a/MasKit/Commands/Upgrade.swift +++ b/MasKit/Commands/Upgrade.swift @@ -7,7 +7,6 @@ // import Commandant -import CommerceKit /// Command which upgrades apps with new versions available in the Mac App Store. public struct UpgradeCommand: CommandProtocol { @@ -16,71 +15,82 @@ public struct UpgradeCommand: CommandProtocol { public let function = "Upgrade outdated apps from the Mac App Store" private let appLibrary: AppLibrary + private let storeSearch: StoreSearch /// Public initializer. - /// - Parameter appLibrary: AppLibrary manager. public init() { self.init(appLibrary: MasAppLibrary()) } /// Internal initializer. /// - Parameter appLibrary: AppLibrary manager. - init(appLibrary: AppLibrary = MasAppLibrary()) { + /// - Parameter storeSearch: StoreSearch manager. + init(appLibrary: AppLibrary = MasAppLibrary(), storeSearch: StoreSearch = MasStoreSearch()) { self.appLibrary = appLibrary + self.storeSearch = storeSearch } /// Runs the command. public func run(_ options: Options) -> Result<(), MASError> { - let updateController = CKUpdateController.shared() - let updates: [CKUpdate] - let apps = options.apps - if apps.count > 0 { - // convert input into a list of appId's - let appIds: [UInt64] - - appIds = apps.compactMap { - if let appId = UInt64($0) { - return appId + do { + let apps = + try ( + options.apps.count == 0 + ? appLibrary.installedApps + : options.apps.compactMap { + if let appId = UInt64($0) { + // if argument a UInt64, lookup app by id using argument + return appLibrary.installedApp(forId: appId) + } else { + // if argument not a UInt64, lookup app by name using argument + return appLibrary.installedApp(named: $0) + } + } + ).compactMap {(installedApp: SoftwareProduct) -> SoftwareProduct? in + // only upgrade apps whose local version differs from the store version + if let storeApp = try storeSearch.lookup(app: installedApp.itemIdentifier.intValue) { + return storeApp.version != installedApp.bundleVersion + ? installedApp + : nil + } else { + return nil + } } - if let appId = appLibrary.appIdsByName[$0] { - return appId - } - return nil - } - // check each of those for updates - updates = appIds.compactMap { - updateController?.availableUpdate(withItemIdentifier: $0) - } - - guard updates.count > 0 else { + guard apps.count > 0 else { printWarning("Nothing found to upgrade") return .success(()) } - } else { - updates = updateController?.availableUpdates() ?? [] - - // Upgrade everything - guard updates.count > 0 else { - print("Everything is up-to-date") - return .success(()) - } - } - print("Upgrading \(updates.count) outdated application\(updates.count > 1 ? "s" : ""):") - print(updates.map({ "\($0.title) (\($0.bundleVersion))" }).joined(separator: ", ")) + print("Upgrading \(apps.count) outdated application\(apps.count > 1 ? "s" : ""):") + print(apps.map {"\($0.appName) (\($0.bundleVersion))"}.joined(separator: ", ")) - let updateResults = updates.compactMap { - download($0.itemIdentifier.uint64Value) - } + var updatedAppCount = 0 + var failedUpgradeResults = [MASError]() + for app in apps { + if let upgradeResult = download(app.itemIdentifier.uint64Value) { + failedUpgradeResults.append(upgradeResult) + } else { + updatedAppCount += 1 + } + } - switch updateResults.count { - case 0: - return .success(()) - case 1: - return .failure(updateResults[0]) - default: - return .failure(.downloadFailed(error: nil)) + switch failedUpgradeResults.count { + case 0: + if updatedAppCount == 0 { + print("Everything is up-to-date") + } + return .success(()) + case 1: + return .failure(failedUpgradeResults[0]) + default: + return .failure(.downloadFailed(error: nil)) + } + } catch { + // Bubble up MASErrors + // swiftlint:disable force_cast + return .failure(error is MASError ? error as! MASError : .searchFailed) + // swiftlint:enable force_cast } } } diff --git a/MasKit/Formatters/Utilities.swift b/MasKit/Formatters/Utilities.swift index 3044758e5..2b069f286 100644 --- a/MasKit/Formatters/Utilities.swift +++ b/MasKit/Formatters/Utilities.swift @@ -33,7 +33,7 @@ func printWarning(_ message: String) { public func printError(_ message: String) { guard isatty(fileno(stdout)) != 0 else { - print("Warning: \(message)") + print("Error: \(message)") return }