diff --git a/.github/workflows/automatic_pull_request_review.yml b/.github/project_workflows/automatic_pull_request_review.yml similarity index 100% rename from .github/workflows/automatic_pull_request_review.yml rename to .github/project_workflows/automatic_pull_request_review.yml diff --git a/.github/workflows/deploy_app_store.yml b/.github/project_workflows/deploy_app_store.yml similarity index 100% rename from .github/workflows/deploy_app_store.yml rename to .github/project_workflows/deploy_app_store.yml diff --git a/.github/workflows/deploy_production_firebase.yml b/.github/project_workflows/deploy_production_firebase.yml similarity index 100% rename from .github/workflows/deploy_production_firebase.yml rename to .github/project_workflows/deploy_production_firebase.yml diff --git a/.github/workflows/deploy_staging_firebase.yml b/.github/project_workflows/deploy_staging_firebase.yml similarity index 100% rename from .github/workflows/deploy_staging_firebase.yml rename to .github/project_workflows/deploy_staging_firebase.yml diff --git a/.github/workflows/draft_a_new_release.yml b/.github/project_workflows/draft_a_new_release.yml similarity index 100% rename from .github/workflows/draft_a_new_release.yml rename to .github/project_workflows/draft_a_new_release.yml diff --git a/.github/project_workflows/publish_docs_to_wiki.yml b/.github/project_workflows/publish_docs_to_wiki.yml new file mode 100644 index 00000000..dc71b021 --- /dev/null +++ b/.github/project_workflows/publish_docs_to_wiki.yml @@ -0,0 +1,19 @@ +name: Publish docs to Wiki + +on: + push: + paths: + - .github/wiki/** + branches: + - main + - master + +jobs: + publish_docs_to_wiki: + name: Publish Wiki + uses: nimblehq/github-actions-workflows/.github/workflows/publish_wiki.yml@0.1.0 + with: + USER_NAME: team-nimblehq + USER_EMAIL: dev@nimblehq.co + secrets: + USER_TOKEN: ${{ secrets.NIMBLE_DEV_TOKEN }} diff --git a/.github/wiki/Deliverable-Configurations.md b/.github/wiki/Deliverable-Configurations.md index 1568bc23..74aac6d0 100644 --- a/.github/wiki/Deliverable-Configurations.md +++ b/.github/wiki/Deliverable-Configurations.md @@ -6,7 +6,7 @@ The file [DeliverableConstants.rb](https://github.com/nimblehq/ios-templates/blo ## Use the template -1. Running the `make.sh` script will ask if the developer wants to configure the `DeliverableConstants` file. +1. Running the `iOSTemplateMaker` script will ask if the developer wants to configure the `DeliverableConstants` file. 2. When confirming with the prompt, the template will launch Xcode to modify the `DeliverableConstants` file. ## Configure later diff --git a/.github/wiki/Getting-Started.md b/.github/wiki/Getting-Started.md index 0d2af9ad..a6f6ea48 100644 --- a/.github/wiki/Getting-Started.md +++ b/.github/wiki/Getting-Started.md @@ -9,5 +9,21 @@ 2. Clone your repository 3. Setup the project by running the following command in your terminal: ```bash - sh make.sh --bundle-id [BUNDLE_ID_PRODUCTION] --bundle-id-staging [BUNDLE_ID_STAGING] --project-name [PROJECT_NAME] + swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make ``` + +## Options + +Options are optional and will be prompted if not provided. Example is provided in (brackets). + +- `--bundle-id-production`: The application's bundle id for production variant. (co.nimblehq.project) +- `--bundle-id-staging`: The application's bundle id for staging variant. (co.nimblehq.project.staging) +- `--project-name`: The name of the project. (Project) +- `--minimum-version`: The minimum version of the iOS application. (14.0) +- `--interface`: The user interface. (UIKit or SwiftUI) + +### Example + +``` +swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make --bundle-id-production co.nimblehq.ios.templates --bundle-id-staging co.nimblehq.ios.templates.staging --project-name TemplateApp --interface SwiftUI +``` diff --git a/.github/wiki/Selecting-User-Interface.md b/.github/wiki/Selecting-User-Interface.md index 80482ea7..3d07a4a3 100644 --- a/.github/wiki/Selecting-User-Interface.md +++ b/.github/wiki/Selecting-User-Interface.md @@ -7,5 +7,5 @@ Current the template supports setup with two user interfaces: To select a user interface when creating a project pass the argument `--interface [SwiftUI or UIKit]` with the `make` script. ```bash - sh make.sh --bundle-id [BUNDLE_ID_PRODUCTION] --bundle-id-staging [BUNDLE_ID_STAGING] --project-name [PROJECT_NAME] --interface SwiftUI + swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make --interface SwiftUI ``` diff --git a/.github/workflows/test_swiftui_install_script.yml b/.github/workflows/test_swiftui_install_script.yml index 7002abae..bded91e1 100644 --- a/.github/workflows/test_swiftui_install_script.yml +++ b/.github/workflows/test_swiftui_install_script.yml @@ -19,7 +19,7 @@ jobs: run: bundle install - name: Start Install Script for SwiftUI Template App - run: sh make.sh --bundle-id co.nimblehq.template --bundle-id-staging co.nimblehq.template.staging --project-name TemplateApp --interface SwiftUI + run: swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make --bundle-id-production co.nimblehq.ios.templates --bundle-id-staging co.nimblehq.ios.templates.staging --project-name TemplateApp --interface SwiftUI - name: Build and Test run: bundle exec fastlane buildAndTest diff --git a/.github/workflows/test_uikit_install_script.yml b/.github/workflows/test_uikit_install_script.yml index d5cc86e9..3f4d0a60 100644 --- a/.github/workflows/test_uikit_install_script.yml +++ b/.github/workflows/test_uikit_install_script.yml @@ -19,7 +19,7 @@ jobs: run: bundle install - name: Start Install Script for UIKit Template App - run: sh make.sh --bundle-id co.nimblehq.template --bundle-id-staging co.nimblehq.template.staging --project-name TemplateApp --interface UIKit + run: swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make --bundle-id-production co.nimblehq.ios.templates --bundle-id-staging co.nimblehq.ios.templates.staging --project-name TemplateApp --interface UIKit - name: Build and Test run: bundle exec fastlane buildAndTest diff --git a/.github/workflows/test_upload_build_to_firebase.yml b/.github/workflows/test_upload_build_to_firebase.yml index 5f8d2655..8b0becfc 100644 --- a/.github/workflows/test_upload_build_to_firebase.yml +++ b/.github/workflows/test_upload_build_to_firebase.yml @@ -47,10 +47,10 @@ jobs: ${{ runner.os }}-pods- - name: Start Install Script for Template App - run: sh make.sh --bundle-id co.nimblehq.ios.templates --bundle-id-staging co.nimblehq.ios.templates.staging --project-name TemplateApp --interface UIKit + run: swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make --bundle-id-production co.nimblehq.ios.templates --bundle-id-staging co.nimblehq.ios.templates.staging --project-name TemplateApp --interface UIKit - name: Start Setup Script for Template App Firebase Upload - run: cat Scripts/Swift/SetUpTestFirebase.swift Scripts/Swift/Extensions/FileManager+Utils.swift | swift - + run: swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make-test-firebase env: MATCH_REPO: ${{ secrets.MATCH_REPO }} STAGING_FIREBASE_APP_ID: ${{ secrets.STAGING_FIREBASE_APP_ID }} diff --git a/.github/workflows/test_upload_build_to_test_flight.yml b/.github/workflows/test_upload_build_to_test_flight.yml index 28baf490..9d68acba 100644 --- a/.github/workflows/test_upload_build_to_test_flight.yml +++ b/.github/workflows/test_upload_build_to_test_flight.yml @@ -44,10 +44,10 @@ jobs: ${{ runner.os }}-pods- - name: Start Install Script for Template App - run: sh make.sh --bundle-id co.nimblehq.ios.templates --bundle-id-staging co.nimblehq.ios.templates.staging --project-name TemplateApp --interface UIKit + run: swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make --bundle-id-production co.nimblehq.ios.templates --bundle-id-staging co.nimblehq.ios.templates.staging --project-name TemplateApp --interface UIKit - name: Start Setup Script for Template App TestFlight Upload - run: cat Scripts/Swift/SetUpTestTestFlight.swift Scripts/Swift/Extensions/FileManager+Utils.swift | swift - + run: swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make-test-test-flight env: MATCH_REPO: ${{ secrets.MATCH_REPO }} API_KEY_ID: ${{ secrets.API_KEY_ID }} diff --git a/.gitignore b/.gitignore index d64a8594..f641b291 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,4 @@ dependencies/ # Environment .env +Scripts/Swift/iOSTemplateMaker/.* diff --git a/.swiftlint.yml b/.swiftlint.yml index 4ca7db79..e7d3d235 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -106,6 +106,7 @@ implicit_return: included: - closure - getter + - function custom_rules: multiline_collection_one_per_line: diff --git a/Gemfile b/Gemfile index 2b20351d..1b8283e2 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,8 @@ gem "danger-swiftlint" gem "danger-xcode_summary" gem 'danger-swiftformat' gem 'danger-xcov' +# Fix issue with Cocoapods 13.0 when activesupport is 7.1.0 +gem 'activesupport', '~> 7.0.0', '>= 7.0.8' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/README.md b/README.md index 648e5158..86f4b12b 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Our optimized iOS template used in our projects using Xcode Templates 2. Clone your repository 3. Setup the project by running the following command in your terminal: ```bash - sh make.sh --bundle-id [BUNDLE_ID_PRODUCTION] --bundle-id-staging [BUNDLE_ID_STAGING] --project-name [PROJECT_NAME] + swift run --package-path Scripts/Swift/iOSTemplateMaker iOSTemplateMaker make ``` ## Full Documentation @@ -53,4 +53,4 @@ We love open source and do our part in sharing our work with the community! See [our other projects][community] or [hire our team][hire] to help build your product. [community]: https://github.com/nimblehq -[hire]: https://nimblehq.co/ \ No newline at end of file +[hire]: https://nimblehq.co/ diff --git a/Scripts/Swift/SetUpTestFirebase.swift b/Scripts/Swift/SetUpTestFirebase.swift deleted file mode 100644 index f7b36fbb..00000000 --- a/Scripts/Swift/SetUpTestFirebase.swift +++ /dev/null @@ -1,16 +0,0 @@ -let teamIdPlaceholder = "<#teamId#>" -let stagingFirebaseAppIdPlaceholder = "<#stagingFirebaseAppId#>" -let firebaseTesterGroupsPlaceholder = "<#group1#>, <#group2#>" -let matchRepoPlaceholder = "git@github.com:{organization}/{repo}.git" - -let envMatchRepo = ProcessInfo.processInfo.environment["MATCH_REPO"] ?? "" -let envStagingFirebaseAppId = ProcessInfo.processInfo.environment["STAGING_FIREBASE_APP_ID"] ?? "" -let envTeamId = ProcessInfo.processInfo.environment["TEAM_ID"] ?? "" -let firebaseTesterGroup = "nimble" - -let fileManager = FileManager.default - -fileManager.replaceAllOccurrences(of: teamIdPlaceholder, to: envTeamId) -fileManager.replaceAllOccurrences(of: stagingFirebaseAppIdPlaceholder, to: envStagingFirebaseAppId) -fileManager.replaceAllOccurrences(of: firebaseTesterGroupsPlaceholder, to: firebaseTesterGroup) -fileManager.replaceAllOccurrences(of: matchRepoPlaceholder, to: envMatchRepo) diff --git a/Scripts/Swift/SetUpTestTestFlight.swift b/Scripts/Swift/SetUpTestTestFlight.swift deleted file mode 100644 index 950c4952..00000000 --- a/Scripts/Swift/SetUpTestTestFlight.swift +++ /dev/null @@ -1,16 +0,0 @@ -let teamIdPlaceholder = "<#teamId#>" -let apiKeyIdPlaceholder = "<#API_KEY_ID#>" -let issuerIdPlaceholder = "<#ISSUER_ID#>" -let matchRepoPlaceholder = "git@github.com:{organization}/{repo}.git" - -let envMatchRepo = ProcessInfo.processInfo.environment["MATCH_REPO"] ?? "" -let envApiKey = ProcessInfo.processInfo.environment["API_KEY_ID"] ?? "" -let envIssuerId = ProcessInfo.processInfo.environment["ISSUER_ID"] ?? "" -let envTeamId = ProcessInfo.processInfo.environment["TEAM_ID"] ?? "" - -let fileManager = FileManager.default - -fileManager.replaceAllOccurrences(of: teamIdPlaceholder, to: envTeamId) -fileManager.replaceAllOccurrences(of: apiKeyIdPlaceholder, to: envApiKey) -fileManager.replaceAllOccurrences(of: issuerIdPlaceholder, to: envIssuerId) -fileManager.replaceAllOccurrences(of: matchRepoPlaceholder, to: envMatchRepo) diff --git a/Scripts/Swift/iOSTemplateMaker/Package.resolved b/Scripts/Swift/iOSTemplateMaker/Package.resolved new file mode 100644 index 00000000..d429d392 --- /dev/null +++ b/Scripts/Swift/iOSTemplateMaker/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", + "version" : "1.2.3" + } + } + ], + "version" : 2 +} diff --git a/Scripts/Swift/iOSTemplateMaker/Package.swift b/Scripts/Swift/iOSTemplateMaker/Package.swift new file mode 100644 index 00000000..46e6dd43 --- /dev/null +++ b/Scripts/Swift/iOSTemplateMaker/Package.swift @@ -0,0 +1,28 @@ +// swift-tools-version: 5.7.1 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "iOSTemplateMaker", + products: [ + .executable( + name: "iOSTemplateMaker", + targets: ["iOSTemplateMaker"] + ), + ], + dependencies: [ + .package( + url: "https://github.com/apple/swift-argument-parser.git", + from: "1.0.0" + ), + ], + targets: [ + .executableTarget( + name: "iOSTemplateMaker", + dependencies: [ + .product(name: "ArgumentParser", package: "swift-argument-parser") + ] + ), + ] +) diff --git a/Scripts/Swift/Extensions/FileManager+Utils.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Extensions/FileManager+Utils.swift similarity index 82% rename from Scripts/Swift/Extensions/FileManager+Utils.swift rename to Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Extensions/FileManager+Utils.swift index 6fd75e11..77b3979a 100644 --- a/Scripts/Swift/Extensions/FileManager+Utils.swift +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Extensions/FileManager+Utils.swift @@ -46,13 +46,18 @@ extension FileManager { } } - func createFile(name: String, at directory: String) { + func createDirectory(path: String) { let currentDirectory = currentDirectoryPath do { - try createDirectory(atPath: "\(currentDirectory)/\(directory)", withIntermediateDirectories: true, attributes: nil) + try createDirectory(atPath: "\(currentDirectory)/\(path)", withIntermediateDirectories: true, attributes: nil) } catch { print("Error \(error)") } + } + + func createFile(name: String, at directory: String) { + let currentDirectory = currentDirectoryPath + createDirectory(path: directory) createFile(atPath: "\(currentDirectory)\(directory)\(name)", contents: nil) } @@ -66,7 +71,9 @@ extension FileManager { } func replaceAllOccurrences(of original: String, to replacing: String) { - let files = try? allFiles(in: currentDirectoryPath) + let swiftScriptBuildDirectory = "Scripts/Swift/iOSTemplateMaker/.build".lowercased() + let pngImage = ".png" + let files = try? allFiles(in: currentDirectoryPath, skips: [swiftScriptBuildDirectory, pngImage]) guard let files else { return print("Cannot find any files in current directory") } for file in files { do { @@ -79,7 +86,7 @@ extension FileManager { } } - private func allFiles(in directory: String) throws -> [URL] { + private func allFiles(in directory: String, skips: [String] = []) throws -> [URL] { let url = URL(fileURLWithPath: directory) var files = [URL]() if let enumerator = enumerator( @@ -90,6 +97,7 @@ extension FileManager { let hiddenFolderRegex = "\(directory.replacingOccurrences(of: "/", with: "\\/"))\\/\\..*\\/" for case let fileURL as URL in enumerator { guard !(fileURL.relativePath ~= hiddenFolderRegex) else { continue } + guard !(skips.contains(where: { fileURL.relativePath.lowercased().contains($0) })) else { continue } let fileAttributes = try? fileURL.resourceValues(forKeys:[.isRegularFileKey]) guard fileAttributes?.isRegularFile ?? false else { continue } files.append(fileURL) diff --git a/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Extensions/Optional+Utils.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Extensions/Optional+Utils.swift new file mode 100644 index 00000000..40610fbe --- /dev/null +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Extensions/Optional+Utils.swift @@ -0,0 +1,18 @@ +import Foundation + +extension Optional { + + func unwrappedOr(_ defaultValue: Wrapped) -> Wrapped { + switch self { + case .none: + return defaultValue + case let .some(value): + return value + } + } +} + +extension Optional where Wrapped == String { + + var string: String { unwrappedOr("") } +} diff --git a/Scripts/Swift/Extensions/String+Utils.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Extensions/String+Utils.swift similarity index 83% rename from Scripts/Swift/Extensions/String+Utils.swift rename to Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Extensions/String+Utils.swift index 76ccddaf..527a0b9f 100644 --- a/Scripts/Swift/Extensions/String+Utils.swift +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Extensions/String+Utils.swift @@ -1,5 +1,8 @@ +import Foundation + extension String { + /// Match string with regex expression static func ~= (lhs: String, rhs: String) -> Bool { guard let regex = try? NSRegularExpression(pattern: rhs) else { return false } let range = NSRange(location: 0, length: lhs.utf16.count) diff --git a/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Helpers/EnvironmentValue.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Helpers/EnvironmentValue.swift new file mode 100644 index 00000000..295b69c8 --- /dev/null +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Helpers/EnvironmentValue.swift @@ -0,0 +1,15 @@ +// +// EnvironmentValue.swift +// +// +// Created by Bliss on 30/8/23. +// + +import Foundation + +enum EnvironmentValue { + + static func value(for key: String) -> String? { + ProcessInfo.processInfo.environment[key] + } +} diff --git a/Scripts/Swift/Helpers/SafeShell.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Helpers/SafeShell.swift similarity index 100% rename from Scripts/Swift/Helpers/SafeShell.swift rename to Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Helpers/SafeShell.swift diff --git a/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Models/EnvironmentKey.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Models/EnvironmentKey.swift new file mode 100644 index 00000000..8fc4fa20 --- /dev/null +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/Models/EnvironmentKey.swift @@ -0,0 +1,23 @@ +// +// EnvironmentKey.swift +// +// +// Created by Bliss on 30/8/23. +// + +enum EnvironmentKey: String { + + case matchRepo = "MATCH_REPO" + case stagingFirebaseAppId = "STAGING_FIREBASE_APP_ID" + case teamId = "TEAM_ID" + case apiKey = "API_KEY_ID" + case issuerId = "ISSUER_ID" + case isCI = "CI" +} + +extension EnvironmentValue { + + static func value(for key: EnvironmentKey) -> String? { + Self.value(for: key.rawValue) + } +} diff --git a/Scripts/Swift/SetUpCICDService.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpCICDService.swift similarity index 80% rename from Scripts/Swift/SetUpCICDService.swift rename to Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpCICDService.swift index b94d97d4..06436a46 100644 --- a/Scripts/Swift/SetUpCICDService.swift +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpCICDService.swift @@ -1,3 +1,5 @@ +import Foundation + struct SetUpCICDService { enum CICDService { @@ -26,7 +28,7 @@ struct SetUpCICDService { var service: CICDService? = nil while service == nil { print("Which CI/CD service do you use (Can be edited later) [(g)ithub/(b)itrise/(c)odemagic/(l)ater]: ") - service = CICDService(readLine() ?? "") + service = CICDService(readLine().string) } switch service { @@ -34,6 +36,10 @@ struct SetUpCICDService { print("Setting template for Github Actions") fileManager.removeItems(in: "bitrise.yml") fileManager.removeItems(in: "codemagic.yaml") + fileManager.removeItems(in: ".github/workflows") + fileManager.createDirectory(path: ".github/workflows") + fileManager.moveFiles(in: ".github/project_workflows", to: ".github/workflows") + fileManager.removeItems(in: ".github/project_workflows") case .bitrise: print("Setting template for Bitrise") fileManager.removeItems(in: "codemagic.yaml") diff --git a/Scripts/Swift/SetUpDeliveryConstants.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpDeliveryConstants.swift similarity index 100% rename from Scripts/Swift/SetUpDeliveryConstants.swift rename to Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpDeliveryConstants.swift diff --git a/Scripts/Swift/SetUpInterface.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpInterface.swift similarity index 82% rename from Scripts/Swift/SetUpInterface.swift rename to Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpInterface.swift index f34d42b8..9024ea87 100644 --- a/Scripts/Swift/SetUpInterface.swift +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpInterface.swift @@ -1,3 +1,5 @@ +import Foundation + struct SetUpInterface { enum Interface { @@ -5,12 +7,13 @@ struct SetUpInterface { case swiftUI, uiKit init?(_ name: String) { - switch name.lowercased() { - case "s", "swiftui": + let name = name.lowercased() + if name == "s" || name == "swiftui" { self = .swiftUI - case "u", "uikit": + } else if name == "u" || name == "uikit" { self = .uiKit - default: return nil + } else { + return nil } } @@ -40,7 +43,7 @@ struct SetUpInterface { let folderName = interface.folderName fileManager.moveFiles(in: "tuist/Interfaces/\(folderName)/Project", to: "") - fileManager.moveFiles(in: "tuist/Interfaces/\(folderName)/Sources", to: "TemplateApp/Sources") + fileManager.moveFiles(in: "tuist/Interfaces/\(folderName)/Sources", to: "\(projectName)/Sources") fileManager.removeItems(in: "tuist/Interfaces") } } diff --git a/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpTestFirebase.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpTestFirebase.swift new file mode 100644 index 00000000..5f23b513 --- /dev/null +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpTestFirebase.swift @@ -0,0 +1,23 @@ +import Foundation + +struct SetUpTestFirebase { + + private let teamIdPlaceholder = "<#teamId#>" + private let stagingFirebaseAppIdPlaceholder = "<#stagingFirebaseAppId#>" + private let firebaseTesterGroupsPlaceholder = "<#group1#>, <#group2#>" + private let matchRepoPlaceholder = "git@github.com:{organization}/{repo}.git" + + private let firebaseTesterGroup = "nimble" + private let fileManager = FileManager.default + + let matchRepo: String + let stagingFirebaseAppId: String + let teamId: String + + func perform() { + fileManager.replaceAllOccurrences(of: teamIdPlaceholder, to: teamId) + fileManager.replaceAllOccurrences(of: stagingFirebaseAppIdPlaceholder, to: stagingFirebaseAppId) + fileManager.replaceAllOccurrences(of: firebaseTesterGroupsPlaceholder, to: firebaseTesterGroup) + fileManager.replaceAllOccurrences(of: matchRepoPlaceholder, to: matchRepo) + } +} diff --git a/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpTestTestFlight.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpTestTestFlight.swift new file mode 100644 index 00000000..5dd58752 --- /dev/null +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpTestTestFlight.swift @@ -0,0 +1,23 @@ +import Foundation + +struct SetUpTestTestFlight { + + private let teamIdPlaceholder = "<#teamId#>" + private let apiKeyIdPlaceholder = "<#API_KEY_ID#>" + private let issuerIdPlaceholder = "<#ISSUER_ID#>" + private let matchRepoPlaceholder = "git@github.com:{organization}/{repo}.git" + + private let fileManager = FileManager.default + + let matchRepo: String + let apiKey: String + let issuerId: String + let teamId: String + + func perform() { + fileManager.replaceAllOccurrences(of: teamIdPlaceholder, to: teamId) + fileManager.replaceAllOccurrences(of: apiKeyIdPlaceholder, to: apiKey) + fileManager.replaceAllOccurrences(of: issuerIdPlaceholder, to: issuerId) + fileManager.replaceAllOccurrences(of: matchRepoPlaceholder, to: matchRepo) + } +} diff --git a/Scripts/Swift/SetUpiOSProject.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpiOSProject.swift similarity index 85% rename from Scripts/Swift/SetUpiOSProject.swift rename to Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpiOSProject.swift index 9ad2ac5f..412b7a1b 100644 --- a/Scripts/Swift/SetUpiOSProject.swift +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpiOSProject.swift @@ -1,4 +1,4 @@ -#!/usr/bin/swift +import Foundation class SetUpIOSProject { @@ -14,7 +14,21 @@ class SetUpIOSProject { private var minimumVersion = "" private var interface: SetUpInterface.Interface? private var projectNameNoSpace: String { projectName.trimmingCharacters(in: .whitespacesAndNewlines) } - private var isCI = !(ProcessInfo.processInfo.environment["CI"] ?? "").isEmpty + private var isCI = !((EnvironmentValue.value(for: .isCI)).string).isEmpty + + init( + bundleIdProduction: String = "", + bundleIdStaging: String = "", + projectName: String = "", + minimumVersion: String = "", + interface: String = "" + ) { + self.bundleIdProduction = bundleIdProduction + self.bundleIdStaging = bundleIdStaging + self.projectName = projectName + self.minimumVersion = minimumVersion + self.interface = .init(interface) + } func perform() { readArguments() @@ -34,42 +48,30 @@ class SetUpIOSProject { } private func readArguments() { - // TODO: Should be replaced with ArgumentParser instead of command line - for (index, argument) in CommandLine.arguments.enumerated() { - switch index { - case 1: bundleIdProduction = argument - case 2: bundleIdStaging = argument - case 3: projectName = argument - case 4: minimumVersion = argument - case 5: interface = .init(argument) - default: break - } - } - if isCI { minimumVersion = "14.0" } while bundleIdProduction.isEmpty || !checkPackageName(bundleIdProduction) { print("BUNDLE ID PRODUCTION (i.e. com.example.project):") - bundleIdProduction = readLine() ?? "" + bundleIdProduction = readLine().string } while bundleIdStaging.isEmpty || !checkPackageName(bundleIdStaging) { print("BUNDLE ID STAGING (i.e. com.example.project.staging):") - bundleIdStaging = readLine() ?? "" + bundleIdStaging = readLine().string } while projectName.isEmpty { print("PROJECT NAME (i.e. NewProject):") - projectName = readLine() ?? "" + projectName = readLine().string } while minimumVersion.isEmpty || !checkVersion(minimumVersion) { print("iOS Minimum Version (i.e. 14.0):") - let version = readLine() ?? "" + let version = readLine().string minimumVersion = !version.isEmpty ? version : "14.0" } while interface == nil { print("Interface [(S)wiftUI or (U)IKit]:") - interface = SetUpInterface.Interface(readLine() ?? "") + interface = SetUpInterface.Interface(readLine().string) } } @@ -149,8 +151,8 @@ class SetUpIOSProject { fileManager.removeItems(in: "Workspace.swift") print("Remove script files and git/index") - fileManager.removeItems(in: "make.sh") - fileManager.removeItems(in: ".github/workflows/test_install_script.yml") + fileManager.removeItems(in: ".github/workflows/test_uikit_install_script.yml") + fileManager.removeItems(in: ".github/workflows/test_swiftui_install_script.yml") fileManager.removeItems(in: ".git/index") try safeShell("git reset") } @@ -159,8 +161,6 @@ class SetUpIOSProject { if !isCI { SetUpCICDService().perform() SetUpDeliveryConstants().perform() - fileManager.removeItems(in: "fastlane/Tests") - fileManager.removeItems(in: "set_up_test_testflight.sh") fileManager.removeItems(in: "Scripts") } print("✅ Completed") @@ -191,5 +191,3 @@ class SetUpIOSProject { return valid } } - -SetUpIOSProject().perform() diff --git a/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/iOSTemplateMaker.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/iOSTemplateMaker.swift new file mode 100644 index 00000000..ea1b341d --- /dev/null +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/iOSTemplateMaker.swift @@ -0,0 +1,78 @@ +import ArgumentParser +import Foundation + +@main +struct iOSTemplateMaker: ParsableCommand { + + static let configuration: CommandConfiguration = CommandConfiguration( + abstract: "Set up an iOS Project", + subcommands: [Make.self, MakeTestFirebase.self, MakeTestTestFlight.self], + defaultSubcommand: Make.self + ) +} + +extension iOSTemplateMaker { + + struct Make: ParsableCommand { + + @Option(help: "The production id (i.e. com.example.package)") + var bundleIdProduction: String? + @Option(help: "The staging id (i.e. com.example.package.staging)") + var bundleIdStaging: String? + @Option(help: "The project name (i.e. MyApp)") + var projectName: String? + @Option(help: "The minimum iOS version (14.0)") + var minimumVersion: String? + @Option(help: "The user interface framework (SwiftUI or UIKit)") + var interface: String? + + mutating func run() { + SetUpIOSProject( + bundleIdProduction: bundleIdProduction.string, + bundleIdStaging: bundleIdStaging.string, + projectName: projectName.string, + minimumVersion: minimumVersion.string, + interface: interface.string + ).perform() + } + } +} + +extension iOSTemplateMaker { + + struct MakeTestFirebase: ParsableCommand { + + mutating func run() { + let envMatchRepo = EnvironmentValue.value(for: .matchRepo).string + let envStagingFirebaseAppId = EnvironmentValue.value(for: .stagingFirebaseAppId).string + let envTeamId = EnvironmentValue.value(for: .teamId).string + + SetUpTestFirebase( + matchRepo: envMatchRepo, + stagingFirebaseAppId: envStagingFirebaseAppId, + teamId: envTeamId + ).perform() + } + } +} + + +extension iOSTemplateMaker { + + struct MakeTestTestFlight: ParsableCommand { + + mutating func run() { + let envMatchRepo = EnvironmentValue.value(for: .matchRepo).string + let envApiKey = EnvironmentValue.value(for: .apiKey).string + let envIssuerId = EnvironmentValue.value(for: .issuerId).string + let envTeamId = EnvironmentValue.value(for: .teamId).string + + SetUpTestTestFlight( + matchRepo: envMatchRepo, + apiKey: envApiKey, + issuerId: envIssuerId, + teamId: envTeamId + ).perform() + } + } +} diff --git a/Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Interceptors/.gitkeep b/Scripts/Swift/iOSTemplateMaker/Tests/iOSTemplateMakerTests/.gitkeep similarity index 100% rename from Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Interceptors/.gitkeep rename to Scripts/Swift/iOSTemplateMaker/Tests/iOSTemplateMakerTests/.gitkeep diff --git a/Tuist/Interfaces/SwiftUI/Project/Podfile b/Tuist/Interfaces/SwiftUI/Project/Podfile index 6dbc2e01..5c5a1e9a 100644 --- a/Tuist/Interfaces/SwiftUI/Project/Podfile +++ b/Tuist/Interfaces/SwiftUI/Project/Podfile @@ -3,10 +3,11 @@ use_frameworks! inhibit_all_warnings! def testing_pods - pod 'Quick', '~> 6.0' - pod 'Nimble', '~> 11.0' + pod 'Quick', '~> 7.0' + pod 'Nimble', '~> 12.0' pod 'Sourcery' pod 'SwiftFormat/CLI' + pod 'OHHTTPStubs/Swift', :configurations => ['Debug Staging', 'Debug Production'] end target '{PROJECT_NAME}' do diff --git a/Tuist/Interfaces/UIKit/Project/.sourcery.yml b/Tuist/Interfaces/UIKit/Project/.sourcery.yml index 29469316..492262a4 100644 --- a/Tuist/Interfaces/UIKit/Project/.sourcery.yml +++ b/Tuist/Interfaces/UIKit/Project/.sourcery.yml @@ -7,6 +7,3 @@ output: args: autoMockableTestableImports: - {PROJECT_NAME} - autoMockableImports: - - RxSwift - - RxCocoa diff --git a/Tuist/Interfaces/UIKit/Project/Podfile b/Tuist/Interfaces/UIKit/Project/Podfile index dee619b6..1d90e584 100644 --- a/Tuist/Interfaces/UIKit/Project/Podfile +++ b/Tuist/Interfaces/UIKit/Project/Podfile @@ -1,14 +1,13 @@ -platform :ios, '11.0' +platform :ios, '13.0' use_frameworks! inhibit_all_warnings! def testing_pods - pod 'Quick' - pod 'Nimble' - pod 'RxNimble', subspecs: ['RxBlocking', 'RxTest'] - pod 'RxSwift' + pod 'Quick', '~> 7.0' + pod 'Nimble', '~> 12.0' pod 'Sourcery' pod 'SwiftFormat/CLI' + pod 'OHHTTPStubs/Swift', :configurations => ['Debug Staging', 'Debug Production'] end target '{PROJECT_NAME}' do @@ -16,11 +15,9 @@ target '{PROJECT_NAME}' do pod 'Kingfisher' pod 'SnapKit' - # Rx - pod 'RxAlamofire' - pod 'RxCocoa' - pod 'RxDataSources' - pod 'RxSwift' + # Backend + pod 'Alamofire' + pod 'JSONAPIMapper', :git => 'https://github.com/nimblehq/JSONMapper', :tag => '1.1.1' # Storage pod 'KeychainAccess' diff --git a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Core/NetworkAPIError.swift b/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Core/NetworkAPIError.swift deleted file mode 100644 index 78c72199..00000000 --- a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Core/NetworkAPIError.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// NetworkAPIError.swift -// - -import Foundation - -enum NetworkAPIError: Error { - - case generic - case dataNotFound -} diff --git a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift b/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift deleted file mode 100644 index c8be3e5b..00000000 --- a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// NetworkAPIProtocol.swift -// - -import Alamofire -import RxAlamofire -import RxSwift - -protocol NetworkAPIProtocol { - - func performRequest(_ configuration: RequestConfiguration, for type: T.Type) -> Single -} - -extension NetworkAPIProtocol { - - func request( - session: Session, - configuration: RequestConfiguration, - decoder: JSONDecoder - ) -> Single { - return session.rx.request( - configuration.method, - configuration.url, - parameters: configuration.parameters, - encoding: configuration.encoding, - headers: configuration.headers, - interceptor: configuration.interceptor - ) - .responseData() - .flatMap { _, data -> Observable in - Observable.create { observer in - do { - let decodable = try decoder.decode(T.self, from: data) - observer.on(.next(decodable)) - } catch { - observer.on(.error(error)) - } - observer.on(.completed) - return Disposables.create() - } - } - .asSingle() - } -} diff --git a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Core/RequestConfiguration.swift b/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Core/RequestConfiguration.swift deleted file mode 100644 index 32ddb9d3..00000000 --- a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Core/RequestConfiguration.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// RequestConfiguration.swift -// - -import Alamofire -import Foundation - -protocol RequestConfiguration { - - var baseURL: String { get } - - var endpoint: String { get } - - var method: HTTPMethod { get } - - var url: URLConvertible { get } - - var parameters: Parameters? { get } - - var encoding: ParameterEncoding { get } - - var headers: HTTPHeaders? { get } - - var interceptor: RequestInterceptor? { get } -} - -extension RequestConfiguration { - - var url: URLConvertible { - let url = URL(string: baseURL)?.appendingPathComponent(endpoint) - return url?.absoluteString ?? "\(baseURL)\(endpoint)" - } - - var parameters: Parameters? { nil } - - var headers: HTTPHeaders? { nil } - - var interceptor: RequestInterceptor? { nil } -} diff --git a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Models/.gitkeep b/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Models/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/NetworkAPI.swift b/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/NetworkAPI.swift deleted file mode 100644 index a713490e..00000000 --- a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/NetworkAPI.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// NetworkAPI.swift -// - -import Alamofire -import Foundation -import RxSwift - -final class NetworkAPI: NetworkAPIProtocol { - - private let decoder: JSONDecoder - - init(decoder: JSONDecoder = JSONDecoder()) { - self.decoder = decoder - } - - func performRequest(_ configuration: RequestConfiguration, for type: T.Type) -> Single { - request( - session: Session(), - configuration: configuration, - decoder: decoder - ) - } -} diff --git a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/RequestConfigurations/.gitkeep b/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/RequestConfigurations/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Tuist/Interfaces/UIKit/Sources/Data/Repositories/.gitkeep b/Tuist/Interfaces/UIKit/Sources/Data/Repositories/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/make.sh b/make.sh deleted file mode 100644 index 61a83ee0..00000000 --- a/make.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/sh -set -e - -# Script inspired by https://gist.github.com/szeidner/613fe4652fc86f083cefa21879d5522b - -readonly PROGNAME=$(basename $0) -readonly WORKING_DIR=$(cd -P -- "$(dirname -- "$0")" && pwd -P) - -die() { - echo "$PROGNAME: $*" >&2 - exit 1 -} - -usage() { - if [ "$*" != "" ] ; then - echo "Error: $*" - fi - - cat << EOF -Usage: $PROGNAME --bundle-id [BUNDLE_ID_PRODUCTION] --bundle-id-staging [BUNDLE_ID_STAGING] --project-name [PROJECT_NAME] --minimum-version [MINIMUM_VERSION] --interface [INTERFACE] -Set up an iOS app from tuist template. -Options: --h, --help display this usage message and exit --b, --bundle-id [BUNDLE_ID_PRODUCTION] the production id (i.e. com.example.package) --s, --bundle-id-staging [BUNDLE_ID_STAGING] the staging id (i.e. com.example.package.staging) --n, --project-name [PROJECT_NAME] the project name (i.e. MyApp) --m, --minimum-version [MINIMUM_VERSION] the minimum version of the project (i.e. 14.0) --i, --interface [INTERFACE] the user interface frameword (SwiftUI or UIKit) -EOF - exit 1 -} - -bundle_id_production="" -bundle_id_staging="" -project_name="" -minimum_version="" -interface="" - -while [ $# -gt 0 ] ; do - case "$1" in - -h|--help) - usage - ;; - -b|--bundle-id) - bundle_id_production="$2" - shift - ;; - -s|--bundle-id-staging) - bundle_id_staging="$2" - shift - ;; - -n|--project-name) - project_name="$2" - shift - ;; - -m|--minimum-version) - minimum_version="$2" - shift - ;; - -i|--interface) - interface="$2" - shift - ;; - -*) - usage "Unknown option '$1'" - ;; - *) - usage "Too many arguments" - ;; - esac - shift -done - -cat Scripts/Swift/SetUpiOSProject.swift Scripts/Swift/SetUpCICDService.swift Scripts/Swift/SetUpDeliveryConstants.swift Scripts/Swift/SetUpInterface.swift Scripts/Swift/Extensions/FileManager+Utils.swift Scripts/Swift/Extensions/String+Utils.swift Scripts/Swift/Helpers/SafeShell.swift > t.swift && swift t.swift $bundle_id_production $bundle_id_staging $project_name "$minimum_version" $interface && rm -rf 't.swift' diff --git a/Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Core/NetworkAPIError.swift b/{PROJECT_NAME}/Sources/Data/NetworkAPI/Core/NetworkAPIError.swift similarity index 100% rename from Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Core/NetworkAPIError.swift rename to {PROJECT_NAME}/Sources/Data/NetworkAPI/Core/NetworkAPIError.swift diff --git a/Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift b/{PROJECT_NAME}/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift similarity index 77% rename from Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift rename to {PROJECT_NAME}/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift index fb6a161d..c2ee0030 100644 --- a/Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift +++ b/{PROJECT_NAME}/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift @@ -3,14 +3,13 @@ // import Alamofire -import Combine protocol NetworkAPIProtocol { func performRequest( _ configuration: RequestConfiguration, for type: T.Type - ) -> AnyPublisher + ) async throws -> T } extension NetworkAPIProtocol { @@ -19,8 +18,8 @@ extension NetworkAPIProtocol { session: Session, configuration: RequestConfiguration, decoder: JSONDecoder - ) -> AnyPublisher { - return session.request( + ) async throws -> T { + try await session.request( configuration.url, method: configuration.method, parameters: configuration.parameters, @@ -28,7 +27,7 @@ extension NetworkAPIProtocol { headers: configuration.headers, interceptor: configuration.interceptor ) - .publishDecodable(type: T.self, decoder: decoder) - .value() + .serializingDecodable(T.self) + .value } } diff --git a/Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Core/RequestConfiguration.swift b/{PROJECT_NAME}/Sources/Data/NetworkAPI/Core/RequestConfiguration.swift similarity index 100% rename from Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Core/RequestConfiguration.swift rename to {PROJECT_NAME}/Sources/Data/NetworkAPI/Core/RequestConfiguration.swift diff --git a/Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Models/.gitkeep b/{PROJECT_NAME}/Sources/Data/NetworkAPI/Interceptors/.gitkeep similarity index 100% rename from Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/Models/.gitkeep rename to {PROJECT_NAME}/Sources/Data/NetworkAPI/Interceptors/.gitkeep diff --git a/Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/RequestConfigurations/.gitkeep b/{PROJECT_NAME}/Sources/Data/NetworkAPI/Models/.gitkeep similarity index 100% rename from Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/RequestConfigurations/.gitkeep rename to {PROJECT_NAME}/Sources/Data/NetworkAPI/Models/.gitkeep diff --git a/Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/NetworkAPI.swift b/{PROJECT_NAME}/Sources/Data/NetworkAPI/NetworkAPI.swift similarity index 86% rename from Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/NetworkAPI.swift rename to {PROJECT_NAME}/Sources/Data/NetworkAPI/NetworkAPI.swift index 413a003c..d52c2081 100644 --- a/Tuist/Interfaces/SwiftUI/Sources/Data/NetworkAPI/NetworkAPI.swift +++ b/{PROJECT_NAME}/Sources/Data/NetworkAPI/NetworkAPI.swift @@ -3,7 +3,6 @@ // import Alamofire -import Combine final class NetworkAPI: NetworkAPIProtocol { @@ -16,8 +15,8 @@ final class NetworkAPI: NetworkAPIProtocol { func performRequest( _ configuration: RequestConfiguration, for type: T.Type - ) -> AnyPublisher { - request( + ) async throws -> T { + try await request( session: Session(), configuration: configuration, decoder: decoder diff --git a/Tuist/Interfaces/SwiftUI/Sources/Data/Repositories/.gitkeep b/{PROJECT_NAME}/Sources/Data/NetworkAPI/RequestConfigurations/.gitkeep similarity index 100% rename from Tuist/Interfaces/SwiftUI/Sources/Data/Repositories/.gitkeep rename to {PROJECT_NAME}/Sources/Data/NetworkAPI/RequestConfigurations/.gitkeep diff --git a/Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Interceptors/.gitkeep b/{PROJECT_NAME}/Sources/Data/Repositories/.gitkeep similarity index 100% rename from Tuist/Interfaces/UIKit/Sources/Data/NetworkAPI/Interceptors/.gitkeep rename to {PROJECT_NAME}/Sources/Data/Repositories/.gitkeep diff --git a/{PROJECT_NAME}KIFUITests/Sources/Specs/Application/ApplicationSpec.swift b/{PROJECT_NAME}KIFUITests/Sources/Specs/Application/ApplicationSpec.swift index 9380ba9f..238a4b10 100644 --- a/{PROJECT_NAME}KIFUITests/Sources/Specs/Application/ApplicationSpec.swift +++ b/{PROJECT_NAME}KIFUITests/Sources/Specs/Application/ApplicationSpec.swift @@ -6,9 +6,9 @@ import Foundation import Nimble import Quick -final class ApplicationSpec: QuickSpec { +final class ApplicationSpec: KIFSpec { - override func spec() { + override class func spec() { describe("a {PROJECT_NAME} screen") { @@ -23,7 +23,7 @@ final class ApplicationSpec: QuickSpec { context("when opens") { it("shows its UI components") { - self.tester().waitForView(withAccessibilityLabel: "Hello, world!") + tester().waitForView(withAccessibilityLabel: "Hello, world!") } } } diff --git a/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIF+Swift.swift b/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIF+Swift.swift index b7b69cd9..66696b28 100644 --- a/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIF+Swift.swift +++ b/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIF+Swift.swift @@ -5,13 +5,13 @@ import KIF -extension XCTestCase { +extension KIFSpec { - func tester(file: String = #file, _ line: Int = #line) -> KIFUITestActor { - return KIFUITestActor(inFile: file, atLine: line, delegate: self) + static func tester(file: String = #file, _ line: Int = #line) -> KIFUITestActor { + return KIFUITestActor(inFile: file, atLine: line, delegate: kifDelegate) } - func system(file: String = #file, _ line: Int = #line) -> KIFSystemTestActor { - return KIFSystemTestActor(inFile: file, atLine: line, delegate: self) + static func system(file: String = #file, _ line: Int = #line) -> KIFSystemTestActor { + return KIFSystemTestActor(inFile: file, atLine: line, delegate: kifDelegate) } } diff --git a/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIFSpec.swift b/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIFSpec.swift new file mode 100644 index 00000000..15b2e17a --- /dev/null +++ b/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIFSpec.swift @@ -0,0 +1,10 @@ +// +// KIFSpec.swift +// + +import Quick + +class KIFSpec: QuickSpec { + + static let kifDelegate = XCTestCase() +} diff --git a/{PROJECT_NAME}Tests/Sources/Dummy/.gitkeep b/{PROJECT_NAME}Tests/Sources/Dummy/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/{PROJECT_NAME}Tests/Sources/Dummy/Data/DummyNetworkModel.swift b/{PROJECT_NAME}Tests/Sources/Dummy/Data/DummyNetworkModel.swift new file mode 100644 index 00000000..e6dfb70c --- /dev/null +++ b/{PROJECT_NAME}Tests/Sources/Dummy/Data/DummyNetworkModel.swift @@ -0,0 +1,14 @@ +// +// DummyNetworkModel.swift +// + +import Foundation + +struct DummyNetworkModel: Decodable { + + static let json = + """ + {"message": "Hello"} + """ + let message: String +} diff --git a/{PROJECT_NAME}Tests/Sources/Dummy/Data/DummyRequestConfiguration.swift b/{PROJECT_NAME}Tests/Sources/Dummy/Data/DummyRequestConfiguration.swift new file mode 100644 index 00000000..1359b72b --- /dev/null +++ b/{PROJECT_NAME}Tests/Sources/Dummy/Data/DummyRequestConfiguration.swift @@ -0,0 +1,29 @@ +// +// DummyRequestConfiguration.swift +// + +import Alamofire + +@testable import {PROJECT_NAME} + +struct DummyRequestConfiguration: RequestConfiguration { + + var baseURL: String { "https://example.com" } + + var endpoint: String { "" } + + var method: Alamofire.HTTPMethod { .get } + + var encoding: Alamofire.ParameterEncoding { URLEncoding.queryString } +} + +extension DummyRequestConfiguration: RequestConfigurationStubable { + + var sampleData: Data { + DummyNetworkModel.json.data(using: .utf8) ?? Data() + } + + var path: String { + (try? url.asURL().path).string + } +} diff --git a/{PROJECT_NAME}Tests/Sources/Specs/Data/NetworkAPI/NetworkAPISpec.swift b/{PROJECT_NAME}Tests/Sources/Specs/Data/NetworkAPI/NetworkAPISpec.swift new file mode 100644 index 00000000..23e388c7 --- /dev/null +++ b/{PROJECT_NAME}Tests/Sources/Specs/Data/NetworkAPI/NetworkAPISpec.swift @@ -0,0 +1,64 @@ +// +// NetworkAPISpec.swift +// + +import Nimble +import Quick + +@testable import {PROJECT_NAME} + +final class NetworkAPISpec: AsyncSpec { + + override class func spec() { + + describe("a NetworkAPI") { + + var networkAPI: NetworkAPI! + var requestConfiguration: DummyRequestConfiguration! + + describe("its performRequest") { + + beforeEach { + requestConfiguration = DummyRequestConfiguration() + } + + afterEach { + NetworkStubber.removeAllStubs() + } + + context("when network returns value") { + + beforeEach { + NetworkStubber.stub(requestConfiguration) + networkAPI = NetworkAPI() + } + + it("returns message as Hello") { + let response = try await networkAPI.performRequest( + requestConfiguration, + for: DummyNetworkModel.self + ) + expect(response.message) == "Hello" + } + } + + context("when network returns error") { + + beforeEach { + NetworkStubber.stub(requestConfiguration, data: Data(), statusCode: 400) + networkAPI = NetworkAPI() + } + + it("throws error") { + await expect { + try await networkAPI.performRequest( + requestConfiguration, + for: DummyNetworkModel.self + ) + }.to(throwError()) + } + } + } + } + } +} diff --git a/{PROJECT_NAME}Tests/Sources/Specs/Supports/Extensions/Foundation/OptionalUnwrapSpec.swift b/{PROJECT_NAME}Tests/Sources/Specs/Supports/Extensions/Foundation/OptionalUnwrapSpec.swift index c6952037..71d436f7 100644 --- a/{PROJECT_NAME}Tests/Sources/Specs/Supports/Extensions/Foundation/OptionalUnwrapSpec.swift +++ b/{PROJECT_NAME}Tests/Sources/Specs/Supports/Extensions/Foundation/OptionalUnwrapSpec.swift @@ -9,7 +9,7 @@ import Quick final class OptionalUnwrapSpec: QuickSpec { - override func spec() { + override class func spec() { describe("an string optional") { var value: String? diff --git a/{PROJECT_NAME}Tests/Sources/Utilities/.gitkeep b/{PROJECT_NAME}Tests/Sources/Utilities/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/{PROJECT_NAME}Tests/Sources/Utilities/NetworkStubber.swift b/{PROJECT_NAME}Tests/Sources/Utilities/NetworkStubber.swift new file mode 100644 index 00000000..85ccb721 --- /dev/null +++ b/{PROJECT_NAME}Tests/Sources/Utilities/NetworkStubber.swift @@ -0,0 +1,32 @@ +// +// NetworkStubber.swift +// + +import OHHTTPStubs + +protocol RequestConfigurationStubable { + + var sampleData: Data { get } + var path: String { get } +} + +enum NetworkStubber { + + static func removeAllStubs() { + HTTPStubs.removeAllStubs() + } + + static func stub( + _ request: RequestConfigurationStubable, + data: Data? = nil, + statusCode: Int32 = 200 + ) { + OHHTTPStubs.stub(condition: isPath(request.path)) { _ in + HTTPStubsResponse( + data: data ?? request.sampleData, + statusCode: statusCode, + headers: nil + ) + } + } +}