Merge branch 'master' into pkg

This commit is contained in:
Ben Chatelain 2019-01-03 16:33:58 -08:00 committed by GitHub
commit 6e002b79ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 522 additions and 136 deletions

View file

@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
- 🐛📦 Fix paths building installer package #195
- ♻️ AppLibrary refactor #193
## [v1.5.0] 🗑 Uninstall - 2018-12-27

View file

@ -2,8 +2,8 @@ class Mas < Formula
desc "Mac App Store command-line interface"
homepage "https://github.com/mas-cli/mas"
url "https://github.com/mas-cli/mas.git",
:tag => "v1.4.4",
:revision => "3660365dd334cd852dd83d42ee016e267821a5de"
:tag => "v1.5.0",
:revision => "ccaaa74c9593d04dc41fcff40af196fdad49f517"
head "https://github.com/mas-cli/mas.git"
bottle do
@ -15,9 +15,9 @@ class Mas < Formula
sha256 "237fd7270cb8f0d68a33e7ce05671a2e5c269d05d736abb0f66b50215439084e" => :el_capitan
end
depends_on "trash"
depends_on "carthage" => :build
depends_on :xcode => ["10.1", :build]
depends_on "trash"
def install
# Working around build issues in dependencies

View file

@ -2,8 +2,8 @@ class Mas < Formula
desc "Mac App Store command-line interface"
homepage "https://github.com/mas-cli/mas"
url "https://github.com/mas-cli/mas.git",
:tag => "v1.4.4",
:revision => "3660365dd334cd852dd83d42ee016e267821a5de"
:tag => "v1.5.0",
:revision => "ccaaa74c9593d04dc41fcff40af196fdad49f517"
head "https://github.com/mas-cli/mas.git"
bottle do
@ -12,9 +12,9 @@ class Mas < Formula
sha256 "fc6658113d785a660e3f4d2e4e134ad02fe003ffa7d69271a2c53f503aaae726" => :high_sierra
end
depends_on "trash"
depends_on "carthage" => :build
depends_on :xcode => ["10.1", :build]
depends_on "trash"
def install
# Working around build issues in dependencies

View file

@ -8,11 +8,29 @@
/// Utility for managing installed apps.
public protocol AppLibrary {
/// Finds an app by ID from the set of installed apps
/// Entire set of installed apps.
var installedApps: [SoftwareProduct] { get }
/// Map of app name to ID.
var appIdsByName: [String: UInt64] { get }
/// Finds an app by ID.
///
/// - Parameter appId: MAS ID for app.
/// - Parameter forId: MAS ID for app.
/// - Returns: Software Product of app if found; nil otherwise.
func installedApp(appId: UInt64) -> SoftwareProduct?
func installedApp(forId: UInt64) -> SoftwareProduct?
/// Finds an app by it's bundle identifier.
///
/// - Parameter forBundleId: Bundle identifier of app.
/// - Returns: Software Product of app if found; nil otherwise.
func installedApp(forBundleId: String) -> SoftwareProduct?
/// Finds an app by name.
///
/// - Parameter named: Name of app.
/// - Returns: Software Product of app if found; nil otherwise.
func installedApp(named: String) -> SoftwareProduct?
/// Uninstalls an app.
///
@ -20,3 +38,34 @@ public protocol AppLibrary {
/// - Throws: Error if there is a problem.
func uninstallApp(app: SoftwareProduct) throws
}
/// Common logic
extension AppLibrary {
/// Map of app name to ID.
public var appIdsByName: [String: UInt64] {
get {
var destMap = [String: UInt64]()
for product in installedApps {
destMap[product.appName] = product.itemIdentifier.uint64Value
}
return destMap
}
}
/// Finds an app by name.
///
/// - Parameter id: MAS ID for app.
/// - Returns: Software Product of app if found; nil otherwise.
public func installedApp(forId id: UInt64) -> SoftwareProduct? {
let appId = NSNumber(value: id)
return installedApps.first { $0.itemIdentifier == appId }
}
/// Finds an app by name.
///
/// - Parameter appName: Full title of an app.
/// - Returns: Software Product of app if found; nil otherwise.
public func installedApp(named appName: String) -> SoftwareProduct? {
return installedApps.first { $0.appName == appName }
}
}

View file

@ -1,50 +0,0 @@
//
// CKSoftwareMap+AppLookup.swift
// mas-cli
//
// Created by Andrew Griffiths on 20/8/17.
// Copyright © 2017 Andrew Griffiths.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import CommerceKit
private var appIdsByName : [String:UInt64]?
extension CKSoftwareMap {
func appIdWithProductName(_ name: String) -> UInt64? {
if appIdsByName == nil {
let softwareMap = CKSoftwareMap.shared()
var destMap = [String:UInt64]()
guard let products = softwareMap.allProducts() else {
return nil
}
for product in products {
destMap[product.appName] = product.itemIdentifier.uint64Value
}
appIdsByName = destMap
}
return appIdsByName?[name]
}
}

View file

@ -10,17 +10,25 @@ import Commandant
import Result
import CommerceKit
/// Installs previously purchased apps from the Mac App Store.
public struct InstallCommand: CommandProtocol {
public typealias Options = InstallOptions
public let verb = "install"
public let function = "Install from the Mac App Store"
public init() {}
private let appLibrary: AppLibrary
/// Designated initializer.
///
/// - Parameter appLibrary: AppLibrary manager.
public init(appLibrary: AppLibrary = MasAppLibrary()) {
self.appLibrary = appLibrary
}
public func run(_ options: Options) -> Result<(), MASError> {
// Try to download applications with given identifiers and collect results
let downloadResults = options.appIds.compactMap { (appId) -> MASError? in
if let product = installedApp(appId), !options.forceInstall {
if let product = appLibrary.installedApp(forId: appId), !options.forceInstall {
printWarning("\(product.appName) is already installed")
return nil
}
@ -37,13 +45,6 @@ public struct InstallCommand: CommandProtocol {
return .failure(.downloadFailed(error: nil))
}
}
fileprivate func installedApp(_ appId: UInt64) -> CKSoftwareProduct? {
let appId = NSNumber(value: appId)
let softwareMap = CKSoftwareMap.shared()
return softwareMap.allProducts()?.first { $0.itemIdentifier == appId }
}
}
public struct InstallOptions: OptionsProtocol {

View file

@ -8,18 +8,25 @@
import Commandant
import Result
import CommerceKit
/// Command which lists all installed apps.
public struct ListCommand: CommandProtocol {
public typealias Options = NoOptions<MASError>
public let verb = "list"
public let function = "Lists apps from the Mac App Store which are currently installed"
public init() {}
private let appLibrary: AppLibrary
/// Designated initializer.
///
/// - Parameter appLibrary: AppLibrary manager.
public init(appLibrary: AppLibrary = MasAppLibrary()) {
self.appLibrary = appLibrary
}
public func run(_ options: Options) -> Result<(), MASError> {
let softwareMap = CKSoftwareMap.shared()
guard let products = softwareMap.allProducts() else {
let products = appLibrary.installedApps
if products.isEmpty {
print("No installed apps found")
return .success(())
}

View file

@ -10,19 +10,26 @@ import Commandant
import Result
import CommerceKit
/// Command which installs the first search result. This is handy as many MAS titles
/// can be long with embedded keywords.
public struct LuckyCommand: CommandProtocol {
public typealias Options = LuckyOptions
public let verb = "lucky"
public let function = "Install the first result from the Mac App Store"
private let appLibrary: AppLibrary
private let urlSession: URLSession
public init(urlSession: URLSession = URLSession.shared) {
/// Designated initializer.
///
/// - Parameter appLibrary: AppLibrary manager.
/// - Parameter urlSession: URL session for network communication.
public init(appLibrary: AppLibrary = MasAppLibrary(), urlSession: URLSession = URLSession.shared) {
self.appLibrary = appLibrary
self.urlSession = urlSession
}
public func run(_ options: Options) -> Result<(), MASError> {
guard let searchURLString = searchURLString(options.appName),
let searchJson = urlSession.requestSynchronousJSONWithURLString(searchURLString) as? [String: Any] else {
return .failure(.searchFailed)
@ -34,23 +41,22 @@ public struct LuckyCommand: CommandProtocol {
return .failure(.noSearchResultsFound)
}
let appId = results[0][ResultKeys.TrackId] as! UInt64
return install(appId, options: options)
}
fileprivate func install(_ appId: UInt64, options: Options) -> Result<(), MASError> {
// Try to download applications with given identifiers and collect results
let downloadResults = [appId].compactMap { (appId) -> MASError? in
if let product = installedApp(appId) , !options.forceInstall {
if let product = appLibrary.installedApp(forId: appId), !options.forceInstall {
printWarning("\(product.appName) is already installed")
return nil
}
return download(appId)
}
switch downloadResults.count {
case 0:
return .success(())
@ -61,13 +67,6 @@ public struct LuckyCommand: CommandProtocol {
}
}
fileprivate func installedApp(_ appId: UInt64) -> CKSoftwareProduct? {
let appId = NSNumber(value: appId)
let softwareMap = CKSoftwareMap.shared()
return softwareMap.allProducts()?.first { $0.itemIdentifier == appId }
}
func searchURLString(_ appName: String) -> String? {
if let urlEncodedAppName = appName.URLEncodedString {
return "https://itunes.apple.com/search?entity=macSoftware&term=\(urlEncodedAppName)&attribute=allTrackTerm"

View file

@ -10,19 +10,28 @@ import Commandant
import Result
import CommerceKit
/// Command which displays a list of installed apps which have available updates
/// ready to be installed from the Mac App Store.
public struct OutdatedCommand: CommandProtocol {
public typealias Options = NoOptions<MASError>
public let verb = "outdated"
public let function = "Lists pending updates from the Mac App Store"
public init() {}
private let appLibrary: AppLibrary
/// Designated initializer.
///
/// - Parameter appLibrary: AppLibrary manager.
public init(appLibrary: AppLibrary = MasAppLibrary()) {
self.appLibrary = appLibrary
}
public func run(_ options: Options) -> Result<(), MASError> {
let updateController = CKUpdateController.shared()
let updates = updateController?.availableUpdates()
let softwareMap = CKSoftwareMap.shared()
for update in updates! {
if let installed = softwareMap.product(forBundleIdentifier: update.bundleID) {
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))")

View file

@ -33,7 +33,7 @@ public struct UninstallCommand: CommandProtocol {
public func run(_ options: Options) -> Result<(), MASError> {
let appId = UInt64(options.appId)
guard let product = appLibrary.installedApp(appId: appId) else {
guard let product = appLibrary.installedApp(forId: appId) else {
return .failure(.notInstalled)
}

View file

@ -10,37 +10,40 @@ import Commandant
import Result
import CommerceKit
/// Command which upgrades apps with new versions available in the Mac App Store.
public struct UpgradeCommand: CommandProtocol {
public typealias Options = UpgradeOptions
public let verb = "upgrade"
public let function = "Upgrade outdated apps from the Mac App Store"
public init() {}
private let appLibrary: AppLibrary
/// Designated initializer.
///
/// - Parameter appLibrary: <#appLibrary description#>
public init(appLibrary: AppLibrary = MasAppLibrary()) {
self.appLibrary = appLibrary
}
public func run(_ options: Options) -> Result<(), MASError> {
let updateController = CKUpdateController.shared()
let updates: [CKUpdate]
let apps = options.apps
if apps.count > 0 {
let softwareMap = CKSoftwareMap.shared()
// convert input into a list of appId's
let appIds: [UInt64]
appIds = apps.compactMap {
if let appId = UInt64($0) {
return appId
}
if let appId = softwareMap.appIdWithProductName($0) {
if let appId = appLibrary.appIdsByName[$0] {
return appId
}
return nil
}
// check each of those for updates
updates = appIds.compactMap {
updateController?.availableUpdate(withItemIdentifier: $0)
}

View file

@ -9,6 +9,7 @@
import Commandant
import Result
/// Command which displays the version of the mas tool.
public struct VersionCommand: CommandProtocol {
public typealias Options = NoOptions<MASError>
public let verb = "version"

View file

@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.5</string>
<string>1.5.1</string>
<key>CFBundleVersion</key>
<string>10500000</string>
<string>10501000</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 Andrew Naylor. All rights reserved.</string>
</dict>

View file

@ -13,15 +13,23 @@ public class MasAppLibrary: AppLibrary {
/// CommerceKit's singleton manager of installed software.
private let softwareMap = CKSoftwareMap.shared()
/// Array of installed software products.
public lazy var installedApps: [SoftwareProduct] = {
var appList = [SoftwareProduct]()
guard let products = softwareMap.allProducts()
else { return appList }
appList.append(contentsOf: products)
return products
}()
public init() {}
/// Finds an app by ID from the set of installed apps
/// Finds an app using a bundle identifier.
///
/// - Parameter appId: MAS ID for app.
/// - Parameter bundleId: Bundle identifier of app.
/// - Returns: Software Product of app if found; nil otherwise.
public func installedApp(appId: UInt64) -> SoftwareProduct? {
let appId = NSNumber(value: appId)
return softwareMap.allProducts()?.first { $0.itemIdentifier == appId }
public func installedApp(forBundleId bundleId: String) -> SoftwareProduct? {
return softwareMap.product(forBundleIdentifier: bundleId)
}
/// Uninstalls an app.

View file

@ -10,5 +10,6 @@
public protocol SoftwareProduct {
var appName: String { get }
var bundlePath: String { get set }
var bundleVersion: String { get set }
var itemIdentifier: NSNumber { get set }
}

View file

@ -0,0 +1,25 @@
//
// AccountCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class AccountCommandSpec: QuickSpec {
override func spec() {
describe("Account command") {
it("displays active account") {
let cmd = AccountCommand()
let result = cmd.run(AccountCommand.Options())
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -0,0 +1,25 @@
//
// InfoCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class InfoCommandSpec: QuickSpec {
override func spec() {
describe("Info command") {
it("displays app details") {
let cmd = InfoCommand()
let result = cmd.run(InfoCommand.Options(appId: ""))
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -0,0 +1,25 @@
//
// InstallCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class InstallCommandSpec: QuickSpec {
override func spec() {
describe("install command") {
it("installs apps") {
let cmd = InstallCommand()
let result = cmd.run(InstallCommand.Options(appIds: [], forceInstall: false))
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -0,0 +1,25 @@
//
// LuckyCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class LuckyCommandSpec: QuickSpec {
override func spec() {
describe("lucky command") {
it("installs the first app matching a search") {
let cmd = LuckyCommand()
let result = cmd.run(LuckyCommand.Options(appName: "", forceInstall: false))
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -0,0 +1,25 @@
//
// OutdatedCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class OutdatedCommandSpec: QuickSpec {
override func spec() {
describe("outdated command") {
it("displays apps with pending updates") {
let cmd = OutdatedCommand()
let result = cmd.run(OutdatedCommand.Options())
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -0,0 +1,25 @@
//
// ResetCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class ResetCommandSpec: QuickSpec {
override func spec() {
describe("reset command") {
it("updates stuff") {
let cmd = ResetCommand()
let result = cmd.run(ResetCommand.Options(debug: false))
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -0,0 +1,25 @@
//
// SearchCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class SearchCommandSpec: QuickSpec {
override func spec() {
describe("search command") {
it("updates stuff") {
let cmd = SearchCommand()
let result = cmd.run(SearchCommand.Options(appName: "", price: false))
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -0,0 +1,25 @@
//
// SignInCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class SignInCommandSpec: QuickSpec {
override func spec() {
describe("signn command") {
it("updates stuff") {
let cmd = SignInCommand()
let result = cmd.run(SignInCommand.Options(username: "", password: "", dialog: false))
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -0,0 +1,25 @@
//
// SignOutCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class SignOutCommandSpec: QuickSpec {
override func spec() {
describe("signout command") {
it("updates stuff") {
let cmd = SignOutCommand()
let result = cmd.run(SignOutCommand.Options())
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -18,6 +18,7 @@ class UninstallCommandSpec: QuickSpec {
let app = MockSoftwareProduct(
appName: "Some App",
bundlePath: "/tmp/Some.app",
bundleVersion: "1.0",
itemIdentifier: NSNumber(value: appId)
)
let mockLibrary = MockAppLibrary()
@ -26,16 +27,17 @@ class UninstallCommandSpec: QuickSpec {
context("dry run") {
let options = UninstallCommand.Options(appId: appId, dryRun: true)
beforeEach {
mockLibrary.reset()
}
it("can't remove a missing app") {
mockLibrary.apps = []
let result = uninstall.run(options)
expect(result).to(beFailure { error in
expect(error) == .notInstalled
})
}
it("finds an app") {
mockLibrary.apps.append(app)
mockLibrary.installedApps.append(app)
let result = uninstall.run(options)
expect(result).to(beSuccess())
@ -44,25 +46,25 @@ class UninstallCommandSpec: QuickSpec {
context("wet run") {
let options = UninstallCommand.Options(appId: appId, dryRun: false)
beforeEach {
mockLibrary.reset()
}
it("can't remove a missing app") {
mockLibrary.apps = []
let result = uninstall.run(options)
expect(result).to(beFailure { error in
expect(error) == .notInstalled
})
}
it("removes an app") {
mockLibrary.apps.append(app)
mockLibrary.installedApps.append(app)
let result = uninstall.run(options)
expect(result).to(beSuccess())
}
it("fails if there is a problem with the trash command") {
mockLibrary.apps = []
var brokenUninstall = app // make mutable copy
brokenUninstall.bundlePath = "/dev/null"
mockLibrary.apps.append(brokenUninstall)
mockLibrary.installedApps.append(brokenUninstall)
let result = uninstall.run(options)
expect(result).to(beFailure { error in

View file

@ -0,0 +1,25 @@
//
// UpgradeCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class UpgradeCommandSpec: QuickSpec {
override func spec() {
describe("upgrade command") {
it("updates stuff") {
let cmd = UpgradeCommand()
let result = cmd.run(UpgradeCommand.Options(apps: [""]))
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -0,0 +1,25 @@
//
// VersionCommandSpec.swift
// MasKitTests
//
// Created by Ben Chatelain on 2018-12-28.
// Copyright © 2018 mas-cli. All rights reserved.
//
@testable import MasKit
import Result
import Quick
import Nimble
class VersionCommandSpec: QuickSpec {
override func spec() {
describe("version command") {
it("displays the current version") {
let cmd = VersionCommand()
let result = cmd.run(VersionCommand.Options())
print(result)
// expect(result).to(beSuccess())
}
}
}
}

View file

@ -15,8 +15,8 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.5</string>
<string>1.5.1</string>
<key>CFBundleVersion</key>
<string>10500000</string>
<string>10501000</string>
</dict>
</plist>

View file

@ -9,14 +9,18 @@
@testable import MasKit
class MockAppLibrary: AppLibrary {
var apps = [SoftwareProduct]()
var installedApps = [SoftwareProduct]()
func installedApp(appId: UInt64) -> SoftwareProduct? {
return apps.first { $0.itemIdentifier == NSNumber(value: appId) }
/// Finds an app using a bundle identifier.
///
/// - Parameter bundleId: Bundle identifier of app.
/// - Returns: Software Product of app if found; nil otherwise.
public func installedApp(forBundleId bundleId: String) -> SoftwareProduct? {
return nil
}
func uninstallApp(app: SoftwareProduct) throws {
if !apps.contains(where: { (product) -> Bool in
if !installedApps.contains(where: { (product) -> Bool in
return app.itemIdentifier == product.itemIdentifier
}) { throw MASError.notInstalled }
@ -28,3 +32,11 @@ class MockAppLibrary: AppLibrary {
// Success is the default, watch out for false positives!
}
}
/// Members not part of the AppLibrary protocol that are only for test state managment.
extension MockAppLibrary {
/// Clears out the list of installed apps.
func reset() {
installedApps = []
}
}

View file

@ -11,5 +11,6 @@
struct MockSoftwareProduct: SoftwareProduct {
var appName: String
var bundlePath: String
var bundleVersion: String
var itemIdentifier: NSNumber
}

View file

@ -31,6 +31,17 @@
B594B13021D5855D00F3AC59 /* MasAppLibrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B12F21D5855D00F3AC59 /* MasAppLibrary.swift */; };
B594B13221D5876200F3AC59 /* ResultPredicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B13121D5876200F3AC59 /* ResultPredicates.swift */; };
B594B13421D5897100F3AC59 /* MockSoftwareProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B13321D5897100F3AC59 /* MockSoftwareProduct.swift */; };
B594B13621D6D68600F3AC59 /* VersionCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B13521D6D68600F3AC59 /* VersionCommandSpec.swift */; };
B594B13821D6D6C100F3AC59 /* UpgradeCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B13721D6D6C100F3AC59 /* UpgradeCommandSpec.swift */; };
B594B13A21D6D70400F3AC59 /* SignOutCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B13921D6D70400F3AC59 /* SignOutCommandSpec.swift */; };
B594B13C21D6D72E00F3AC59 /* SignInCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B13B21D6D72E00F3AC59 /* SignInCommandSpec.swift */; };
B594B13E21D6D78900F3AC59 /* SearchCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B13D21D6D78900F3AC59 /* SearchCommandSpec.swift */; };
B594B14021D6D8BF00F3AC59 /* ResetCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B13F21D6D8BF00F3AC59 /* ResetCommandSpec.swift */; };
B594B14221D6D8EC00F3AC59 /* OutdatedCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B14121D6D8EC00F3AC59 /* OutdatedCommandSpec.swift */; };
B594B14421D6D91800F3AC59 /* LuckyCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B14321D6D91800F3AC59 /* LuckyCommandSpec.swift */; };
B594B14621D6D95700F3AC59 /* InstallCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B14521D6D95700F3AC59 /* InstallCommandSpec.swift */; };
B594B14821D6D98400F3AC59 /* InfoCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B14721D6D98400F3AC59 /* InfoCommandSpec.swift */; };
B594B14A21D6D9AE00F3AC59 /* AccountCommandSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = B594B14921D6D9AE00F3AC59 /* AccountCommandSpec.swift */; };
ED031A7C1B5127C00097692E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED031A7B1B5127C00097692E /* main.swift */; };
F83213892173D3E1008BA8A0 /* CKAccountStore.h in Headers */ = {isa = PBXBuildFile; fileRef = F8FB719B20F2EC4500F56FDC /* CKAccountStore.h */; };
F832138A2173D3E1008BA8A0 /* CKDownloadQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = F8FB719C20F2EC4500F56FDC /* CKDownloadQueue.h */; };
@ -64,7 +75,6 @@
F83213AA2173F5D0008BA8A0 /* Result.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 90CB4069213F4DDD0044E445 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
F8FB715B20F2B41400F56FDC /* MasKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8FB715220F2B41400F56FDC /* MasKit.framework */; };
F8FB716220F2B41400F56FDC /* MasKit.h in Headers */ = {isa = PBXBuildFile; fileRef = F8FB715420F2B41400F56FDC /* MasKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
F8FB716920F2B4DD00F56FDC /* CKSoftwareMap+AppLookup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4913269A1F48921D0010EB86 /* CKSoftwareMap+AppLookup.swift */; };
F8FB716A20F2B4DD00F56FDC /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0F238A1B87569C00AE40CD /* Downloader.swift */; };
F8FB716B20F2B4DD00F56FDC /* ISStoreAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0F238F1B87A56F00AE40CD /* ISStoreAccount.swift */; };
F8FB716C20F2B4DD00F56FDC /* PurchaseDownloadObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED0F23881B87543D00AE40CD /* PurchaseDownloadObserver.swift */; };
@ -153,7 +163,6 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
4913269A1F48921D0010EB86 /* CKSoftwareMap+AppLookup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CKSoftwareMap+AppLookup.swift"; sourceTree = "<group>"; };
693A98981CBFFA760004D3B4 /* Search.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Search.swift; sourceTree = "<group>"; };
693A989A1CBFFAAA0004D3B4 /* NSURLSession+Synchronous.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSURLSession+Synchronous.swift"; sourceTree = "<group>"; };
8078FAA71EC4F2FB004B5B3F /* Lucky.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lucky.swift; sourceTree = "<group>"; };
@ -176,6 +185,17 @@
B594B12F21D5855D00F3AC59 /* MasAppLibrary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasAppLibrary.swift; sourceTree = "<group>"; };
B594B13121D5876200F3AC59 /* ResultPredicates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultPredicates.swift; sourceTree = "<group>"; };
B594B13321D5897100F3AC59 /* MockSoftwareProduct.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSoftwareProduct.swift; sourceTree = "<group>"; };
B594B13521D6D68600F3AC59 /* VersionCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionCommandSpec.swift; sourceTree = "<group>"; };
B594B13721D6D6C100F3AC59 /* UpgradeCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpgradeCommandSpec.swift; sourceTree = "<group>"; };
B594B13921D6D70400F3AC59 /* SignOutCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignOutCommandSpec.swift; sourceTree = "<group>"; };
B594B13B21D6D72E00F3AC59 /* SignInCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignInCommandSpec.swift; sourceTree = "<group>"; };
B594B13D21D6D78900F3AC59 /* SearchCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchCommandSpec.swift; sourceTree = "<group>"; };
B594B13F21D6D8BF00F3AC59 /* ResetCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResetCommandSpec.swift; sourceTree = "<group>"; };
B594B14121D6D8EC00F3AC59 /* OutdatedCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutdatedCommandSpec.swift; sourceTree = "<group>"; };
B594B14321D6D91800F3AC59 /* LuckyCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LuckyCommandSpec.swift; sourceTree = "<group>"; };
B594B14521D6D95700F3AC59 /* InstallCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstallCommandSpec.swift; sourceTree = "<group>"; };
B594B14721D6D98400F3AC59 /* InfoCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoCommandSpec.swift; sourceTree = "<group>"; };
B594B14921D6D9AE00F3AC59 /* AccountCommandSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountCommandSpec.swift; sourceTree = "<group>"; };
ED031A781B5127C00097692E /* mas */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mas; sourceTree = BUILT_PRODUCTS_DIR; };
ED031A7B1B5127C00097692E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
ED0F237E1B87522400AE40CD /* Install.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Install.swift; sourceTree = "<group>"; };
@ -282,9 +302,20 @@
B594B12321D57FF300F3AC59 /* Commands */ = {
isa = PBXGroup;
children = (
B594B14921D6D9AE00F3AC59 /* AccountCommandSpec.swift */,
B594B14721D6D98400F3AC59 /* InfoCommandSpec.swift */,
B594B14521D6D95700F3AC59 /* InstallCommandSpec.swift */,
B594B12121D5416100F3AC59 /* ListCommandSpec.swift */,
B594B14321D6D91800F3AC59 /* LuckyCommandSpec.swift */,
B594B14121D6D8EC00F3AC59 /* OutdatedCommandSpec.swift */,
B594B13F21D6D8BF00F3AC59 /* ResetCommandSpec.swift */,
B594B13D21D6D78900F3AC59 /* SearchCommandSpec.swift */,
B555292C219A1FE700ACB4CA /* SearchSpec.swift */,
B594B13B21D6D72E00F3AC59 /* SignInCommandSpec.swift */,
B594B13921D6D70400F3AC59 /* SignOutCommandSpec.swift */,
B594B12421D580BB00F3AC59 /* UninstallCommandSpec.swift */,
B594B13721D6D6C100F3AC59 /* UpgradeCommandSpec.swift */,
B594B13521D6D68600F3AC59 /* VersionCommandSpec.swift */,
);
path = Commands;
sourceTree = "<group>";
@ -354,7 +385,6 @@
ED0F238E1B87A54700AE40CD /* AppStore */ = {
isa = PBXGroup;
children = (
4913269A1F48921D0010EB86 /* CKSoftwareMap+AppLookup.swift */,
B594B12A21D5837200F3AC59 /* CKSoftwareProduct+SoftwareProduct.swift */,
ED0F238A1B87569C00AE40CD /* Downloader.swift */,
ED0F238F1B87A56F00AE40CD /* ISStoreAccount.swift */,
@ -668,14 +698,25 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B594B12221D5416100F3AC59 /* ListCommandSpec.swift in Sources */,
B555292B219A1CB200ACB4CA /* MASErrorTestCase.swift in Sources */,
B594B12E21D5850700F3AC59 /* MockAppLibrary.swift in Sources */,
B594B13421D5897100F3AC59 /* MockSoftwareProduct.swift in Sources */,
B5793E2B219BE0CD00135B39 /* MockURLSession.swift in Sources */,
B594B13221D5876200F3AC59 /* ResultPredicates.swift in Sources */,
B555292D219A1FE700ACB4CA /* SearchSpec.swift in Sources */,
B555292B219A1CB200ACB4CA /* MASErrorTestCase.swift in Sources */,
B594B13421D5897100F3AC59 /* MockSoftwareProduct.swift in Sources */,
B594B14421D6D91800F3AC59 /* LuckyCommandSpec.swift in Sources */,
B594B12221D5416100F3AC59 /* ListCommandSpec.swift in Sources */,
B594B13C21D6D72E00F3AC59 /* SignInCommandSpec.swift in Sources */,
B594B13A21D6D70400F3AC59 /* SignOutCommandSpec.swift in Sources */,
B594B12521D580BB00F3AC59 /* UninstallCommandSpec.swift in Sources */,
B594B13E21D6D78900F3AC59 /* SearchCommandSpec.swift in Sources */,
B594B13821D6D6C100F3AC59 /* UpgradeCommandSpec.swift in Sources */,
B594B14821D6D98400F3AC59 /* InfoCommandSpec.swift in Sources */,
B555292D219A1FE700ACB4CA /* SearchSpec.swift in Sources */,
B594B14621D6D95700F3AC59 /* InstallCommandSpec.swift in Sources */,
B594B14021D6D8BF00F3AC59 /* ResetCommandSpec.swift in Sources */,
B594B12E21D5850700F3AC59 /* MockAppLibrary.swift in Sources */,
B594B14221D6D8EC00F3AC59 /* OutdatedCommandSpec.swift in Sources */,
B594B13221D5876200F3AC59 /* ResultPredicates.swift in Sources */,
B594B14A21D6D9AE00F3AC59 /* AccountCommandSpec.swift in Sources */,
B594B13621D6D68600F3AC59 /* VersionCommandSpec.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -723,7 +764,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
CURRENT_PROJECT_VERSION = 10500000;
CURRENT_PROJECT_VERSION = 10501000;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@ -788,7 +829,7 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CREATE_INFOPLIST_SECTION_IN_BINARY = YES;
CURRENT_PROJECT_VERSION = 10500000;
CURRENT_PROJECT_VERSION = 10501000;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -875,10 +916,10 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 10500000;
CURRENT_PROJECT_VERSION = 10501000;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 5.0;
DYLIB_CURRENT_VERSION = 5.0;
DYLIB_CURRENT_VERSION = 5.1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@ -913,10 +954,10 @@
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 10500000;
CURRENT_PROJECT_VERSION = 10501000;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 5.0;
DYLIB_CURRENT_VERSION = 5.0;
DYLIB_CURRENT_VERSION = 5.1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",

10
mas.xcworkspace/contents.xcworkspacedata generated Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:">
</FileRef>
<FileRef
location = "container:mas-cli.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Latest</string>
</dict>
</plist>

View file

@ -11,6 +11,6 @@
<key>CFBundleName</key>
<string>mas-cli</string>
<key>CFBundleShortVersionString</key>
<string>1.5</string>
<string>1.5.1</string>
</dict>
</plist>

View file

@ -23,7 +23,7 @@ main() {
# but is set per-project or per-user. By default, this is set to `$(PROJECT_DIR)/build`.
test() {
echo "==> 🏗️ Building"
echo "==> ✅ Testing"
set -o pipefail && \
xcodebuild -project "$PROJECT" \
-scheme "$SCHEME" \