diff --git a/.gitignore b/.gitignore index a884902..ab12654 100644 --- a/.gitignore +++ b/.gitignore @@ -23,12 +23,12 @@ Mintfile .swiftlint.yml # Danger config file which is downloaded Dangerfile -# Ruby gems config file which is downloaded -Gemfile # Bundler configuration which is bootstrapped .bundle # Bundler vendor information which is bootstrapped vendor +# Documentation script - loaded via https://raw.githubusercontent.com/Blackjacx/Scripts/master/frameworks/bootstrap.sh +scripts/make-docc-documentation.sh ## Build generated build/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 779b48a..6ee1d20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +## [0.3.0] - 2022-09-02Z +* Upgrade to Swift 5.6 - [@Blackjacx](https://github.com/blackjacx). + ## [0.2.0] - 2022-03-14Z * [#43](https://github.com/blackjacx/assist/pull/43): Async Await Support - [@Blackjacx](https://github.com/blackjacx). diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..72f0171 --- /dev/null +++ b/Gemfile @@ -0,0 +1,17 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +source "https://rubygems.org" + +gem 'fastlane' +gem 'cocoapods' +gem 'xcov' +gem 'danger' +gem 'danger-changelog' +gem 'danger-commit_lint' +gem 'danger-swiftlint' +gem 'danger-xcov' + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 4e14890..746a753 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,34 +3,34 @@ GEM specs: CFPropertyList (3.0.5) rexml - activesupport (6.1.5) + activesupport (6.1.6.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.566.0) - aws-sdk-core (3.130.0) + aws-partitions (1.625.0) + aws-sdk-core (3.139.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.55.0) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.58.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.113.0) + aws-sdk-s3 (1.114.0) aws-sdk-core (~> 3, >= 3.127.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) - aws-sigv4 (1.4.0) + aws-sigv4 (1.5.1) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -38,10 +38,10 @@ GEM cork nap open4 (~> 1.3) - cocoapods (1.11.2) + cocoapods (1.11.3) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.2) + cocoapods-core (= 1.11.3) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 1.4.0, < 2.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -56,7 +56,7 @@ GEM nap (~> 1.0) ruby-macho (>= 1.0, < 3.0) xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.2) + cocoapods-core (1.11.3) activesupport (>= 5.0, < 7) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -67,7 +67,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.5.1) + cocoapods-downloader (1.6.3) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -79,10 +79,10 @@ GEM colored2 (3.1.2) commander (4.6.0) highline (~> 2.0.0) - concurrent-ruby (1.1.9) + concurrent-ruby (1.1.10) cork (0.3.0) colored2 (~> 3.1) - danger (8.4.5) + danger (8.6.1) claide (~> 1.0) claide-plugins (>= 0.9.2) colored2 (~> 3.1) @@ -101,7 +101,7 @@ GEM danger-plugin-api (~> 1.0) danger-plugin-api (1.0.0) danger (> 2.0) - danger-swiftlint (0.30.1) + danger-swiftlint (0.30.2) danger rake (> 10) thor (~> 0.19) @@ -113,13 +113,13 @@ GEM rake (>= 12.0.0, < 14.0.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.6) + dotenv (2.8.1) emoji_regex (3.2.3) escape (0.0.4) ethon (0.15.0) ffi (>= 1.15.0) - excon (0.92.0) - faraday (1.10.0) + excon (0.92.4) + faraday (1.10.2) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -137,11 +137,11 @@ GEM faraday-em_http (1.0.0) faraday-em_synchrony (1.0.0) faraday-excon (1.1.0) - faraday-http-cache (2.2.0) + faraday-http-cache (2.4.1) faraday (>= 0.8) faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) + faraday-multipart (1.0.4) + multipart-post (~> 2) faraday-net_http (1.0.1) faraday-net_http_persistent (1.2.0) faraday-patron (1.0.0) @@ -150,7 +150,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.204.3) + fastlane (2.209.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -194,11 +194,12 @@ GEM fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) - git (1.10.2) + git (1.12.0) + addressable (~> 2.8) rchardet (~> 1.8) - google-apis-androidpublisher_v3 (0.16.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-core (0.4.2) + google-apis-androidpublisher_v3 (0.25.0) + google-apis-core (>= 0.7, < 2.a) + google-apis-core (0.7.0) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -207,27 +208,27 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.10.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-playcustomapp_v1 (0.7.0) - google-apis-core (>= 0.4, < 2.a) - google-apis-storage_v1 (0.11.0) - google-apis-core (>= 0.4, < 2.a) + google-apis-iamcredentials_v1 (0.13.0) + google-apis-core (>= 0.7, < 2.a) + google-apis-playcustomapp_v1 (0.10.0) + google-apis-core (>= 0.7, < 2.a) + google-apis-storage_v1 (0.17.0) + google-apis-core (>= 0.7, < 2.a) google-cloud-core (1.6.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.5.0) - faraday (>= 0.17.3, < 2.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) google-cloud-errors (1.2.0) - google-cloud-storage (1.36.1) + google-cloud-storage (1.39.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.17.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.1.2) + googleauth (1.2.0) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -235,22 +236,22 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.4) + http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - i18n (1.10.0) + i18n (1.12.0) concurrent-ruby (~> 1.0) jmespath (1.6.1) - json (2.6.1) - jwt (2.3.0) - kramdown (2.3.1) + json (2.6.2) + jwt (2.5.0) + kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) memoist (0.16.2) mini_magick (4.11.0) mini_mime (1.1.2) - minitest (5.15.0) + minitest (5.16.3) molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.0.0) @@ -259,17 +260,17 @@ GEM naturally (2.2.1) netrc (0.11.0) no_proxy_fix (0.1.2) - octokit (4.22.0) - faraday (>= 0.9) - sawyer (~> 0.8.0, >= 0.5.3) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) open4 (1.3.4) optparse (0.1.1) os (1.1.4) plist (3.6.0) - public_suffix (4.0.6) + public_suffix (4.0.7) rake (13.0.6) rchardet (1.8.0) - representable (3.1.1) + representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) @@ -279,13 +280,13 @@ GEM ruby-macho (2.5.1) ruby2_keywords (0.0.5) rubyzip (2.3.2) - sawyer (0.8.2) + sawyer (0.9.2) addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) + faraday (>= 0.17.3, < 3) security (0.1.3) - signet (0.16.1) + signet (0.17.0) addressable (~> 2.8) - faraday (>= 0.17.5, < 3.0) + faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simctl (1.6.8) @@ -303,16 +304,16 @@ GEM tty-cursor (~> 0.7) typhoeus (1.4.0) ethon (>= 0.9.0) - tzinfo (2.0.4) + tzinfo (2.0.5) concurrent-ruby (~> 1.0) uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8.1) + unf_ext (0.0.8.2) unicode-display_width (1.8.0) webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.21.0) + xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -331,7 +332,7 @@ GEM xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) xcresult (0.2.1) - zeitwerk (2.5.4) + zeitwerk (2.6.0) PLATFORMS ruby @@ -348,4 +349,4 @@ DEPENDENCIES xcov BUNDLED WITH - 2.3.7 + 2.3.21 diff --git a/Package.resolved b/Package.resolved index a529ff2..42b9c3a 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,70 +1,68 @@ { - "object": { - "pins": [ - { - "package": "ASCKit", - "repositoryURL": "https://github.com/blackjacx/ASCKit", - "state": { - "branch": null, - "revision": "90e05ac524dd800041af5244342cc2e5a35b5b98", - "version": "0.1.0" - } - }, - { - "package": "Engine", - "repositoryURL": "https://github.com/blackjacx/Engine", - "state": { - "branch": null, - "revision": "87ffff294809c405ccb558850a4bac93e5a32424", - "version": "0.0.3" - } - }, - { - "package": "jwt-kit", - "repositoryURL": "https://github.com/vapor/jwt-kit.git", - "state": { - "branch": null, - "revision": "5f9c44d4c196cc06c3fc601f279169f121d6f62d", - "version": "4.4.0" - } - }, - { - "package": "swift-argument-parser", - "repositoryURL": "https://github.com/Blackjacx/swift-argument-parser", - "state": { - "branch": "async", - "revision": "747d66953cd1c49e8f6110d2be9a9c51a07fd272", - "version": null - } - }, - { - "package": "swift-crypto", - "repositoryURL": "https://github.com/apple/swift-crypto.git", - "state": { - "branch": null, - "revision": "a8911e0fadc25aef1071d582355bd1037a176060", - "version": "2.0.4" - } - }, - { - "package": "SwiftKeychainWrapper", - "repositoryURL": "https://github.com/jrendel/SwiftKeychainWrapper", - "state": { - "branch": null, - "revision": "185a3165346a03767101c4f62e9a545a0fe0530f", - "version": "4.0.1" - } - }, - { - "package": "SwiftShell", - "repositoryURL": "https://github.com/kareman/SwiftShell", - "state": { - "branch": null, - "revision": "99680b2efc7c7dbcace1da0b3979d266f02e213c", - "version": "5.1.0" - } + "pins" : [ + { + "identity" : "asckit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/blackjacx/ASCKit", + "state" : { + "revision" : "90e05ac524dd800041af5244342cc2e5a35b5b98", + "version" : "0.1.0" } - ] - }, - "version": 1 + }, + { + "identity" : "engine", + "kind" : "remoteSourceControl", + "location" : "https://github.com/blackjacx/Engine", + "state" : { + "revision" : "87ffff294809c405ccb558850a4bac93e5a32424", + "version" : "0.0.3" + } + }, + { + "identity" : "jwt-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt-kit.git", + "state" : { + "revision" : "5f9c44d4c196cc06c3fc601f279169f121d6f62d", + "version" : "4.4.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "9f39744e025c7d377987f30b03770805dcb0bcd1", + "version" : "1.1.4" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "a8911e0fadc25aef1071d582355bd1037a176060", + "version" : "2.0.4" + } + }, + { + "identity" : "swiftkeychainwrapper", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jrendel/SwiftKeychainWrapper", + "state" : { + "revision" : "185a3165346a03767101c4f62e9a545a0fe0530f", + "version" : "4.0.1" + } + }, + { + "identity" : "swiftshell", + "kind" : "remoteSourceControl", + "location" : "https://github.com/kareman/SwiftShell", + "state" : { + "revision" : "99680b2efc7c7dbcace1da0b3979d266f02e213c", + "version" : "5.1.0" + } + } + ], + "version" : 2 } diff --git a/Package.swift b/Package.swift index 4903537..715db17 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.6 import PackageDescription let package = Package( @@ -16,10 +16,9 @@ let package = Package( .executable(name: "snap", targets: ["Snap"]) ], dependencies: [ - .package(name: "Engine", url: "https://github.com/blackjacx/Engine", from: "0.0.3"), - .package(name: "ASCKit", url: "https://github.com/blackjacx/ASCKit", from: "0.1.0"), - // Change back to https://github.com/Apple/swift-argument-parser - .package(url: "https://github.com/Blackjacx/swift-argument-parser", branch: "async"), + .package(url: "https://github.com/blackjacx/Engine", from: "0.0.3"), + .package(url: "https://github.com/blackjacx/ASCKit", from: "0.1.0"), + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.1.1"), .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.1.0"), .package(url: "https://github.com/kareman/SwiftShell", from: "5.1.0") ], @@ -37,6 +36,7 @@ let package = Package( name: "ASC", dependencies: [ .product(name: "ArgumentParser", package: "swift-argument-parser"), + "Core", "ASCKit", ] ), diff --git a/README.md b/README.md index dca9bbf..f8c8074 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # App Store Connect API Command-Line Tool -[![Twitter](https://img.shields.io/twitter/follow/blackjacxxx?label=%40Blackjacxxx)](https://twitter.com/blackjacx) +[![Twitter](https://img.shields.io/twitter/follow/blackjacxxx?label=%40Blackjacxxx)](https://twitter.com/blackjacxxx) [![Swift Package Manager Compatible](https://img.shields.io/badge/SPM-compatible-brightgreen.svg)](https://swift.org/package-manager/) [![Swift Versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FBlackjacx%2FAssist%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/Blackjacx/Assist) @@ -247,6 +247,28 @@ SUBCOMMANDS: - If you have a **feature request**, please open an **issue**. - If you want to **contribute**, please submit a **pull request**. +### Release + +To release this Swift package the following steps have to be taken: +- Create a new branch `release-x.y.z` +- Increment the version in `Core.Constants.version` +- Run `bash <(curl -H -s https://raw.githubusercontent.com/Blackjacx/Scripts/master/frameworks/bootstrap.sh)` to update to the latest shared development files +- Run `bundle update` to update all Ruby gems +- Commit all changes, make a PR and merge it to develop +- Run `bundle exec fastlane release framework:"Assist" version:"x.y.z" formula:"blackjacx/formulae/asc"` to release the new version +- Post the following on Twitter +``` +Assist release x.y.z πŸŽ‰ + +β–Έ πŸš€ Tools asc, snap, push (x.y.z) successfully published +β–Έ πŸ“… September 2nd +β–Έ 🌎 https://swiftpackageindex.com/Blackjacx/Assist +β–Έ 🌎 https://github.com/Blackjacx/Assist/releases/latest +β–Έ πŸ‘ Tell your friends! + +#SPM #Automated #Snapshots #Push #Firebase #APNS #ASC #AppStoreConnectAPI +``` + ## Author [Stefan Herold](mailto:stefan.herold@gmail.com) β€’ 🐦 [@Blackjacxxx](https://twitter.com/Blackjacxxx) diff --git a/Sources/ASC/commands/ASC.swift b/Sources/ASC/commands/ASC.swift index 8ea2ba8..ff323f2 100644 --- a/Sources/ASC/commands/ASC.swift +++ b/Sources/ASC/commands/ASC.swift @@ -9,10 +9,11 @@ import Foundation import ASCKit import Engine import ArgumentParser +import Core /// The main class for the App Store Connect command line tool. @main -public final class ASC: ParsableCommand { +public final class ASC: AsyncParsableCommand { /// The API key chosen by the user. If only one key is registered this one is automatically used. static var apiKey: ApiKey? @@ -22,7 +23,7 @@ public final class ASC: ParsableCommand { abstract: "A utility for accessing the App Store Connect API.", // Commands can define a version for automatic '--version' support. - version: "0.0.8", + version: Constants.version, subcommands: [Keys.self, BetaGroups.self, @@ -58,7 +59,7 @@ struct ApiKeyOptions: ParsableArguments { @Flag(name: .shortAndLong, help: "Activate verbose logging.") var verbose: Int - @Option(name: .shortAndLong, help: "The absolute path to the p8 key file.") + @Option(name: .shortAndLong, help: "The ID of the key to use.") var keyId: String? mutating func validate() throws { diff --git a/Sources/ASC/commands/sub/AppStoreVersions.swift b/Sources/ASC/commands/sub/AppStoreVersions.swift index 0fec13f..20cac72 100644 --- a/Sources/ASC/commands/sub/AppStoreVersions.swift +++ b/Sources/ASC/commands/sub/AppStoreVersions.swift @@ -11,7 +11,7 @@ import ArgumentParser extension ASC { - struct AppStoreVersions: ParsableCommand { + struct AppStoreVersions: AsyncParsableCommand { static var configuration = CommandConfiguration( abstract: "Manage versions of your app that are available in App Store.", subcommands: [List.self], @@ -23,7 +23,7 @@ extension ASC.AppStoreVersions { /// Get a list of all App Store versions of an app across all platforms. /// https://developer.apple.com/documentation/appstoreconnectapi/list_all_app_store_versions_for_an_app - struct List: ParsableCommand { + struct List: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Get a list of all App Store versions of an app across all platforms.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another diff --git a/Sources/ASC/commands/sub/Apps.swift b/Sources/ASC/commands/sub/Apps.swift index 40fbfc2..c398c58 100644 --- a/Sources/ASC/commands/sub/Apps.swift +++ b/Sources/ASC/commands/sub/Apps.swift @@ -11,7 +11,7 @@ import ArgumentParser extension ASC { - struct Apps: ParsableCommand { + struct Apps: AsyncParsableCommand { static var configuration = CommandConfiguration( abstract: "Manage your apps in App Store Connect.", subcommands: [List.self], @@ -23,7 +23,7 @@ extension ASC.Apps { /// Find and list apps added in App Store Connect. /// https://developer.apple.com/documentation/appstoreconnectapi/list_apps - struct List: ParsableCommand { + struct List: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Find and list apps added in App Store Connect.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another diff --git a/Sources/ASC/commands/sub/BetaGroups.swift b/Sources/ASC/commands/sub/BetaGroups.swift index 70fa32c..6d96f6f 100644 --- a/Sources/ASC/commands/sub/BetaGroups.swift +++ b/Sources/ASC/commands/sub/BetaGroups.swift @@ -11,7 +11,7 @@ import ArgumentParser extension ASC { - struct BetaGroups: ParsableCommand { + struct BetaGroups: AsyncParsableCommand { static var configuration = CommandConfiguration( abstract: "Manage groups of beta testers that have access to one or more builds.", subcommands: [List.self], @@ -23,7 +23,7 @@ extension ASC.BetaGroups { /// Find and list beta groups for all apps. /// https://developer.apple.com/documentation/appstoreconnectapi/list_beta_groups - struct List: ParsableCommand { + struct List: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Find and list beta testers for all apps, builds, and beta groups.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another diff --git a/Sources/ASC/commands/sub/BetaTesters.swift b/Sources/ASC/commands/sub/BetaTesters.swift index aeb4896..0f8637a 100644 --- a/Sources/ASC/commands/sub/BetaTesters.swift +++ b/Sources/ASC/commands/sub/BetaTesters.swift @@ -11,7 +11,7 @@ import ArgumentParser extension ASC { - struct BetaTesters: ParsableCommand { + struct BetaTesters: AsyncParsableCommand { static var configuration = CommandConfiguration( abstract: "Manage people who can install and test prerelease builds.", subcommands: [List.self, Invite.self, Add.self, Delete.self], @@ -23,7 +23,7 @@ extension ASC.BetaTesters { /// Find and list beta testers for all apps, builds, and beta groups. /// https://developer.apple.com/documentation/appstoreconnectapi/list_beta_testers - struct List: ParsableCommand { + struct List: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Find and list beta testers for all apps, builds, and beta groups.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another @@ -48,7 +48,7 @@ extension ASC.BetaTesters { /// Send or resend an invitation to a beta tester to test a specified app. /// https://developer.apple.com/documentation/appstoreconnectapi/send_an_invitation_to_a_beta_tester - struct Invite: ParsableCommand { + struct Invite: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Send or resend an invitation to a beta tester to test specified apps.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another @@ -69,7 +69,7 @@ extension ASC.BetaTesters { /// Create a beta tester assigned to a group, a build, or an app. /// https://developer.apple.com/documentation/appstoreconnectapi/create_a_beta_tester - struct Add: ParsableCommand { + struct Add: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Create a beta tester assigned to a group, a build, or an app.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another @@ -96,7 +96,7 @@ extension ASC.BetaTesters { /// Remove a beta tester's ability to test all or specific apps. /// https://developer.apple.com/documentation/appstoreconnectapi/delete_a_beta_tester - struct Delete: ParsableCommand { + struct Delete: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Remove a beta tester's ability to test all or specific apps.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another diff --git a/Sources/ASC/commands/sub/Builds.swift b/Sources/ASC/commands/sub/Builds.swift index c357803..3f22060 100644 --- a/Sources/ASC/commands/sub/Builds.swift +++ b/Sources/ASC/commands/sub/Builds.swift @@ -11,7 +11,7 @@ import ArgumentParser extension ASC { - struct Builds: ParsableCommand { + struct Builds: AsyncParsableCommand { static var configuration = CommandConfiguration( abstract: "Manage builds for testers and submit builds for review.", subcommands: [List.self, Expire.self], @@ -23,7 +23,7 @@ extension ASC.Builds { /// Find and list builds for all apps in App Store Connect. /// https://developer.apple.com/documentation/appstoreconnectapi/list_builds - struct List: ParsableCommand { + struct List: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Find and list builds for all apps in App Store Connect.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another @@ -48,7 +48,7 @@ extension ASC.Builds { /// Expire a build. /// https://developer.apple.com/documentation/appstoreconnectapi/modify_a_build - struct Expire: ParsableCommand { + struct Expire: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Expire a build.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another diff --git a/Sources/ASC/commands/sub/BundleIds.swift b/Sources/ASC/commands/sub/BundleIds.swift index 33fa036..d5197d1 100644 --- a/Sources/ASC/commands/sub/BundleIds.swift +++ b/Sources/ASC/commands/sub/BundleIds.swift @@ -11,7 +11,7 @@ import ArgumentParser extension ASC { - struct BundleIds: ParsableCommand { + struct BundleIds: AsyncParsableCommand { static var configuration = CommandConfiguration( abstract: "Manage the bundle IDs that uniquely identify your apps.", subcommands: [List.self, Register.self, Delete.self], @@ -23,7 +23,7 @@ extension ASC.BundleIds { /// Manage the bundle IDs that uniquely identify your apps. /// https://developer.apple.com/documentation/appstoreconnectapi/bundle_ids - struct List: ParsableCommand { + struct List: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Find and list bundle IDs that are registered to your team.") // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another @@ -48,7 +48,7 @@ extension ASC.BundleIds { /// Register a new bundle ID for app development. /// https://developer.apple.com/documentation/appstoreconnectapi/register_a_new_bundle_id - struct Register: ParsableCommand { + struct Register: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Register a new bundle ID for app development.") @@ -80,7 +80,7 @@ extension ASC.BundleIds { /// Delete a bundle ID that is used for app development. /// https://developer.apple.com/documentation/appstoreconnectapi/delete_a_bundle_id - struct Delete: ParsableCommand { + struct Delete: AsyncParsableCommand { static var configuration = CommandConfiguration(abstract: "Delete a bundle ID that is used for app development.", discussion: "You can only delete bundle IDs that are used for development. You can’t delete bundle IDs that are being used by an app in App Store Connect.") diff --git a/Sources/Core/Core.swift b/Sources/Core/Core.swift index f4c2f45..2d5c31c 100644 --- a/Sources/Core/Core.swift +++ b/Sources/Core/Core.swift @@ -7,3 +7,6 @@ import Foundation +public struct Constants { + public static let version = "0.3.0" +} diff --git a/Sources/Core/Shell/Simctl/Simctl.swift b/Sources/Core/Shell/Simctl/Simctl.swift index 08e7823..0f32013 100644 --- a/Sources/Core/Shell/Simctl/Simctl.swift +++ b/Sources/Core/Shell/Simctl/Simctl.swift @@ -220,7 +220,10 @@ public extension Simctl { // "iPhone 8" enum DeviceType: String, CaseIterable { + // DEPRECATED: Use `iPhoneSE2` instead case iPhoneSE + case iPhoneSE2 + case iPhoneSE3 case iPhone11 case iPhone11Pro case iPhone11ProMax @@ -228,12 +231,18 @@ public extension Simctl { case iPhone12 case iPhone12Pro case iPhone12ProMax + case iPhone13Mini + case iPhone13 + case iPhone13Pro + case iPhone13ProMax public var parameterName: String { rawValue } public var simCtlValue: String { switch self { case .iPhoneSE: return "iPhone SE (2nd generation)" + case .iPhoneSE2: return "iPhone SE (2nd generation)" + case .iPhoneSE3: return "iPhone SE (3rd generation)" case .iPhone11: return "iPhone 11" case .iPhone11Pro: return "iPhone 11 Pro" case .iPhone11ProMax: return "iPhone 11 Pro Max" @@ -241,23 +250,34 @@ public extension Simctl { case .iPhone12: return "iPhone 12" case .iPhone12Pro: return "iPhone 12 Pro" case .iPhone12ProMax: return "iPhone 12 Pro Max" + case .iPhone13Mini: return "iPhone 13 mini" + case .iPhone13: return "iPhone 13" + case .iPhone13Pro: return "iPhone 13 Pro" + case .iPhone13ProMax: return "iPhone 13 Pro Max" } } } - /// This enum represents only the latest ios versions from a major version. Omit the platform parameter to use the - /// latest current version. This enum is updated after a new version comes out. + /// This enum represents only the latest iOS versions from a major version. + /// Omit the platform parameter to use the latest available version. This + /// enum is updated after a new version comes out. enum Platform: String, CaseIterable { case ios12_4 = "iOS 12.4" case ios13_7 = "iOS 13.6" case ios14_5 = "iOS 14.5" case ios15_0 = "iOS 15.0" + case ios16_0 = "iOS 16.0" public var parameterName: String { "\(self)" } } enum DataNetwork: String { - case wifi, three_g = "3g", four_g = "4g", lte, lte_a = "lte-a", ltePlus = "lte+" + case wifi + case three_g = "3g" + case four_g = "4g" + case lte + case lte_a = "lte-a" + case ltePlus = "lte+" } enum WifiMode: String { diff --git a/Sources/Push/commands/Push.swift b/Sources/Push/commands/Push.swift index 4cf8d89..ca71918 100644 --- a/Sources/Push/commands/Push.swift +++ b/Sources/Push/commands/Push.swift @@ -12,14 +12,14 @@ import Core /// The main class for the Push command line tool. @main -public final class Push: ParsableCommand { +public final class Push: AsyncParsableCommand { public static var configuration = CommandConfiguration( // Optional abstracts and discussions are used for help output. abstract: "A utility for sending and testing push notifications to Apple Push Notification Service (APNS) and via Firebase.", // Commands can define a version for automatic '--version' support. - version: "0.0.8", + version: Constants.version, // Pass an array to `subcommands` to set up a nested tree of subcommands. // With language support for type-level introspection, this could be diff --git a/Sources/Push/commands/Apns.swift b/Sources/Push/commands/sub/Apns.swift similarity index 98% rename from Sources/Push/commands/Apns.swift rename to Sources/Push/commands/sub/Apns.swift index afcd7a9..b543a9c 100644 --- a/Sources/Push/commands/Apns.swift +++ b/Sources/Push/commands/sub/Apns.swift @@ -11,7 +11,7 @@ import Core extension Push { - struct Apns: ParsableCommand { + struct Apns: AsyncParsableCommand { enum Endpoint: String, ExpressibleByArgument, CaseIterable { case sandbox diff --git a/Sources/Push/commands/Fcm.swift b/Sources/Push/commands/sub/Fcm.swift similarity index 95% rename from Sources/Push/commands/Fcm.swift rename to Sources/Push/commands/sub/Fcm.swift index 11c0643..fb6b7de 100644 --- a/Sources/Push/commands/Fcm.swift +++ b/Sources/Push/commands/sub/Fcm.swift @@ -11,7 +11,7 @@ import Core extension Push { - struct Fcm: ParsableCommand { + struct Fcm: AsyncParsableCommand { static var configuration = CommandConfiguration( abstract: "Access FCM, see https://firebase.google.com/docs/cloud-messaging" diff --git a/Sources/Snap/commands/Snap.swift b/Sources/Snap/commands/Snap.swift index ba48de3..c902769 100644 --- a/Sources/Snap/commands/Snap.swift +++ b/Sources/Snap/commands/Snap.swift @@ -10,28 +10,156 @@ import Engine import ArgumentParser import Core -/// The main class for the screen-shotting tool @main public final class Snap: ParsableCommand { public static var configuration = CommandConfiguration( - // Optional abstracts and discussions are used for help output. abstract: "Make your mobile screenshot automation a breeze and blazingly fast.", + discussion: """ + This script runs your screenshot UNIT tests with the goal to + automatically generate as many screenshot variants as possible in + as less time as possible. - // Commands can define a version for automatic '--version' support. - version: "0.0.8", + These variants are: + + - all supported device classes + - all supported languages + - dark | light mode + - normal | high contrast mode + - selected dynamic font sizes (smallest, normal, largest) + + To speed up this process we do the following: + - run only one unit test: our screenshot test + - first builds the app using `build-for-testing` and then runs + tests in parallel using `test-without-building` with all variants. - // Pass an array to `subcommands` to set up a nested tree of subcommands. - // With language support for type-level introspection, this could be - // provided by automatically finding nested `ParsableCommand` types. - subcommands: [Run.self], + To finish things up we create a nice static webpage from the + results which can cycle through the screenshots automatically so + they can be viewed on a big screen. This way bugs can be detected + early. - // A default subcommand, when provided, is automatically selected if a - // subcommand is not given on the command line. - defaultSubcommand: Run.self + The generated screens will also be automatically put into nice + device mockups so the output will actually look like a real phone. + """, + + // Commands can define a version for automatic '--version' support. + version: Constants.version ) - public init() {} + // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another + // `ParsableArguments` type. + @OptionGroup() + var options: Options + + @Option(help: "The workspace used to make the screenshots.") + var workspace: String + + @Option(name: [.short, .customLong("scheme")], help: "A scheme to run the screenshot tests on. Can be specified multiple times to generate screenshots for multiple schemes.") + var schemes: [String] + + @Option(help: "The name of the TestPlan running the screenshot tests.") + var testPlanName: String? + + @Option(parsing: .upToNextOption, help: "The appearances the screenshots should be made for, e.g. --appearances \(Simctl.Style.allCases.map({"\"\($0.parameterName)\""}).joined(separator: " "))") + var appearances: [Simctl.Style] = [.light] + + @Option(parsing: .upToNextOption, help: "Devices you want to generate screenshots for, e.g. --devices \(Simctl.DeviceType.allCases.map({"\"\($0.parameterName)\""}).joined(separator: " "))") + var devices: [Simctl.DeviceType] = [.iPhone12Pro] + + @Option(help: "The destination directory where the screenshots and the zip archive should be stored.") + var destinationDir: String? + + @Option(help: "The zip file name that should be used.") + var zipFileName: String + + @Option(help: "An optional platform to be used. Omit to use the latest. Currently only iOS is supported.") + var platform: Simctl.Platform? + + public init() { + + } + + public func validate() throws { + guard FileManager.default.fileExists(atPath: workspace) else { + throw ValidationError("\(workspace) not found.") + } + + guard !schemes.isEmpty else { + throw ValidationError("No target specified.") + } + + if let destinationDir = self.destinationDir { + var isDir: ObjCBool = false + guard FileManager.default.fileExists(atPath: destinationDir, isDirectory: &isDir), isDir.boolValue else { + throw ValidationError("\(destinationDir) does not exist or is no directory.") + } + } + + if let platform = self.platform { + guard try Simctl.isPlatformValid(platform.rawValue) else { + throw ValidationError("\(platform) not installed on your system. Run `xcrun simctl list` to find available ones.") + } + } + } + + public func run() throws { + let outURL: URL + + if let destinationDir = destinationDir { + outURL = URL(fileURLWithPath: destinationDir, isDirectory: true) + } else { + outURL = try FileManager.createTemporaryDirectory() + } + + do { + let platform = try self.platform?.rawValue ?? Simctl.latestAvailableIOS() + + let configMessage = """ + Using the following config: + styles: \(appearances.map { $0.parameterName }) + devices: \(devices.map { $0.parameterName }) + platform: \(platform) + schemes: \(schemes) + destination: \(outURL.path.appendPathComponent(zipFileName)) + """ + Logger.shared.info(configMessage) + + Simctl.killAllSimulators() + + Logger.shared.info("Finding runtime for platform \(platform)") + let runtime = try Simctl.runtimeForPlatform(platform) + Logger.shared.info("Runtime found \(runtime)") + + Logger.shared.info("Find IDs of preferred device IDs") + let deviceIds = try Simctl.deviceIdsFor(deviceTypes: devices, runtime: runtime) + Logger.shared.info("Device IDs Found: \(deviceIds)") + + Logger.shared.info("Building all requested schemes for testing") + try Xcodebuild.execute(cmd: .buildForTesting, + workspace: workspace, + schemes: schemes, + deviceIds: deviceIds) + + Logger.shared.info("Taking screenshots for all requested configs") + try Simctl.snap(styles: appearances, + workspace: workspace, + schemes: schemes, + testPlanName: testPlanName, + deviceIds: deviceIds, + outURL: outURL, + zipFileName: zipFileName) + + Logger.shared.info("Find your screens in \(outURL.path)") + + } catch { + // Do not remove the destination directory when it came from outside. + // The responsibility of removal lies there. + if self.destinationDir == nil { + try FileManager.default.removeItem(at: outURL) + } + throw error + } + } } /// Here you can specify parameters valid for all sub commands. @@ -45,3 +173,7 @@ struct Options: ParsableArguments { Network.verbosityLevel = verbose } } + +extension Simctl.Style: ExpressibleByArgument {} +extension Simctl.DeviceType: ExpressibleByArgument {} +extension Simctl.Platform: ExpressibleByArgument {} diff --git a/Sources/Snap/commands/sub/Run.swift b/Sources/Snap/commands/sub/Run.swift deleted file mode 100644 index 562c538..0000000 --- a/Sources/Snap/commands/sub/Run.swift +++ /dev/null @@ -1,160 +0,0 @@ -// -// Run.swift -// -// -// Created by Stefan Herold on 21.09.20. -// - -import Foundation -import ArgumentParser -import Core -import Engine -import SwiftShell - -/// This script runs your screenshot UNIT tests with the goal to automatically generate as many screenshot varaints -/// as possible in as less time as possible. -/// -/// These variants are: -/// -/// - all supported device classes -/// - all supported languages -/// - dark | light mode -/// - normal | high contrast mode -/// - selected dynamic font sizes (smallest, normal, largest) -/// -/// To speed up this process we do the following: -/// -/// - run only one unit test: our screenshot test -/// - first builds the app using `build-for-testing` and then runs tests in parallel using `test-without-building` -/// with all variants. -/// -/// To finish things up we create a nice static webpage from the results which can cycle through the screenshots -/// automatically so they can be viewed on a big screen. This way bugs can be detected early. -/// -/// The generated screens will also be automatically put into nice device mockups so the output will actually look -/// like a real phone. -/// -extension Snap { - - struct Run: ParsableCommand { - - static var configuration = CommandConfiguration( - abstract: "Runs snapshotting using the specified parameters." - ) - - // The `@OptionGroup` attribute includes the flags, options, and arguments defined by another - // `ParsableArguments` type. - @OptionGroup() - var options: Options - - @Option(help: "The workspace used to make the screenshots.") - var workspace: String - - @Option(name: [.short, .customLong("scheme")], help: "A scheme to run the screenshot tests on. Can be specified multiple times to generate screenshots for multiple schemes.") - var schemes: [String] - - @Option(help: "The name of the TestPlan running the screenshot tests.") - var testPlanName: String? - - @Option(parsing: .upToNextOption, help: "The appearances the screenshots should be made for, e.g. --appearances \(Simctl.Style.allCases.map({"\"\($0.parameterName)\""}).joined(separator: " "))") - var appearances: [Simctl.Style] = [.light] - - @Option(parsing: .upToNextOption, help: "Devices you want to generate screenshots for, e.g. --devices \(Simctl.DeviceType.allCases.map({"\"\($0.parameterName)\""}).joined(separator: " "))") - var devices: [Simctl.DeviceType] = [.iPhone12Pro] - - @Option(help: "The destination directory where the screenshots and the zip archive should be stored.") - var destinationDir: String? - - @Option(help: "The zip file name that should be used.") - var zipFileName: String - - @Option(help: "An optional platform to be used. Omit to use the latest. Currently only iOS is supported.") - var platform: Simctl.Platform? - - func validate() throws { - guard FileManager.default.fileExists(atPath: workspace) else { - throw ValidationError("\(workspace) not found.") - } - - guard !schemes.isEmpty else { - throw ValidationError("No target specified.") - } - - if let destinationDir = self.destinationDir { - var isDir: ObjCBool = false - guard FileManager.default.fileExists(atPath: destinationDir, isDirectory: &isDir), isDir.boolValue else { - throw ValidationError("\(destinationDir) does not exist or is no directory.") - } - } - - if let platform = self.platform { - guard try Simctl.isPlatformValid(platform.rawValue) else { - throw ValidationError("\(platform) not installed on your system. Run `xcrun simctl list` to find available ones.") - } - } - } - - func run() throws { - let outURL: URL - - if let destinationDir = destinationDir { - outURL = URL(fileURLWithPath: destinationDir, isDirectory: true) - } else { - outURL = try FileManager.createTemporaryDirectory() - } - - do { - let platform = try self.platform?.rawValue ?? Simctl.latestAvailableIOS() - - let configMessage = """ - Using the following config: - styles: \(appearances.map { $0.parameterName }) - devices: \(devices.map { $0.parameterName }) - platform: \(platform) - schemes: \(schemes) - destination: \(outURL.path.appendPathComponent(zipFileName)) - """ - Logger.shared.info(configMessage) - - Simctl.killAllSimulators() - - Logger.shared.info("Finding runtime for platform \(platform)") - let runtime = try Simctl.runtimeForPlatform(platform) - Logger.shared.info("Runtime found \(runtime)") - - Logger.shared.info("Find IDs of preferred device IDs") - let deviceIds = try Simctl.deviceIdsFor(deviceTypes: devices, runtime: runtime) - Logger.shared.info("Device IDs Found: \(deviceIds)") - - Logger.shared.info("Building all requested schemes for testing") - try Xcodebuild.execute(cmd: .buildForTesting, - workspace: workspace, - schemes: schemes, - deviceIds: deviceIds) - - Logger.shared.info("Taking screenshots for all requested configs") - try Simctl.snap(styles: appearances, - workspace: workspace, - schemes: schemes, - testPlanName: testPlanName, - deviceIds: deviceIds, - outURL: outURL, - zipFileName: zipFileName) - - Logger.shared.info("Find your screens in \(outURL.path)") - - } catch { - // Do not remove the destination directory when it came from outside. - // The responsibility of removal lies there. - if self.destinationDir == nil { - try FileManager.default.removeItem(at: outURL) - } - throw error - } - } - } -} - -extension Simctl.Style: ExpressibleByArgument {} -extension Simctl.DeviceType: ExpressibleByArgument {} -extension Simctl.Platform: ExpressibleByArgument {}