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)
}
}