diff --git a/.env.example b/.env.example index e69de29b..0096e2ae 100644 --- a/.env.example +++ b/.env.example @@ -0,0 +1,3 @@ +API_KEY_ID="" +ISSUER_ID="" +APPSTORE_CONNECT_API_KEY="" diff --git a/.github/project_workflows/deploy_app_store.yml b/.github/project_workflows/deploy_app_store.yml index a054675e..640ab706 100644 --- a/.github/project_workflows/deploy_app_store.yml +++ b/.github/project_workflows/deploy_app_store.yml @@ -27,9 +27,9 @@ jobs: fetch-depth: 0 - name: Run SwiftLint - uses: norio-nomura/action-swiftlint@3.1.0 + uses: docker://norionomura/swiftlint:0.53.0_swift-5.7 with: - args: --strict + args: swiftlint --strict build: name: Build diff --git a/.github/project_workflows/deploy_production_firebase.yml b/.github/project_workflows/deploy_production_firebase.yml index ad71aed9..3e3d989e 100644 --- a/.github/project_workflows/deploy_production_firebase.yml +++ b/.github/project_workflows/deploy_production_firebase.yml @@ -3,7 +3,7 @@ name: Deploy Production Build To Firebase # SECRETS needed: ### SSH_PRIVATE_KEY for Match Repo ### MATCH_PASS -### FIREBASE_TOKEN +### FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 on: push: @@ -25,9 +25,9 @@ jobs: fetch-depth: 0 - name: Run SwiftLint - uses: norio-nomura/action-swiftlint@3.1.0 + uses: docker://norionomura/swiftlint:0.53.0_swift-5.7 with: - args: --strict + args: swiftlint --strict build: name: Build @@ -50,6 +50,13 @@ jobs: touch .env echo $ENV | base64 --decode > .env + - name: Read Google Service Account + id: firebase_service_account + uses: timheuer/base64-to-file@v1.2 + with: + fileName: 'firebase_service_account.json' + encodedString: ${{ secrets.FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 }} + - name: Bundle install run: bundle install @@ -80,7 +87,7 @@ jobs: - name: Build Production App and Distribute to Firebase run: bundle exec fastlane buildProductionAndUploadToFirebase env: - FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.firebase_service_account.outputs.filePath }} - name: Upload Artifacts uses: actions/upload-artifact@v3 diff --git a/.github/project_workflows/deploy_staging_firebase.yml b/.github/project_workflows/deploy_staging_firebase.yml index fff0630a..82619532 100644 --- a/.github/project_workflows/deploy_staging_firebase.yml +++ b/.github/project_workflows/deploy_staging_firebase.yml @@ -3,7 +3,7 @@ name: Deploy Staging Build To Firebase # SECRETS needed: ### SSH_PRIVATE_KEY for Match Repo ### MATCH_PASS -### FIREBASE_TOKEN +### FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 on: push: @@ -25,9 +25,9 @@ jobs: fetch-depth: 0 - name: Run SwiftLint - uses: norio-nomura/action-swiftlint@3.1.0 + uses: docker://norionomura/swiftlint:0.53.0_swift-5.7 with: - args: --strict + args: swiftlint --strict build: name: Build @@ -55,6 +55,13 @@ jobs: touch .env echo $ENV | base64 --decode > .env + - name: Read Google Service Account + id: firebase_service_account + uses: timheuer/base64-to-file@v1.2 + with: + fileName: 'firebase_service_account.json' + encodedString: ${{ secrets.FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 }} + - name: Bundle install # if: steps.bundleCache.outputs.cache-hit != 'true' run: bundle install @@ -86,7 +93,7 @@ jobs: - name: Build App and Distribute to Firebase run: bundle exec fastlane buildStagingAndUploadToFirebase env: - FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.firebase_service_account.outputs.filePath }} - name: Upload Artifacts uses: actions/upload-artifact@v3 diff --git a/.github/self_hosted_project_workflows/automatic_pull_request_review.yml b/.github/self_hosted_project_workflows/automatic_pull_request_review.yml new file mode 100644 index 00000000..d539c914 --- /dev/null +++ b/.github/self_hosted_project_workflows/automatic_pull_request_review.yml @@ -0,0 +1,64 @@ +name: Automatic pull request review + +on: + pull_request: + types: [opened, reopened, edited, synchronize] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + review_pull_request: + name: Pull request review by Danger + runs-on: [self-hosted, macOS] + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/cache@v3 + id: bunlderCache + with: + path: vendor/bundle + key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + ${{ runner.os }}-gems- + + - name: Setup ENV file + env: + ENV: ${{ secrets.ENV }} + run: | + touch .env + echo $ENV | base64 --decode > .env + + - name: Bundle install + run: bundle install --path vendor/bundle + + - name: Run Arkana + run: bundle exec arkana + + - name: Cache Pods + uses: actions/cache@v3 + id: cocoapodCache + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + + - name: Install Pods Dependencies + run: bundle exec pod install + + - name: Build and Test + run: bundle exec fastlane buildAndTest + env: + CI: true + + - name: Clean up previous code coverage report + run: bundle exec fastlane cleanUpOutput + + - name: Review pull request by Danger + env: + DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bundle exec danger diff --git a/.github/self_hosted_project_workflows/deploy_app_store.yml b/.github/self_hosted_project_workflows/deploy_app_store.yml new file mode 100644 index 00000000..a29955de --- /dev/null +++ b/.github/self_hosted_project_workflows/deploy_app_store.yml @@ -0,0 +1,104 @@ +name: Deploy Build To App Store + +# SECRETS needed: +### SSH_PRIVATE_KEY for Match Repo +### MATCH_PASS +### APPSTORE_CONNECT_API_KEY +### API_KEY_ID +### ISSUER_ID + +on: + push: + branches: [ master, main ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Run SwiftLint + uses: docker://norionomura/swiftlint:0.53.0_swift-5.7 + with: + args: swiftlint --strict + + build: + name: Build + runs-on: [self-hosted, macOS] + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + # Set fetch-depth (default: 1) to get whole tree + with: + fetch-depth: 0 + + - name: Install SSH key + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Setup ENV file + env: + ENV: ${{ secrets.ENV }} + run: | + touch .env + echo $ENV | base64 --decode > .env + + - name: Bundle install + run: bundle install + + - name: Run Arkana + run: bundle exec arkana + + - name: Cache Pods + uses: actions/cache@v3 + id: cocoapodCache + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + + - name: Install Pods Dependencies + run: bundle exec pod install + shell: bash + + - name: Build and Test + run: bundle exec fastlane buildAndTest + + - name: Match AppStore + run: bundle exec fastlane syncAppStoreCodeSigning + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASS }} + + - name: Build App and Distribute to AppStore + run: bundle exec fastlane buildAndUploadToAppStore + env: + APPSTORE_CONNECT_API_KEY: ${{ secrets.APPSTORE_CONNECT_API_KEY }} + API_KEY_ID: ${{ secrets.API_KEY_ID }} + ISSUER_ID: ${{ secrets.ISSUER_ID }} + BUMP_APP_STORE_BUILD_NUMBER: "true" + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ format('v{0}({1})-{2}', env.VERSION_NUMBER, env.BUILD_NUMBER, env.TAG_TYPE) }} + path: | + ${{ env.IPA_OUTPUT_PATH }} + ${{ env.DSYM_OUTPUT_PATH }} + env: + TAG_TYPE: App_Store + + - name: Remove keychain + if: ${{ always() }} + run: bundle exec fastlane removeKeychain + continue-on-error: true diff --git a/.github/self_hosted_project_workflows/deploy_production_firebase.yml b/.github/self_hosted_project_workflows/deploy_production_firebase.yml new file mode 100644 index 00000000..461bdd01 --- /dev/null +++ b/.github/self_hosted_project_workflows/deploy_production_firebase.yml @@ -0,0 +1,105 @@ +name: Deploy Production Build To Firebase + +# SECRETS needed: +### SSH_PRIVATE_KEY for Match Repo +### MATCH_PASS +### FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 + +on: + push: + branches: [ release/** ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + Lint: + name: lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Run SwiftLint + uses: docker://norionomura/swiftlint:0.53.0_swift-5.7 + with: + args: swiftlint --strict + + build: + name: Build + runs-on: [self-hosted, macOS] + steps: + - uses: actions/checkout@v3 + # Set fetch-depth (default: 1) to get whole tree + with: + fetch-depth: 0 + + - name: Install SSH key + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Setup ENV file + env: + ENV: ${{ secrets.ENV }} + run: | + touch .env + echo $ENV | base64 --decode > .env + + - name: Read Google Service Account + id: firebase_service_account + uses: timheuer/base64-to-file@v1.2 + with: + fileName: 'firebase_service_account.json' + encodedString: ${{ secrets.FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 }} + + - name: Bundle install + run: bundle install + + - name: Run Arkana + run: bundle exec arkana + + - name: Cache Pods + uses: actions/cache@v3 + id: cocoapodCache + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + + - name: Install Pods Dependencies + run: bundle exec pod install + shell: bash + + - name: Build and Test + run: bundle exec fastlane buildAndTest + + - name: Match Ad-hoc + run: bundle exec fastlane syncAdHocProductionCodeSigning + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASS }} + + - name: Build Production App and Distribute to Firebase + run: bundle exec fastlane buildProductionAndUploadToFirebase + env: + GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.firebase_service_account.outputs.filePath }} + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ format('v{0}({1})-{2}', env.VERSION_NUMBER, env.BUILD_NUMBER, env.TAG_TYPE) }} + path: | + ${{ env.IPA_OUTPUT_PATH }} + ${{ env.DSYM_OUTPUT_PATH }} + env: + TAG_TYPE: Production_Firebase + + - name: Remove keychain + if: ${{ always() }} + run: bundle exec fastlane removeKeychain + continue-on-error: true diff --git a/.github/self_hosted_project_workflows/deploy_staging_firebase.yml b/.github/self_hosted_project_workflows/deploy_staging_firebase.yml new file mode 100644 index 00000000..21ce2095 --- /dev/null +++ b/.github/self_hosted_project_workflows/deploy_staging_firebase.yml @@ -0,0 +1,111 @@ +name: Deploy Staging Build To Firebase + +# SECRETS needed: +### SSH_PRIVATE_KEY for Match Repo +### MATCH_PASS +### FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 + +on: + push: + branches: [ develop ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Run SwiftLint + uses: docker://norionomura/swiftlint:0.53.0_swift-5.7 + with: + args: swiftlint --strict + + build: + name: Build + runs-on: [self-hosted, macOS] + steps: + - uses: actions/checkout@v3 + # Set fetch-depth (default: 1) to get whole tree + with: + fetch-depth: 0 + + - name: Install SSH key + uses: webfactory/ssh-agent@v0.7.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Install Firebase-Tools + run: | + yarn global add firebase-tools + echo "$(yarn global bin)" >> $GITHUB_PATH + + - name: Setup ENV file + env: + ENV: ${{ secrets.ENV }} + run: | + touch .env + echo $ENV | base64 --decode > .env + + - name: Read Google Service Account + id: firebase_service_account + uses: timheuer/base64-to-file@v1.2 + with: + fileName: 'firebase_service_account.json' + encodedString: ${{ secrets.FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 }} + + - name: Bundle install + # if: steps.bundleCache.outputs.cache-hit != 'true' + run: bundle install + + - name: Run Arkana + run: bundle exec arkana + + - name: Cache Pods + uses: actions/cache@v2 + id: cocoapodCache + with: + path: Pods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + + - name: Install Pods Dependencies + run: bundle exec pod install + shell: bash + + - name: Build and Test + run: bundle exec fastlane buildAndTest + + - name: Match Ad-hoc + run: bundle exec fastlane syncAdHocStagingCodeSigning + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASS }} + + - name: Build App and Distribute to Firebase + run: bundle exec fastlane buildStagingAndUploadToFirebase + env: + GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.firebase_service_account.outputs.filePath }} + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ format('v{0}({1})-{2}', env.VERSION_NUMBER, env.BUILD_NUMBER, env.TAG_TYPE) }} + path: | + ${{ env.IPA_OUTPUT_PATH }} + ${{ env.DSYM_OUTPUT_PATH }} + env: + TAG_TYPE: Staging_Firebase + + - name: Remove keychain + if: ${{ always() }} + run: bundle exec fastlane removeKeychain + continue-on-error: true diff --git a/.github/self_hosted_project_workflows/draft_a_new_release.yml b/.github/self_hosted_project_workflows/draft_a_new_release.yml new file mode 100644 index 00000000..5550e4bc --- /dev/null +++ b/.github/self_hosted_project_workflows/draft_a_new_release.yml @@ -0,0 +1,18 @@ +name: Draft a new release + +on: + push: + branches: + - main +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/self_hosted_project_workflows/publish_docs_to_wiki.yml b/.github/self_hosted_project_workflows/publish_docs_to_wiki.yml new file mode 100644 index 00000000..dc71b021 --- /dev/null +++ b/.github/self_hosted_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/CodeMagic.md b/.github/wiki/CodeMagic.md index f12bc0c6..420988f4 100644 --- a/.github/wiki/CodeMagic.md +++ b/.github/wiki/CodeMagic.md @@ -33,7 +33,7 @@ Out of the box, the CodeMagic Template has the following workflows and steps: | MATCH_PASSWORD | The password is used to encrypt/decrypt the Match repository to store the distribution certificates and provisioning profiles. | | MATCH_SSH_KEY | The SSH private key is used for cloning the Match repository that contains your distribution certificates and provisioning. | | KEYCHAIN_PASSWORD | The password to access the keychain. | -| FIREBASE_CLI_TOKEN | [Firebase token](https://firebase.google.com/docs/cli#cli-ci-systems) for uploading build to Firebase Distributions and Analytics. | +| FIREBASE_SERVICE_ACCOUNT | [Google Service Firebase Account](https://firebase.google.com/docs/app-distribution/ios/distribute-fastlane#service-acc-fastlane) for uploading build to Firebase Distributions and Analytics. | | APPSTORE_CONNECT_API_KEY | [App Store Connect API](https://docs.fastlane.tools/actions/app_store_connect_api_key/) for uploading build to TestFlight or App Store. It should be `base64` encoded. | | API_KEY_ID | The key identifier of your App Store Connect API key. | | ISSUER_ID | The issuer of your App Store Connect API key. | @@ -57,4 +57,4 @@ ROOT ├──... ``` -4. Push changes to SCM. \ No newline at end of file +4. Push changes to SCM. diff --git a/.github/wiki/Github-Actions.md b/.github/wiki/Github-Actions.md index 07693092..e96e33ad 100644 --- a/.github/wiki/Github-Actions.md +++ b/.github/wiki/Github-Actions.md @@ -53,7 +53,7 @@ Make sure the following secrets are set up. |SSH_PRIVATE_KEY |SSH key connected to a user with access to the match repo for check out the match repo. |- |✅ |✅ |✅ | |MATCH_PASS |Fastlane Match Passphrase for decrypting a match repository. |- |✅ |✅ |✅ | |APPSTORE_CONNECT_API_KEY|App Store Connect API https://docs.fastlane.tools/actions/app_store_connect_api_key/ for uploading build to TestFlight or App Store. Should be `base64` encoded.|- |- |- |✅ | -|FIREBASE_TOKEN |Firebase token https://firebase.google.com/docs/cli#cli-ci-systems for uploading build to Firebase Distributions and Analytics. |- |✅ |✅ |✅ For uploading dSYM to Crashlytics| +|FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64|Google Service Firebase Account https://firebase.google.com/docs/app-distribution/ios/distribute-fastlane#service-acc-fastlane for uploading build to Firebase Distributions and Analytics. Should be `base64` encoded.|- |✅ |✅ |✅ For uploading dSYM to Crashlytics| ## Installation @@ -63,4 +63,4 @@ Make sure the following secrets are set up. - fastlane/Constants/Constants.rb 3. Get APPSTORE_CONNECT_API_KEY base64 from AuthKey file (.p8) with `cat AuthKey_ABCDEFGH.p8 | base64`. 4. Provide SECRETS noted in `yml` file in [Github Project's Setting](https://docs.github.com/en/actions/reference/encrypted-secrets) -4. Push changes to Github \ No newline at end of file +4. Push changes to Github diff --git a/.github/wiki/Standard-File-Organization.md b/.github/wiki/Standard-File-Organization.md index 762f7cb9..07134b13 100644 --- a/.github/wiki/Standard-File-Organization.md +++ b/.github/wiki/Standard-File-Organization.md @@ -2,125 +2,168 @@ To keep all current and upcoming iOS projects aligned, we standardize an iOS project’s file organization by following this below structure: +### Common + ``` . ├── README.md -├── {ProjectName} -│   ├── Configurations -│   │   ├── Plists -│   │   └── XCConfigs -│   ├── Resources -│   │   ├── Assets -│   │   ├── Languages -│   │   └── LaunchScreen -│   └── Sources -│   ├── Application -│   │   ├── AppDelegate.swift -│   │   ├── Application.swift -│   │   └── SceneDelegate.swift -│   ├── Constants -│   │   ├── Constants+API.swift -│   │   └── Constants.swift -│   ├── Data -│   │   ├── Keychain -│   │   │   ├── Keychain.swift -│   │   │   ├── KeychainKey.swift -│   │   │   └── Models -│   │   ├── Models -│   │   ├── NetworkAPI -│   │   │   ├── AuthenticatedNetworkAPI.swift -│   │   │   ├── NetworkAPI.swift -│   │   │   ├── Core -│   │   │   ├── Interceptors -│   │   │   ├── Models -│   │   │   └── RequestConfigurations -│   │   └── Repositories -│   │   ├── RepositoryProvider.swift -│   │   ├── Authentication -│   │   └── User -│   ├── Domain -│   │   ├── Entities -│   │   │   ├── User.swift -│   │   │   └── Token.swift -│   │   ├── Interfaces -│   │   │   └── Repositories -│   │   └── UseCases -│   │   ├── UseCaseProvider.swift -│   │   ├── Authentication -│   │   └── User -│   ├── Presentation -│   │   ├── Modules -│   │   │   ├── Home -│   │   │   └── Login -│   │   ├── Navigator -│   │   │   ├── Navigator+Scene.swift -│   │   │   ├── Navigator+Transition.swift -│   │   │   └── Navigator.swift -│   │   └── Views -│   │   ├── Button -│   │   ├── CollectionView -│   │   ├── TextField -│   │   └── Transition -│   └── Supports -│   ├── Builder -│   │   └── Builder.swift -│   ├── Extensions -│   │   ├── Foundation -│   │   ├── Rx -│   │   └── UIKit -│   └── Helpers -│   ├── Rx -│   ├── Typealias -│   └── UIKit -├── {ProjectName}Tests -│   ├── Configurations -│   │   └── Plists -│   ├── Resources -│   └── Sources -│   ├── Dummy -│   │   ├── Data -│   │   │   └── Models -│   │   ├── Domain -│   │   │   └── Entities -│   │   └── Modules -│   │   └── Home -│   ├── Mocks -│   │   ├── NetworkAPIMock.swift -│   │   └── Sourcery -│   │   ├── AutoMockable.generated.swift -│   │   └── HomeViewModelProtocolMock+Equatable.swift -│   ├── Specs -│   │   ├── Data -│   │   │   └── Repositories -│   │   ├── Domain -│   │   │   └── UseCases -│   │   ├── Presentation -│   │   │   ├── Modules -│   │   │   └── Navigator -│   │   └── Supports -│   │   └── Extensions -│   └── Utilities -│   ├── Data+Decode.swift -│   ├── String+Data.swift -│   └── TestError.swift -└── {ProjectName}KIFUITests -    ├── Configurations -    │   └── Plists +├── Modules/ +│ ├── Data/ +│ │ ├── Sources/ +│ │ │ ├── NetworkAPI/ +│ │ │ │ ├── Interceptors +│ │ │ │ ├── Models +│ │ │ │ ├── RequestConfigurations +│ │ │ │ ├── Core/ +│ │ │ │ │ ├── NetworkAPIError.swift +│ │ │ │ │ ├── NetworkAPIProtocol.swift +│ │ │ │ │ └── RequestConfiguration.swift +│ │ │ │ └── NetworkAPI.swift +│ │ │ └── Repositories +│ │ └── Tests/ +│ │ ├── Resources +│ │ └── Sources/ +│ │ ├── Dummies/ +│ │ │ ├── DummyNetworkModel.swift +│ │ │ └── DummyRequestConfiguration.swift +│ │ ├── Specs/ +│ │ │ └── NetworkAPISpec.swift +│ │ └── Utilities/ +│ │ └── NetworkStubber.swift +│ └── Domain/ +│ ├── Sources/ +│ │ ├── Entities +│ │ ├── Interfaces +│ │ └── UseCases/ +│ │ └── UseCaseFactoryProtocol.swift +│ └── Tests/ +│ ├── Resources +│ └── Sources/ +│ └── DummySpec.swift +├── {ProjectName}/ +│ ├── Configurations/ +│ │ ├── Plists +│ │ └── XCConfigs +│ ├── Resources/ +│ │ ├── Assets +│ │ ├── Languages +│ │ └── LaunchScreen +│ └── Sources/ +│ ├── Application/ +│ │ └── Varies by UI Interface +│ ├── Constants/ +│ │ ├── Constants+API.swift +│ │ └── Constants.swift +│ ├── Presentations/ +│ │ └── Varies by UI Interaface +│ └── Supports/ +│ ├── Builder/ +│ │ └── Builder.swift +│ ├── Extensions/ +│ │ ├── Foundation +│ │ └── UIKit +│ └── Helpers/ +│ ├── Typealias +│ └── UIKit +├── {ProjectName}Tests/ +│ ├── Configurations/ +│ │ └── Plists +│ ├── Resources +│ └── Sources/ +│ ├── Dummy/ +│ │ ├── Data/ +│ │ │ └── Models +│ │ ├── Domain/ +│ │ │ └── Entities +│ │ └── Modules/ +│ │ └── Home +│ ├── Mocks/ +│ │ ├── NetworkAPIMock.swift +│ │ └── Sourcery/ +│ │ ├── AutoMockable.generated.swift +│ │ └── HomeViewModelProtocolMock+Equatable.swift +│ ├── Specs/ +│ │ ├── Presentation/ +│ │ │ ├── Modules +│ │ │ └── Navigator +│ │ └── Supports/ +│ │ └── Extensions +│ └── Utilities/ +│ ├── Data+Decode.swift +│ ├── String+Data.swift +│ └── TestError.swift +└── {ProjectName}KIFUITests/ + ├── Configurations/ + │ └── Plists + └── Sources/ + ├── AccessibilityIdentifiers/ + │ ├── Login + │ └── Home + ├── Flows/ + │ ├── Login + │ └── Home + ├── Screens/ + │ ├── Login + │ └── Home + ├── Specs/ + │ ├── Login + │ └── Home + └── Utilities/ + └── KIF+Swift.swift +``` + +### SwiftUI + +``` +. +└── {ProjectName}    └── Sources -    ├── AccessibilityIdentifiers -    │   ├── Login -    │   └── Home -    ├── Flows -    │   ├── Login -    │   └── Home -    ├── Screens -    │   ├── Login -    │   └── Home -    ├── Specs -    │   ├── Login -    │   └── Home -    └── Utilities -    └── KIF+Swift.swift +    ├── Application +    │   ├── {ProjectName}App.swift +    │   └── AppDelegate.swift +    └── Presentation +       ├── Models +            │   └── ProductUIModel.swift +       ├── Coordinators +            │   └── AppCoordinator.swift +       ├── Modules +       │   ├── Home +       │   └── Login +       ├── Styles +       │   └── RoundedButtonStyle.swift +       ├── ViewModifiers +       │   └── View+PrimaryNavigationBar.swift +       ├── Views +       │   └── SearchBarView.swift +       └── ViewIds +          └── ViewId.swift +``` + +### UIKit + +``` +. +└── {ProjectName} +    └── Sources +    ├── Application +    │   ├── AppDelegate.swift +    │   ├── Application.swift +    │   └── SceneDelegate.swift +    └── Presentation +       ├── Modules +       │   ├── Home +       │   └── Login +       ├── Navigator +       │   ├── Navigator+Scene.swift +       │   ├── Navigator+Transition.swift +       │   └── Navigator.swift +       └── Views +       │ ├── Button +       │ ├── CollectionView +       │ ├── TextField +       │ └── Transition +       └── ViewIds +          └── ViewId.swift ``` ## README.md @@ -131,6 +174,17 @@ To keep all current and upcoming iOS projects aligned, we standardize an iOS pro - How to set up the project? - What are project configurations? +## Modules + +This folder contains modules which represent targets in the project. Currently, it contains `Data` and `Domain` folder. + +- Data: This folder contains two subfolders + - Sources: This folder contains only `.swift` files - the main source code of the module. + - Tests: This folder contains the unit testing. +- Domain: This folder contains source files and Unit Test for the `Domain` target. + - Sources: This folder contains only `.swift` files - the main source code of the module. + - Tests: This folder contains the unit testing. + ## {ProjectName} This folder contains the main sources of the project. There are three sub-folders: diff --git a/.github/workflows/test_upload_build_to_firebase.yml b/.github/workflows/test_upload_build_to_firebase.yml index 8b0becfc..8aff8251 100644 --- a/.github/workflows/test_upload_build_to_firebase.yml +++ b/.github/workflows/test_upload_build_to_firebase.yml @@ -3,7 +3,7 @@ name: Test Upload Build to Firebase # SECRETS needed: ### SSH_PRIVATE_KEY for Match Repo ### MATCH_PASS -### FIREBASE_TOKEN +### FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 ### STAGING_FIREBASE_APP_ID ### TEAM_ID @@ -34,6 +34,13 @@ jobs: yarn global add firebase-tools echo "$(yarn global bin)" >> $GITHUB_PATH + - name: Read Google Service Account + id: firebase_service_account + uses: timheuer/base64-to-file@v1.2 + with: + fileName: 'firebase_service_account.json' + encodedString: ${{ secrets.FIREBASE_GOOGLE_APPLICATION_CREDENTIALS_BASE64 }} + - name: Bundle install run: bundle install @@ -67,7 +74,7 @@ jobs: - name: Build App and Distribute to Firebase run: bundle exec fastlane buildStagingAndUploadToFirebase env: - FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }} + GOOGLE_APPLICATION_CREDENTIALS: ${{ steps.firebase_service_account.outputs.filePath }} - name: Upload Artifacts uses: actions/upload-artifact@v3 diff --git a/.swiftformat b/.swiftformat index 472eefd4..62c8f1e6 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,5 +1,5 @@ # file options ---exclude Pods, Generated, **/*.generated.swift, fastlane/swift +--exclude Pods, Generated, **/*.generated.swift, fastlane/swift, ArkanaKeys # rules --disable fileHeader diff --git a/.swiftlint.yml b/.swiftlint.yml index e7d3d235..d0be68c4 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -9,9 +9,9 @@ excluded: - Pods - Derived - DerivedData + - ArkanaKeys opt_in_rules: - - anyobject_protocol - array_init - attributes - closure_body_length diff --git a/{PROJECT_NAME}/Sources/Data/NetworkAPI/Core/NetworkAPIError.swift b/Modules/Data/Sources/NetworkAPI/Core/NetworkAPIError.swift similarity index 100% rename from {PROJECT_NAME}/Sources/Data/NetworkAPI/Core/NetworkAPIError.swift rename to Modules/Data/Sources/NetworkAPI/Core/NetworkAPIError.swift diff --git a/{PROJECT_NAME}/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift b/Modules/Data/Sources/NetworkAPI/Core/NetworkAPIProtocol.swift similarity index 100% rename from {PROJECT_NAME}/Sources/Data/NetworkAPI/Core/NetworkAPIProtocol.swift rename to Modules/Data/Sources/NetworkAPI/Core/NetworkAPIProtocol.swift diff --git a/{PROJECT_NAME}/Sources/Data/NetworkAPI/Core/RequestConfiguration.swift b/Modules/Data/Sources/NetworkAPI/Core/RequestConfiguration.swift similarity index 100% rename from {PROJECT_NAME}/Sources/Data/NetworkAPI/Core/RequestConfiguration.swift rename to Modules/Data/Sources/NetworkAPI/Core/RequestConfiguration.swift diff --git a/{PROJECT_NAME}/Sources/Data/NetworkAPI/Interceptors/.gitkeep b/Modules/Data/Sources/NetworkAPI/Interceptors/.gitkeep similarity index 100% rename from {PROJECT_NAME}/Sources/Data/NetworkAPI/Interceptors/.gitkeep rename to Modules/Data/Sources/NetworkAPI/Interceptors/.gitkeep diff --git a/{PROJECT_NAME}/Sources/Data/NetworkAPI/Models/.gitkeep b/Modules/Data/Sources/NetworkAPI/Models/.gitkeep similarity index 100% rename from {PROJECT_NAME}/Sources/Data/NetworkAPI/Models/.gitkeep rename to Modules/Data/Sources/NetworkAPI/Models/.gitkeep diff --git a/{PROJECT_NAME}/Sources/Data/NetworkAPI/NetworkAPI.swift b/Modules/Data/Sources/NetworkAPI/NetworkAPI.swift similarity index 100% rename from {PROJECT_NAME}/Sources/Data/NetworkAPI/NetworkAPI.swift rename to Modules/Data/Sources/NetworkAPI/NetworkAPI.swift diff --git a/{PROJECT_NAME}/Sources/Data/NetworkAPI/RequestConfigurations/.gitkeep b/Modules/Data/Sources/NetworkAPI/RequestConfigurations/.gitkeep similarity index 100% rename from {PROJECT_NAME}/Sources/Data/NetworkAPI/RequestConfigurations/.gitkeep rename to Modules/Data/Sources/NetworkAPI/RequestConfigurations/.gitkeep diff --git a/{PROJECT_NAME}/Sources/Data/Repositories/.gitkeep b/Modules/Data/Sources/Repositories/.gitkeep similarity index 100% rename from {PROJECT_NAME}/Sources/Data/Repositories/.gitkeep rename to Modules/Data/Sources/Repositories/.gitkeep diff --git a/{PROJECT_NAME}/Sources/Domain/Entities/.gitkeep b/Modules/Data/Tests/Resources/.gitkeep similarity index 100% rename from {PROJECT_NAME}/Sources/Domain/Entities/.gitkeep rename to Modules/Data/Tests/Resources/.gitkeep diff --git a/{PROJECT_NAME}Tests/Sources/Dummy/Data/DummyNetworkModel.swift b/Modules/Data/Tests/Sources/Dummies/DummyNetworkModel.swift similarity index 100% rename from {PROJECT_NAME}Tests/Sources/Dummy/Data/DummyNetworkModel.swift rename to Modules/Data/Tests/Sources/Dummies/DummyNetworkModel.swift diff --git a/{PROJECT_NAME}Tests/Sources/Dummy/Data/DummyRequestConfiguration.swift b/Modules/Data/Tests/Sources/Dummies/DummyRequestConfiguration.swift similarity index 88% rename from {PROJECT_NAME}Tests/Sources/Dummy/Data/DummyRequestConfiguration.swift rename to Modules/Data/Tests/Sources/Dummies/DummyRequestConfiguration.swift index 1359b72b..b5a3d920 100644 --- a/{PROJECT_NAME}Tests/Sources/Dummy/Data/DummyRequestConfiguration.swift +++ b/Modules/Data/Tests/Sources/Dummies/DummyRequestConfiguration.swift @@ -4,7 +4,7 @@ import Alamofire -@testable import {PROJECT_NAME} +@testable import Data struct DummyRequestConfiguration: RequestConfiguration { @@ -24,6 +24,6 @@ extension DummyRequestConfiguration: RequestConfigurationStubable { } var path: String { - (try? url.asURL().path).string + (try? url.asURL().path) ?? "" } } diff --git a/{PROJECT_NAME}Tests/Sources/Specs/Data/NetworkAPI/NetworkAPISpec.swift b/Modules/Data/Tests/Sources/Specs/NetworkAPI/NetworkAPISpec.swift similarity index 93% rename from {PROJECT_NAME}Tests/Sources/Specs/Data/NetworkAPI/NetworkAPISpec.swift rename to Modules/Data/Tests/Sources/Specs/NetworkAPI/NetworkAPISpec.swift index 23e388c7..adbb01a0 100644 --- a/{PROJECT_NAME}Tests/Sources/Specs/Data/NetworkAPI/NetworkAPISpec.swift +++ b/Modules/Data/Tests/Sources/Specs/NetworkAPI/NetworkAPISpec.swift @@ -5,12 +5,13 @@ import Nimble import Quick -@testable import {PROJECT_NAME} +@testable import Data final class NetworkAPISpec: AsyncSpec { override class func spec() { + // swiftlint:disable closure_body_length describe("a NetworkAPI") { var networkAPI: NetworkAPI! @@ -60,5 +61,6 @@ final class NetworkAPISpec: AsyncSpec { } } } + // swiftlint:enable closure_body_length } } diff --git a/{PROJECT_NAME}Tests/Sources/Utilities/NetworkStubber.swift b/Modules/Data/Tests/Sources/Utilities/NetworkStubber.swift similarity index 100% rename from {PROJECT_NAME}Tests/Sources/Utilities/NetworkStubber.swift rename to Modules/Data/Tests/Sources/Utilities/NetworkStubber.swift diff --git a/{PROJECT_NAME}/Sources/Domain/Interfaces/.gitkeep b/Modules/Domain/Sources/Entities/.gitkeep similarity index 100% rename from {PROJECT_NAME}/Sources/Domain/Interfaces/.gitkeep rename to Modules/Domain/Sources/Entities/.gitkeep diff --git a/Modules/Domain/Sources/Interfaces/.gitkeep b/Modules/Domain/Sources/Interfaces/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/{PROJECT_NAME}/Sources/Domain/UseCases/UseCaseFactoryProtocol.swift b/Modules/Domain/Sources/UseCases/UseCaseFactoryProtocol.swift similarity index 100% rename from {PROJECT_NAME}/Sources/Domain/UseCases/UseCaseFactoryProtocol.swift rename to Modules/Domain/Sources/UseCases/UseCaseFactoryProtocol.swift diff --git a/Modules/Domain/Tests/Resources/.gitkeep b/Modules/Domain/Tests/Resources/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/Modules/Domain/Tests/Sources/Specs/DummySpec.swift b/Modules/Domain/Tests/Sources/Specs/DummySpec.swift new file mode 100644 index 00000000..5fc7cdc7 --- /dev/null +++ b/Modules/Domain/Tests/Sources/Specs/DummySpec.swift @@ -0,0 +1,23 @@ +// TODO: Remove this file + +import Nimble +import Quick + +@testable import Domain + +final class DummySpec: QuickSpec { + + override class func spec() { + + describe("A Dummy") { + + context("given a dummy message") { + let message = "Hello" + + it("equals Hello") { + expect(message) == "Hello" + } + } + } + } +} diff --git a/Project.swift b/Project.swift index 92d63a5e..c58b5cd2 100644 --- a/Project.swift +++ b/Project.swift @@ -6,6 +6,8 @@ let project = Project.project(name: "{PROJECT_NAME}", bundleId: "${PRODUCT_BUNDL extension Project { static func project(name: String, bundleId: String) -> Project { + let targets = Target.makeTargets(name: name, bundleId: bundleId) + return Project( name: name, organizationName: "Nimble", @@ -16,11 +18,7 @@ extension Project { settings: .settings( configurations: BuildConfiguration.allCases.map { $0.createConfiguration(projectName: name) } ), - targets: [ - .mainTarget(name: name, bundleId: bundleId), - .testsTarget(name: name, bundleId: bundleId), - .kifUITestsTarget(name: name, bundleId: bundleId), - ], + targets: targets, schemes: [ .productionScheme(name: name), .stagingScheme(name: name), diff --git a/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpCICDService.swift b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpCICDService.swift index 06436a46..972e9250 100644 --- a/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpCICDService.swift +++ b/Scripts/Swift/iOSTemplateMaker/Sources/iOSTemplateMaker/SetUpCICDService.swift @@ -21,6 +21,24 @@ struct SetUpCICDService { } } } + + enum GithubRunnerType { + + case macOSLatest, selfHosted, later + + init?(_ name: String) { + switch name.lowercased() { + case "m", "macOS": + self = .macOSLatest + case "s", "self-hosted": + self = .selfHosted + case "l", "later": + self = .later + default: + return nil + } + } + } private let fileManager = FileManager.default @@ -33,21 +51,40 @@ struct SetUpCICDService { switch service { case .github: + var runnerType: GithubRunnerType? + while runnerType == nil { + print("Which workflow runner do you want to use? [(m)acos-latest/(s)elf-hosted/(l)ater]: ") + runnerType = GithubRunnerType(readLine().string) + } 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") + switch runnerType { + case .macOSLatest: + fileManager.moveFiles(in: ".github/project_workflows", to: ".github/workflows") + fileManager.removeItems(in: ".github/project_workflows") + fileManager.removeItems(in: ".github/self_hosted_project_workflows") + case .selfHosted: + fileManager.moveFiles(in: ".github/self_hosted_project_workflows", to: ".github/workflows") + fileManager.removeItems(in: ".github/project_workflows") + fileManager.removeItems(in: ".github/self_hosted_project_workflows") + case .later, .none: + print("You can manually setup the runner later.") + } case .bitrise: print("Setting template for Bitrise") fileManager.removeItems(in: "codemagic.yaml") fileManager.removeItems(in: ".github/workflows") + fileManager.removeItems(in: ".github/project_workflows") + fileManager.removeItems(in: ".github/self_hosted_project_workflows") case .codemagic: print("Setting template for CodeMagic") fileManager.removeItems(in: "bitrise.yml") fileManager.removeItems(in: ".github/workflows") + fileManager.removeItems(in: ".github/project_workflows") + fileManager.removeItems(in: ".github/self_hosted_project_workflows") case .later, .none: print("You can manually setup the template later.") } diff --git a/Tuist/Interfaces/SwiftUI/Project/Podfile b/Tuist/Interfaces/SwiftUI/Project/Podfile index 5c5a1e9a..e39e56c6 100644 --- a/Tuist/Interfaces/SwiftUI/Project/Podfile +++ b/Tuist/Interfaces/SwiftUI/Project/Podfile @@ -1,4 +1,4 @@ -platform :ios, '14.0' +platform :ios, '{TARGET_VERSION}' use_frameworks! inhibit_all_warnings! @@ -46,6 +46,26 @@ target '{PROJECT_NAME}' do end end +def data_dependencies + pod 'Alamofire' + pod 'JSONAPIMapper', :git => 'https://github.com/nimblehq/JSONMapper', :tag => '1.1.1' +end + +target 'Data' do + data_dependencies + + target 'DataTests' do + data_dependencies + testing_pods + end +end + +target 'Domain' do + target 'DomainTests' do + testing_pods + end +end + post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| diff --git a/Tuist/Interfaces/UIKit/Project/Podfile b/Tuist/Interfaces/UIKit/Project/Podfile index 1d90e584..bb31ac75 100644 --- a/Tuist/Interfaces/UIKit/Project/Podfile +++ b/Tuist/Interfaces/UIKit/Project/Podfile @@ -1,4 +1,4 @@ -platform :ios, '13.0' +platform :ios, '{TARGET_VERSION}' use_frameworks! inhibit_all_warnings! @@ -15,10 +15,6 @@ target '{PROJECT_NAME}' do pod 'Kingfisher' pod 'SnapKit' - # Backend - pod 'Alamofire' - pod 'JSONAPIMapper', :git => 'https://github.com/nimblehq/JSONMapper', :tag => '1.1.1' - # Storage pod 'KeychainAccess' @@ -48,6 +44,25 @@ target '{PROJECT_NAME}' do end end +def data_dependencies + pod 'Alamofire' + pod 'JSONAPIMapper', :git => 'https://github.com/nimblehq/JSONMapper', :tag => '1.1.1' +end + +target 'Data' do + data_dependencies + target 'DataTests' do + data_dependencies + testing_pods + end +end + +target 'Domain' do + target 'DomainTests' do + testing_pods + end +end + post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| diff --git a/Tuist/ProjectDescriptionHelpers/Constant.swift b/Tuist/ProjectDescriptionHelpers/Constant.swift new file mode 100644 index 00000000..1d7de6e2 --- /dev/null +++ b/Tuist/ProjectDescriptionHelpers/Constant.swift @@ -0,0 +1,15 @@ +// +// Constant.swift +// ProjectDescriptionHelpers +// +// Created by Phong on 22/10/2023. +// + +public enum Constant { + + static let plistsPath = "Configurations/Plists" + static let modulesRootPath = "Modules" + static let sourcesPath = "Sources" + static let resourcesPath = "Resources" + static let testsPath = "Tests" +} diff --git a/Tuist/ProjectDescriptionHelpers/Module.swift b/Tuist/ProjectDescriptionHelpers/Module.swift new file mode 100644 index 00000000..28a6a00d --- /dev/null +++ b/Tuist/ProjectDescriptionHelpers/Module.swift @@ -0,0 +1,64 @@ +// +// Modules.swift +// ProjectDescriptionHelpers +// +// Created by Phong on 16/10/2023. +// + +import ProjectDescription + +public enum Module: CaseIterable { + + case domain + case data + + public var name: String { + switch self { + case .domain: + return "Domain" + case .data: + return "Data" + } + } + + public var dependencies: [TargetDependency] { + switch self { + case .domain: + return [] + case .data: + return [.target(name: Module.domain.name)] + } + } + + public var frameworkPath: String { + "\(Constant.modulesRootPath)/\(name)" + } + + public var sources: ProjectDescription.SourceFilesList { + ["\(frameworkPath)/\(Constant.sourcesPath)/**"] + } + + public var resources: ProjectDescription.ResourceFileElements { + [] + } + + public var testsSources: ProjectDescription.SourceFilesList { + ["\(frameworkPath)/\(Constant.testsPath)/**"] + } + + public var testsResources: ProjectDescription.ResourceFileElements { + [ + "\(frameworkPath)/\(Constant.testsPath)/**/.gitkeep", + "\(frameworkPath)/\(Constant.testsPath)/\(Constant.resourcesPath)/**" + ] + } + + + public func getBundleId(mainBundleId: String) -> String { + "\(mainBundleId).\(name)" + } + + public func getTestBundleId(mainBundleId: String) -> String { + "\(mainBundleId).\(name)\(Constant.testsPath)" + } +} diff --git a/Tuist/ProjectDescriptionHelpers/Scheme+Initializing.swift b/Tuist/ProjectDescriptionHelpers/Scheme+Initializing.swift index 8694c4c9..cda644fd 100644 --- a/Tuist/ProjectDescriptionHelpers/Scheme+Initializing.swift +++ b/Tuist/ProjectDescriptionHelpers/Scheme+Initializing.swift @@ -5,14 +5,13 @@ extension Scheme { public static func productionScheme(name: String) -> Scheme { let debugConfigName = BuildConfiguration.debugProduction.name let releaseConfigName = BuildConfiguration.releaseProduction.name + let testModules = testSchemes(name) + return Scheme( name: name, shared: true, buildAction: .buildAction(targets: ["\(name)"]), - testAction: .targets( - ["\(name)Tests", "\(name)KIFUITests"], - configuration: debugConfigName - ), + testAction: .targets(testModules, configuration: debugConfigName), runAction: .runAction(configuration: debugConfigName), archiveAction: .archiveAction(configuration: releaseConfigName), profileAction: .profileAction(configuration: debugConfigName), @@ -23,14 +22,13 @@ extension Scheme { public static func stagingScheme(name: String) -> Scheme { let debugConfigName = BuildConfiguration.debugStaging.name let releaseConfigName = BuildConfiguration.releaseStaging.name + let testModules = testSchemes(name) + return Scheme( name: "\(name) Staging", shared: true, buildAction: .buildAction(targets: ["\(name)"]), - testAction: .targets( - ["\(name)Tests", "\(name)KIFUITests"], - configuration: debugConfigName - ), + testAction: .targets(testModules, configuration: debugConfigName), runAction: .runAction(configuration: debugConfigName), archiveAction: .archiveAction(configuration: releaseConfigName), profileAction: .profileAction(configuration: debugConfigName), @@ -39,12 +37,16 @@ extension Scheme { } public static func kifUITestsScheme(name: String) -> Scheme { - let debugConfigName = BuildConfiguration.debugStaging.name - let releaseConfigName = BuildConfiguration.releaseStaging.name return Scheme( name: "\(name)KIFUITests", shared: false, hidden: true ) } + + private static func testSchemes(_ name: String) -> [TestableTarget] { + var modules = Module.allCases.map { TestableTarget("\($0.name)\(Constant.testsPath)") } + modules.append(contentsOf: ["\(name)Tests", "\(name)KIFUITests"]) + return modules + } } diff --git a/Tuist/ProjectDescriptionHelpers/Target+Initializing.swift b/Tuist/ProjectDescriptionHelpers/Target+Initializing.swift index 5e2158db..5d974c07 100644 --- a/Tuist/ProjectDescriptionHelpers/Target+Initializing.swift +++ b/Tuist/ProjectDescriptionHelpers/Target+Initializing.swift @@ -2,19 +2,41 @@ import ProjectDescription extension Target { - private static let plistsPath: String = "Configurations/Plists" + public static func makeTargets(name: String, bundleId: String) -> [Target] { + var targets: [Target] = [] - public static func mainTarget(name: String, bundleId: String) -> Target { + let frameworks = Module.allCases + .flatMap { Target.frameworkTargets(module: $0, bundleId: bundleId) } + + targets.append(contentsOf: frameworks) + + let mainTargets: [Target] = [ + .mainTarget(name: name, bundleId: bundleId), + .testsTarget(name: name, bundleId: bundleId), + .kifUITestsTarget(name: name, bundleId: bundleId) + ] + + targets.append(contentsOf: mainTargets) + + return targets + } +} + +// MARK: - Main Targets + +extension Target { + + fileprivate static func mainTarget(name: String, bundleId: String) -> Target { return Target( name: name, platform: .iOS, product: .app, bundleId: bundleId, deploymentTarget: .iOS( - targetVersion: "{TARGET_VERSION}", + targetVersion: "{TARGET_VERSION}", devices: [.iphone] ), - infoPlist: "\(name)/\(plistsPath)/Info.plist", + infoPlist: "\(name)/\(Constant.plistsPath)/Info.plist", sources: ["\(name)/Sources/**"], resources: [ "\(name)/Resources/**", @@ -26,18 +48,22 @@ extension Target { .swiftLintScript(), .swiftFormatLintScript(), .firebaseScript() + ], + dependencies: [ + .target(name: Module.data.name), + .target(name: Module.domain.name) ] ) } - public static func testsTarget(name: String, bundleId: String) -> Target { + fileprivate static func testsTarget(name: String, bundleId: String) -> Target { let targetName = "\(name)Tests" return Target( name: targetName, platform: .iOS, product: .unitTests, bundleId: bundleId, - infoPlist: "\(targetName)/\(plistsPath)/Info.plist", + infoPlist: "\(targetName)/\(Constant.plistsPath)/Info.plist", sources: ["\(targetName)/**"], resources: [ "\(targetName)/**/.gitkeep", // To include empty folders @@ -48,19 +74,51 @@ extension Target { ) } - public static func kifUITestsTarget(name: String, bundleId: String) -> Target { + fileprivate static func kifUITestsTarget(name: String, bundleId: String) -> Target { let targetName = "\(name)KIFUITests" return Target( name: targetName, platform: .iOS, product: .unitTests, bundleId: bundleId, - infoPlist: "\(targetName)/\(plistsPath)/Info.plist", + infoPlist: "\(targetName)/\(Constant.plistsPath)/Info.plist", sources: ["\(targetName)/**"], resources: [ "\(targetName)/**/.gitkeep", // To include empty folders - ], + ], dependencies: [.target(name: name)] ) } } + +// MARK: - Dependencies + +extension Target { + + fileprivate static func frameworkTargets(module: Module, bundleId: String) -> [Target] { + let framework = Target( + name: module.name, + platform: .iOS, + product: .framework, + bundleId: module.getBundleId(mainBundleId: bundleId), + deploymentTarget: .iOS( + targetVersion: "{TARGET_VERSION}", + devices: [.iphone] + ), + sources: module.sources, + resources: module.resources, + dependencies: module.dependencies + ) + + let testTarget = Target( + name: "\(module.name)\(Constant.testsPath)", + platform: .iOS, + product: .unitTests, + bundleId: module.getTestBundleId(mainBundleId: bundleId), + sources: module.testsSources, + resources: module.testsResources, + dependencies: [.target(name: module.name)] + ) + return [framework, testTarget] + } +} diff --git a/codemagic.yaml b/codemagic.yaml index 58272b88..a38fd69f 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -7,6 +7,7 @@ workflows: - fastlane xcode: latest cocoapods: default + firebase_service_account: $FIREBASE_SERVICE_ACCOUNT cache: cache_paths: - $HOME/Library/Caches/CocoaPods @@ -71,6 +72,7 @@ workflows: - fastlane xcode: latest cocoapods: default + firebase_service_account: $FIREBASE_SERVICE_ACCOUNT cache: cache_paths: - $HOME/Library/Caches/CocoaPods diff --git a/fastlane/Constants/Constant.swift b/fastlane/Constants/Constant.swift index eb134fb6..110dde40 100644 --- a/fastlane/Constants/Constant.swift +++ b/fastlane/Constants/Constant.swift @@ -28,6 +28,12 @@ enum Constant { static let appleProductionTeamId = "<#teamId#>" static let keychainName = "{PROJECT_NAME}_keychain" static let matchURL = "git@github.com:{organization}/{repo}.git" + static let apiKey: [String: Any] = [ + "key_id" : Secret.appStoreKeyIdKey, + "issuer_id": Secret.appStoreIssuerIdKey, + "key": Secret.appstoreConnectAPIKey, + "in_house": false + ] // MARK: - Path @@ -69,11 +75,6 @@ enum Constant { // MARK: - Device static let devices = ["iPhone 12 Pro Max"] - - // MARK: - Test - - static let testTarget: String = "\(projectName)Tests" - static let kifUITestTarget: String = "\(projectName)KIFUITests" } extension Constant { @@ -119,14 +120,14 @@ extension Constant { let outputDirectoryURL = URL(fileURLWithPath: Constant.outputPath) return outputDirectoryURL.appendingPathComponent(productName + ".app" + Constant.dSYMSuffix).relativePath } - + var appleUsername: String { switch self { case .staging: return Constant.appleStagingUserName case .production: return Constant.appleProductionUserName } } - + var appleTeamId: String { switch self { case .staging: return Constant.appleStagingTeamId @@ -142,7 +143,7 @@ extension Constant { case appStore = "app-store" var value: String { return rawValue } - + var match: String { switch self { case .development: return "development" @@ -150,21 +151,21 @@ extension Constant { case .appStore: return "appstore" } } - + var configuration: String { switch self { case .development: return "Debug" case .adHoc, .appStore: return "Release" } } - + var codeSignIdentity: String { switch self { case .development: return "iPhone Developer" - case .adHoc, . appStore: return "iPhone Distribution" + case .adHoc, .appStore: return "iPhone Distribution" } } - + var method: String { switch self { case .development: return "Development" diff --git a/fastlane/Constants/Secret.swift b/fastlane/Constants/Secret.swift index 9385dd07..12f37911 100644 --- a/fastlane/Constants/Secret.swift +++ b/fastlane/Constants/Secret.swift @@ -11,8 +11,6 @@ enum Secret { static let keychainPassword = EnvironmentParser.string(key: "KEYCHAIN_PASSWORD") - static let firebaseCLIToken = EnvironmentParser.string(key: "FIREBASE_TOKEN") - static let appstoreConnectAPIKey = EnvironmentParser.string(key: "APPSTORE_CONNECT_API_KEY") static let appStoreKeyIdKey = EnvironmentParser.string(key: "API_KEY_ID") diff --git a/fastlane/Fastfile.swift b/fastlane/Fastfile.swift index a5377234..5052b7ad 100644 --- a/fastlane/Fastfile.swift +++ b/fastlane/Fastfile.swift @@ -19,7 +19,7 @@ class Fastfile: LaneFile { environment: .staging ) } - + func syncDevelopmentProductionCodeSigningLane() { desc("Sync the Development match signing for the Production build") Match.syncCodeSigning( @@ -51,7 +51,7 @@ class Fastfile: LaneFile { environment: .production ) } - + func removeKeychainLane() { desc("Delete keychain") Keychain.remove() @@ -150,10 +150,6 @@ class Fastfile: LaneFile { desc("Build and Test project") Test.buildAndTest( environment: .staging, - targets: [ - Constant.testTarget, - Constant.kifUITestTarget - ], devices: Constant.devices ) } @@ -172,6 +168,7 @@ class Fastfile: LaneFile { registerDevice( name: deviceName, udid: deviceUDID, + apiKey: .userDefined(Constant.apiKey), teamId: .userDefined(Constant.appleStagingTeamId) ) diff --git a/fastlane/Helpers/Distribution.swift b/fastlane/Helpers/Distribution.swift index 9361dd4c..24548781 100644 --- a/fastlane/Helpers/Distribution.swift +++ b/fastlane/Helpers/Distribution.swift @@ -21,7 +21,6 @@ enum Distribution { app: .userDefined(environment.firebaseAppId), groups: .userDefined(groups), releaseNotes: .userDefined(releaseNotes), - firebaseCliToken: .userDefined(Secret.firebaseCLIToken), debug: .userDefined(true) ) } diff --git a/fastlane/Helpers/Keychain.swift b/fastlane/Helpers/Keychain.swift index 522a8704..3ba60e06 100644 --- a/fastlane/Helpers/Keychain.swift +++ b/fastlane/Helpers/Keychain.swift @@ -7,7 +7,7 @@ // import Foundation - + enum Keychain { static func create() { @@ -16,10 +16,10 @@ enum Keychain { password: Secret.keychainPassword, defaultKeychain: .userDefined(true), unlock: .userDefined(true), - timeout: 3600 + timeout: 115_200 ) } - + static func remove() { deleteKeychain( name: .userDefined(Constant.keychainName) diff --git a/fastlane/Helpers/Match.swift b/fastlane/Helpers/Match.swift index 2bcdd392..9d913d25 100644 --- a/fastlane/Helpers/Match.swift +++ b/fastlane/Helpers/Match.swift @@ -27,6 +27,7 @@ enum Match { type: type.match, readonly: .userDefined(!isForce), appIdentifier: [environment.bundleId], + apiKey: isForce ? .userDefined(Constant.apiKey) : .nil, username: .userDefined(environment.appleUsername), teamId: .userDefined(environment.appleTeamId), gitUrl: Constant.matchURL, @@ -35,7 +36,7 @@ enum Match { } updateCodeSigning(type: type, environment: environment) } - + static func updateCodeSigning(type: Constant.BuildType, environment: Constant.Environment) { // Update Code signing from automatic to manual updateCodeSigningSettings( @@ -43,16 +44,16 @@ enum Match { useAutomaticSigning: .userDefined(false), teamId: .userDefined(environment.appleTeamId), targets: .userDefined([Constant.projectName]), - buildConfigurations: .userDefined([Self.createBuildConfiguration(type: type, environment: environment)]), + buildConfigurations: .userDefined([createBuildConfiguration(type: type, environment: environment)]), codeSignIdentity: .userDefined(type.codeSignIdentity), - profileName: .userDefined(Self.createProfileName(type: type, environment: environment)) + profileName: .userDefined(createProfileName(type: type, environment: environment)) ) } - + static func createBuildConfiguration(type: Constant.BuildType, environment: Constant.Environment) -> String { "\(type.configuration) \(environment.rawValue)" } - + static func createProfileName(type: Constant.BuildType, environment: Constant.Environment) -> String { "match \(type.method) \(environment.bundleId)" } diff --git a/fastlane/Helpers/Test.swift b/fastlane/Helpers/Test.swift index bb4188d8..3ce377fa 100644 --- a/fastlane/Helpers/Test.swift +++ b/fastlane/Helpers/Test.swift @@ -10,13 +10,13 @@ enum Test { static func buildAndTest( environment: Constant.Environment, - targets: [String], + onlyTesting: [String] = [], devices: [String] ) { scan( scheme: .userDefined(environment.scheme), devices: .userDefined(devices), - onlyTesting: targets, + onlyTesting: onlyTesting, codeCoverage: .userDefined(true), outputDirectory: Constant.testOutputDirectoryPath, xcodebuildFormatter: "Pods/xcbeautify/xcbeautify", diff --git a/{PROJECT_NAME}/Configurations/Plists/Info.plist b/{PROJECT_NAME}/Configurations/Plists/Info.plist index b18749da..8913a934 100644 --- a/{PROJECT_NAME}/Configurations/Plists/Info.plist +++ b/{PROJECT_NAME}/Configurations/Plists/Info.plist @@ -11,7 +11,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - $(PRODUCT_NAME) + $(APP_DISPLAY_NAME) CFBundlePackageType APPL CFBundleShortVersionString diff --git a/{PROJECT_NAME}/Configurations/XCConfigs/DebugProduction.xcconfig b/{PROJECT_NAME}/Configurations/XCConfigs/DebugProduction.xcconfig index 80622ed9..1c6b8f7e 100644 --- a/{PROJECT_NAME}/Configurations/XCConfigs/DebugProduction.xcconfig +++ b/{PROJECT_NAME}/Configurations/XCConfigs/DebugProduction.xcconfig @@ -7,7 +7,7 @@ ONLY_ACTIVE_ARCH = YES SWIFT_OPTIMIZATION_LEVEL = -Onone GCC_PREPROCESSOR_DEFINITIONS = $(inherited) DEBUG=1 PRODUCTION=1 -PRODUCT_NAME = $(TARGET_NAME) +APP_DISPLAY_NAME = $(TARGET_NAME) PRODUCT_BUNDLE_IDENTIFIER = {BUNDLE_ID_PRODUCTION} SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG PRODUCTION OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -warn-long-expression-type-checking=300 -Xfrontend -warn-long-function-bodies=300 diff --git a/{PROJECT_NAME}/Configurations/XCConfigs/DebugStaging.xcconfig b/{PROJECT_NAME}/Configurations/XCConfigs/DebugStaging.xcconfig index ee4be644..4136f867 100644 --- a/{PROJECT_NAME}/Configurations/XCConfigs/DebugStaging.xcconfig +++ b/{PROJECT_NAME}/Configurations/XCConfigs/DebugStaging.xcconfig @@ -7,7 +7,7 @@ ONLY_ACTIVE_ARCH = YES SWIFT_OPTIMIZATION_LEVEL = -Onone GCC_PREPROCESSOR_DEFINITIONS = $(inherited) DEBUG=1 STAGING=1 -PRODUCT_NAME = $(TARGET_NAME) Staging +APP_DISPLAY_NAME = $(TARGET_NAME) Staging PRODUCT_BUNDLE_IDENTIFIER = {BUNDLE_ID_STAGING} SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG STAGING OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -warn-long-expression-type-checking=300 -Xfrontend -warn-long-function-bodies=300 diff --git a/{PROJECT_NAME}/Configurations/XCConfigs/ReleaseProduction.xcconfig b/{PROJECT_NAME}/Configurations/XCConfigs/ReleaseProduction.xcconfig index 227a6cc4..b2858736 100644 --- a/{PROJECT_NAME}/Configurations/XCConfigs/ReleaseProduction.xcconfig +++ b/{PROJECT_NAME}/Configurations/XCConfigs/ReleaseProduction.xcconfig @@ -6,6 +6,6 @@ SWIFT_OPTIMIZATION_LEVEL = -O ENABLE_BITCODE = NO GCC_PREPROCESSOR_DEFINITIONS = $(inherited) PRODUCTION=1 -PRODUCT_NAME = $(TARGET_NAME) +APP_DISPLAY_NAME = $(TARGET_NAME) PRODUCT_BUNDLE_IDENTIFIER = {BUNDLE_ID_PRODUCTION} SWIFT_ACTIVE_COMPILATION_CONDITIONS = PRODUCTION RELEASE diff --git a/{PROJECT_NAME}/Configurations/XCConfigs/ReleaseStaging.xcconfig b/{PROJECT_NAME}/Configurations/XCConfigs/ReleaseStaging.xcconfig index 27daeaea..f857f34b 100644 --- a/{PROJECT_NAME}/Configurations/XCConfigs/ReleaseStaging.xcconfig +++ b/{PROJECT_NAME}/Configurations/XCConfigs/ReleaseStaging.xcconfig @@ -7,6 +7,6 @@ ENABLE_BITCODE = NO ENABLE_BITCODE = NO GCC_PREPROCESSOR_DEFINITIONS = $(inherited) STAGING=1 -PRODUCT_NAME = $(TARGET_NAME) Staging +APP_DISPLAY_NAME = $(TARGET_NAME) Staging PRODUCT_BUNDLE_IDENTIFIER = {BUNDLE_ID_STAGING} SWIFT_ACTIVE_COMPILATION_CONDITIONS = STAGING RELEASE diff --git a/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIF+Swift.swift b/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIF+Swift.swift index 66696b28..f5b3b712 100644 --- a/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIF+Swift.swift +++ b/{PROJECT_NAME}KIFUITests/Sources/Utilities/KIF+Swift.swift @@ -8,10 +8,10 @@ import KIF extension KIFSpec { static func tester(file: String = #file, _ line: Int = #line) -> KIFUITestActor { - return KIFUITestActor(inFile: file, atLine: line, delegate: kifDelegate) + KIFUITestActor(inFile: file, atLine: line, delegate: kifDelegate) } static func system(file: String = #file, _ line: Int = #line) -> KIFSystemTestActor { - return KIFSystemTestActor(inFile: file, atLine: line, delegate: kifDelegate) + KIFSystemTestActor(inFile: file, atLine: line, delegate: kifDelegate) } }