diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 00000000..f817931f --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,37 @@ +## flutter-ci.yml + +This YAML file contains the configuration for the continuous integration (CI) workflow for Flutter projects. It defines the steps and actions to be executed whenever changes are pushed to the repository. The CI workflow ensures that the code is built, tested, and validated before merging it into the main branch. + +## flutter-production-cd.yml + +This YAML file contains the configuration for the continuous deployment (CD) workflow for Flutter projects in a production environment. It defines the steps and actions to be executed when changes are merged into the main branch. The CD workflow automates the process of deploying the Flutter application to the production environment, ensuring a smooth and efficient release process. + +### Secrets required: + +GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT +FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_BASE_64 +IOS_DIST_CERTIFICATE_BASE_64 +DIST_CERTIFICATE_PASSWORD +APPSTORE_CONNECT_API_KEY_ID +APPSTORE_CONNECT_API_KEY_ISSUER_ID +APPSTORE_CONNECT_API_KEY_BASE_64 + +## flutter-staging-cd.yml + +This YAML file contains the configuration for the continuous deployment (CD) workflow for Flutter projects in a staging environment. It defines the steps and actions to be executed when changes are merged into the staging branch. The CD workflow automates the process of deploying the Flutter application to the staging environment, allowing for testing and validation before releasing to production. + +### Secrets required: + +GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT +FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_BASE_64 +IOS_DIST_CERTIFICATE_BASE_64_CONTENT +FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_BASE_64 +APPSTORE_CONNECT_API_KEY_ID +APPSTORE_CONNECT_API_KEY_ISSUER_ID +APPSTORE_CONNECT_API_KEY_BASE_64_CONTENT +IOS_DIST_CERTIFICATE_PASSWORD +FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_BASE_64 + +## pr-title-checker.yml + +This YAML file contains the configuration for a workflow that checks the title of pull requests (PRs) in a Flutter project. It defines the steps and actions to be executed whenever a new PR is created. The workflow ensures that the PR title follows a specific format or meets certain criteria, helping maintain consistency and clarity in the project's PRs. diff --git a/.github/workflows/actions/android-setup/action.yml b/.github/workflows/actions/android-setup/action.yml new file mode 100644 index 00000000..2caf20a3 --- /dev/null +++ b/.github/workflows/actions/android-setup/action.yml @@ -0,0 +1,34 @@ +# IMPORTANT: ANDROID_KEYSTORE_NAME has to be set on github variables + +name: Release Android +description: "Setup Android for release" +inputs: + key-properties-file: + description: "Key properties file" + required: true + android-app-folder: + description: "Android app folder" + required: true + + +runs: + using: "composite" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 15 + + - name: Setup + uses: ./.github/workflows/actions/flutter_setup + with: + setup-android: true + + - name: Download Android Keystore + shell: bash + env: + KEYSTORE_BASE_64: ${{ secrets.ANDROID_KEYSTORE_BASE_64_CONTENT }} + KEY_PROPERTIES_BASE_64: ${{ secrets.ANDROID_KEY_PROPERTIES_BASE_64 }} + run: | + echo -n $KEYSTORE_BASE_64 | base64 -d > ${{ vars.ANDROID_KEYSTORE_NAME }} + echo -n $KEY_PROPERTIES_BASE_64 | base64 -d > ${{ inputs.key-properties-file }} + working-directory: ${{ inputs.android-app-folder }} diff --git a/.github/workflows/actions/deploy-android-firebase/action.yml b/.github/workflows/actions/deploy-android-firebase/action.yml new file mode 100644 index 00000000..5afb3696 --- /dev/null +++ b/.github/workflows/actions/deploy-android-firebase/action.yml @@ -0,0 +1,57 @@ +name: Deploy Android Firebase +description: "Deploy Android to Firebase" +inputs: + key-properties-file: + description: "Key properties file" + required: true + firebase-base64-service-account-credentials: + description: "Firebase service account credentials base64 encoded" + required: true + service-account: + description: "Service account" + required: true + android-app-folder: + description: "Android app folder" + required: true + app-env: + description: "App environment" + required: true + build-number: + description: "Build number" + required: true + firebase-app-id: + description: "Firebase app ID" + required: true + firebase-tester-groups: + description: "Firebase tester groups" + required: true + + +runs: + using: "composite" + steps: + - name: Setup android + uses: ./.github/workflows/actions/setup-android + with: + "key-properties-file": ${{ inputs.key-properties-file }} + "android-app-folder": ${{ inputs.android-app-folder }} + + - name: Download Firebase Service Account + shell: bash + env: + SERVICE_ACCOUNT_CONTENT: ${{ inputs.firebase-base64-service-account-credentials }} + run: | + echo -n $SERVICE_ACCOUNT_CONTENT | base64 -d > ${{ inputs.service-account }} + working-directory: ${{ inputs.android-app-folder }} + + - name: Deploy Android Staging to Firebase App Distribution + env: + APP_ENV: ${{ inputs.app-env }} + BUILD_NUMBER: ${{ inputs.build-number }} + FIREBASE_SERVICE_ACCOUNT_FILE: ${{ inputs.service-account }} + FIREBASE_APP_ID: ${{ inputs.firebase-app-id }} + FIREBASE_TESTER_GROUPS: ${{ inputs.firebase-tester-groups }} + uses: maierj/fastlane-action@v3.0.0 + with: + lane: "deploy_firebase_app_distribution" + subdirectory: ${{ inputs.android-app-folder }} diff --git a/.github/workflows/actions/deploy-android-google-play/action.yml b/.github/workflows/actions/deploy-android-google-play/action.yml new file mode 100644 index 00000000..c500a413 --- /dev/null +++ b/.github/workflows/actions/deploy-android-google-play/action.yml @@ -0,0 +1,38 @@ +name: Deploy Android Google Play +description: "Deploy Android to Google Play" +inputs: + key-properties-file: + description: "Key properties file" + required: true + android-app-folder: + description: "Android app folder" + required: true + app-env: + description: "App environment" + required: true + build-number: + description: "Build number" + required: true + google-play-service-account-credentials-content: + description: "Google Play service account credentials content" + required: true + + +runs: + using: "composite" + steps: + - name: Setup android + uses: ./.github/workflows/actions/setup-android + with: + "key-properties-file": ${{ inputs.key-properties-file }} + "android-app-folder": ${{ inputs.android-app-folder }} + + - name: Release to GooglePlay + env: + GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT: ${{ inputs.google-play-service-account-credentials-content }} + BUILD_NUMBER: ${{ inputs.build-number }} + APP_ENV: ${{ inputs.app-env }} + uses: maierj/fastlane-action@v3.0.0 + with: + lane: "publish_prod_google_play" + subdirectory: ${{ inputs.android-app-folder }} diff --git a/.github/workflows/actions/deploy-ios-firebase/action.yml b/.github/workflows/actions/deploy-ios-firebase/action.yml new file mode 100644 index 00000000..0a16bf0a --- /dev/null +++ b/.github/workflows/actions/deploy-ios-firebase/action.yml @@ -0,0 +1,78 @@ +# IMPORTANT: IOS_CERTIFICATE_NAME has to be set on github variables + +name: Deploy iOS Firebase +description: "Deploy iOS to Firebase" +inputs: + dist-certificate-base-64-content: + description: "Distribution certificate base64 encoded" + required: true + ios-app-folder: + description: "iOS app folder" + required: true + app-env: + description: "App environment" + required: true + build-number: + description: "Build number" + required: true + firebase-app-id: + description: "Firebase app ID" + required: true + firebase-tester-groups: + description: "Firebase tester groups" + required: true + service-account: + description: "Service account" + required: true + firebase-service-account-base-64-content: + description: "Firebase service account base64 encoded" + required: true + dist-cert-password: + description: "Distribution certificate password" + required: true + appstore-connect-api-key-id: + description: "Appstore connect API key ID" + required: true + appstore-connect-api-key-issuer-id: + description: "Appstore connect API key issuer ID" + required: true + appstore-connect-api-key-base-64-content: + description: "Appstore connect API key base64 encoded" + required: true + + + +runs: + using: "composite" + steps: + - name: Setup iOS + uses: ./.github/workflows/actions/setup-ios + with: + "dist-certificate-base-64-content": ${{ inputs.dist-certificate-base-64-content }} + "ios-app-folder": ${{ inputs.ios-app-folder }} + + - name: Download Firebase Service Account + shell: bash + env: + SERVICE_ACCOUNT_CONTENT: ${{ inputs.firebase-service-account-base-64-content }} + run: | + echo -n $SERVICE_ACCOUNT_CONTENT | base64 -d > ${{ inputs.service-account }} + working-directory: ${{ inputs.ios-app-folder }} + + - name: Release to Firebase + env: + APP_ENV: ${{ inputs.app-env }} + FIREBASE_SERVICE_ACCOUNT_FILE: ${{ inputs.service-account }} + FIREBASE_APP_ID: ${{ inputs.firebase-app-id }} + DIST_CERTIFICATE_PATH: ${{ github.workspace }}/ios/${{ vars.IOS_CERTIFICATE_NAME }} + DIST_CERTIFICATE_PASSWORD: ${{ inputs.dist-cert-password }} + APPSTORE_CONNECT_API_KEY_ID: ${{ inputs.appstore-connect-api-key-id }} + APPSTORE_CONNECT_API_KEY_ISSUER_ID: ${{ inputs.appstore-connect-api-key-issuer-id }} + APPSTORE_CONNECT_API_KEY_BASE_64_CONTENT: ${{ inputs.appstore-connect-api-key-base-64-content }} + BUILD_NUMBER: ${{ inputs.build-number }} + FIREBASE_TESTER_GROUPS: ${{ inputs.firebase-tester-groups }} + + uses: maierj/fastlane-action@v3.0.0 + with: + lane: "deploy_firebase_app_distribution" + subdirectory: ${{ inputs.ios-app-folder}} diff --git a/.github/workflows/actions/deploy-ios-testflight/action.yml b/.github/workflows/actions/deploy-ios-testflight/action.yml new file mode 100644 index 00000000..a09148e1 --- /dev/null +++ b/.github/workflows/actions/deploy-ios-testflight/action.yml @@ -0,0 +1,55 @@ +# IMPORTANT: IOS_CERTIFICATE_NAME has to be set on github variables + +name: Deploy iOS TestFlight +description: "Deploy iOS to TestFlight" +inputs: + dist-certificate-base-64-content: + description: "Distribution certificate base64 encoded" + required: true + ios-app-folder: + description: "iOS app folder" + required: true + build-number: + description: "Build number" + required: true + firebase-app-id: + description: "Firebase app ID" + required: true + dist-cert-password: + description: "Distribution certificate password" + required: true + appstore-connect-api-key-id: + description: "Appstore connect API key ID" + required: true + appstore-connect-api-key-issuer-id: + description: "Appstore connect API key issuer ID" + required: true + appstore-connect-api-key-base-64-content: + description: "Appstore connect API key base64 encoded" + required: true + + + +runs: + using: "composite" + steps: + - name: Setup iOS + uses: ./.github/workflows/actions/setup-ios + with: + "dist-certificate-base-64-content": ${{ inputs.dist-certificate-base-64-content }} + "ios-app-folder": ${{ inputs.ios-app-folder }} + + - name: Release to testflight + env: + APPSTORE_CONNECT_API_KEY_ID: ${{ inputs.appstore-connect-api-key-id }} + APPSTORE_CONNECT_API_KEY_ISSUER_ID: ${{ inputs.appstore-connect-api-key-issuer-id }} + APPSTORE_CONNECT_API_KEY_BASE_64_CONTENT: ${{ inputs.appstore-connect-api-key-base-64-content }} + DIST_CERTIFICATE_PATH: ${{ github.workspace }}/ios/${{ vars.IOS_CERTIFICATE_NAME }} + DIST_CERTIFICATE_PASSWORD: ${{ inputs.dist-cert-password }} + FIREBASE_APP_ID: ${{ inputs.firebase-app-id }} + BUILD_NUMBER: ${{ inputs.build-number }} + + uses: maierj/fastlane-action@v3.0.0 + with: + lane: "publish_prod_testflight" + subdirectory: ${{ inputs.ios-app-folder }} diff --git a/.github/workflows/actions/flutter-setup/action.yml b/.github/workflows/actions/flutter-setup/action.yml new file mode 100644 index 00000000..92ed82b7 --- /dev/null +++ b/.github/workflows/actions/flutter-setup/action.yml @@ -0,0 +1,52 @@ +# IMPORTANT: FVM_VERSION, ANDROID_APP_FOLDER and IOS_APP_FOLDER have to be set on github variables + +name: Setup flutter +description: "Setup infra" +inputs: + setup-android: + description: "Setup Android" + required: true + setup-ios: + description: "Setup iOS" + required: true + +runs: + using: "composite" + steps: + - uses: actions/setup-java@v3 + with: + distribution: "zulu" + java-version: "17" + - uses: dart-lang/setup-dart@v1.4 + - name: Cache FVM & Flutter + uses: actions/cache@v3 + with: + path: /Users/runner/fvm/ + key: ${{ runner.os }}-fvm-${{ hashFiles('**/fvm_config.json') }} + restore-keys: | + ${{ runner.os }}-fvm- + - name: Cache pub cache + uses: actions/cache@v3 + with: + path: /Users/runner/.pub-cache/ + key: ${{ runner.os }}-pub-cache + - name: Install FVM && Flutter + shell: bash + run: dart pub global activate fvm ${{ vars.FVM_VERSION }} && fvm install --verbose && fvm use --force --verbose + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + - uses: ruby/setup-ruby@v1 + if: ${{ inputs.setup-android }} + with: + bundler-cache: true + working-directory: ${{ vars.ANDROID_APP_FOLDER }} + - uses: ruby/setup-ruby@v1 + if: ${{ inputs.setup-ios }} + with: + bundler-cache: true + working-directory: ${{ vars.IOS_APP_FOLDER }} + - name: Get dependencies + uses: maierj/fastlane-action@v3.0.0 + with: + lane: "fetch_dependencies" diff --git a/.github/workflows/actions/ios-setup/action.yml b/.github/workflows/actions/ios-setup/action.yml new file mode 100644 index 00000000..87b71ad7 --- /dev/null +++ b/.github/workflows/actions/ios-setup/action.yml @@ -0,0 +1,32 @@ +# IMPORTANT: IOS_CERTIFICATE_NAME and XCODE_VERSION have to be set on github variables + +name: Release iOS +description: "Setup iOS for release" +inputs: + dist-certificate-base-64-content: + description: "Distribution certificate base64 encoded" + required: true + ios-app-folder: + description: "iOS app folder" + required: true + +runs: + using: "composite" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 15 + + - name: Select Xcode version + run: sudo xcode-select -s /Applications/Xcode_${{ vars.XCODE_VERSION }}.app/Contents/Developer + + - name: Setup + uses: ./.github/workflows/actions/flutter_setup + with: + setup-ios: true + + - name: Create distribution certificate + shell: bash + run: | + echo -n ${{ inputs.dist-certificate-base-64-content }} | base64 --decode -o ${{ vars.IOS_CERTIFICATE_NAME }} + working-directory: ${{ inputs.ios-app-folder}} diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index cca6b633..f6f07441 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -54,6 +54,8 @@ jobs: with: lane: 'tests' - name: 'Build android app' + env: + APP_ENV: 'dev' uses: maierj/fastlane-action@v3.0.0 with: lane: 'android build_dev_debug_apk' diff --git a/.github/workflows/flutter-production-cd.yml b/.github/workflows/flutter-production-cd.yml new file mode 100644 index 00000000..f6adeff2 --- /dev/null +++ b/.github/workflows/flutter-production-cd.yml @@ -0,0 +1,93 @@ +name: Flutter Production CD + +on: + pull_request: + branches: + - main + types: + - closed ## Trigger the workflow only when the PR is merged + +env: + FIREBASE_ANDROID_APP_ID: "" ## Add your Firebase Android App ID here + +jobs: + calculate_build_number: + runs-on: ubuntu-latest + timeout-minutes: 30 + outputs: + build_number: ${{ steps.build_number.outputs.build_number }} + env: + GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT }} + SERVICE_ACCOUNT: "${{ github.workspace }}/service_account.json" + steps: + - uses: actions/checkout@v4 + - name: Install ruby dependencies + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Download Firebase Service Account + shell: bash + env: + SERVICE_ACCOUNT_CONTENT: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_BASE_64 }} + run: | + echo -n $SERVICE_ACCOUNT_CONTENT | base64 -d > $SERVICE_ACCOUNT + + - name: Calculate next build number + id: build_number + env: + SERVICE_ACCOUNT: ${{ env.SERVICE_ACCOUNT }} + FIREBASE_APP_ID: ${{ env.FIREBASE_ANDROID_APP_ID }} + run: | + set -e + command_result=$(bundle exec fastlane get_next_build_number) + echo -n -e "Result: $command_result" + build_number=$(echo -e -n $command_result | grep -o 'Next Build Number: [0-9]\+' | awk '{print $NF}') + if [[ $build_number =~ ^[0-9]+$ ]]; then + echo "Incremented build number: $build_number" + echo "build_number=$build_number" >> $GITHUB_OUTPUT + else + echo "Error: $build_number is not a valid number" + exit 1 + fi + + build_and_release_testflight: + needs: [calculate_build_number] + if: github.event.pull_request.merged == true + timeout-minutes: 30 + env: + IOS_APP_FOLDER: "ios" + FIREBASE_APP_ID_PRODUCTION: "" ## Add your Firebase iOS App ID here + + runs-on: macos-13 + steps: + - name: Deploy iOS to TestFlight + uses: ./.github/workflows/actions/deploy-ios-testflight + with: + "dist-certificate-base-64-content": ${{ secrets.IOS_DIST_CERTIFICATE_BASE_64 }} + "ios-app-folder": ${{ env.IOS_APP_FOLDER }} + "firebase-app-id": ${{ env.FIREBASE_APP_ID_PRODUCTION }} + "build-number": ${{ needs.calculate_build_number.outputs.build_number }} + "dist-cert-password": ${{ secrets.DIST_CERTIFICATE_PASSWORD }} + "appstore-connect-api-key-id": ${{ secrets.APPSTORE_CONNECT_API_KEY_ID }} + "appstore-connect-api-key-issuer-id": ${{ secrets.APPSTORE_CONNECT_API_KEY_ISSUER_ID }} + "appstore-connect-api-key-base-64-content": ${{ secrets.APPSTORE_CONNECT_API_KEY_BASE_64 }} + + build_and_release_google_play: + needs: [calculate_build_number] + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + ANDROID_APP_FOLDER: "android" + PROD_KEY_PROPERTIES_FILE: "key.properties" + APP_ENV: 'prod' + steps: + - name: Deploy Android to Google Play + uses: ./.github/workflows/actions/deploy-android-google-play + with: + "key-properties-file": ${{ env.PROD_KEY_PROPERTIES_FILE }} + "android-app-folder": ${{ env.ANDROID_APP_FOLDER }} + "google-play-service-account-credentials": ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT }} + "app-env": ${{ env.APP_ENV }} + "build-number": ${{ needs.calculate_build_number.outputs.build_number }} diff --git a/.github/workflows/flutter-staging-cd.yml b/.github/workflows/flutter-staging-cd.yml new file mode 100644 index 00000000..5ab4c3d1 --- /dev/null +++ b/.github/workflows/flutter-staging-cd.yml @@ -0,0 +1,104 @@ +name: Flutter Staging CD + +on: + pull_request: + branches: + - staging + types: + - closed ## Trigger the workflow only when the PR is merged + +env: + FIREBASE_ANDROID_APP_ID: "" ## Add your Firebase Android App ID here +jobs: + calculate_build_number: + runs-on: ubuntu-latest + outputs: + build_number: ${{ steps.build_number.outputs.build_number }} + env: + GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT }} + SERVICE_ACCOUNT: "${{ github.workspace }}/service_account.json" + steps: + - uses: actions/checkout@v4 + - name: Install ruby dependencies + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Download Firebase Service Account + shell: bash + env: + SERVICE_ACCOUNT_CONTENT: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_BASE_64 }} + run: | + echo -n $SERVICE_ACCOUNT_CONTENT | base64 -d > $SERVICE_ACCOUNT + - name: Calculate next build number + id: build_number + env: + SERVICE_ACCOUNT: ${{ env.SERVICE_ACCOUNT }} + FIREBASE_APP_ID: ${{ env.FIREBASE_ANDROID_APP_ID }} + run: | + set -e + command_result=$(bundle exec fastlane get_next_build_number) + build_number=$(echo -e -n $command_result | grep -o 'Next Build Number: [0-9]\+' | awk '{print $NF}') + echo -n -e "Result: $command_result" + if [[ $build_number =~ ^[0-9]+$ ]]; then + echo "Incremented build number: $build_number" + echo "build_number=$build_number" >> $GITHUB_OUTPUT + else + echo "Error: $build_number is not a valid number" + exit 1 + fi + + build_and_release_ios_staging_firebase: + needs: [calculate_build_number] + if: github.event.pull_request.merged == true + timeout-minutes: 20 + env: + IOS_APP_FOLDER: "ios" + SERVICE_ACCOUNT: "${{ github.workspace }}/ios/service_account.json" + FIREBASE_APP_ID_STAGING: "" ## Add your Firebase iOS App ID here + FIREBASE_TESTER_GROUPS: "" ## Add your Firebase tester groups here + APP_ENV: "staging" + + runs-on: macos-13 + steps: + - name: Deploy iOS to Firebase + uses: ./.github/workflows/actions/deploy-ios-firebase + with: + "dist-certificate-base-64-content": ${{ secrets.IOS_DIST_CERTIFICATE_BASE_64_CONTENT }} + "ios-app-folder": ${{ env.IOS_APP_FOLDER }} + "firebase-service-account-base-64-content": ${{ secrets.FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_BASE_64 }} + "appstore-connect-api-key-id": ${{ secrets.APPSTORE_CONNECT_API_KEY_ID }} + "appstore-connect-api-key-issuer-id": ${{ secrets.APPSTORE_CONNECT_API_KEY_ISSUER_ID }} + "appstore-connect-api-key-base-64-content": ${{ secrets.APPSTORE_CONNECT_API_KEY_BASE_64_CONTENT }} + "app-env": ${{ env.APP_ENV }} + "build-number": ${{ needs.calculate_build_number.outputs.build_number }} + "firebase-app-id": ${{ env.FIREBASE_APP_ID_STAGING }} + "firebase-tester-groups": ${{ env.FIREBASE_TESTER_GROUPS }} + "dist-cert-password": ${{ secrets.IOS_DIST_CERTIFICATE_PASSWORD }} + "service-account": ${{ env.SERVICE_ACCOUNT }} + + build_and_release_android_staging_firebase: + needs: [calculate_build_number] + if: github.event.pull_request.merged == true + timeout-minutes: 20 + runs-on: ubuntu-latest + env: + ANDROID_APP_FOLDER: "android" + SERVICE_ACCOUNT: "${{ github.workspace }}/android/service_account.json" + PROD_KEY_PROPERTIES_FILE: "key.properties" + FIREBASE_APP_ID_STAGING: "" ## Add your Firebase Android App ID here + FIREBASE_TESTER_GROUPS: "" ## Add your Firebase tester groups here + APP_ENV: "staging" + + steps: + - name: Deploy Andorid to Firebase + uses: ./.github/workflows/actions/deploy-android-firebase + with: + "key-properties-file": ${{ inputs.key-properties-file }} + "firebase-base64-service-account-credentials": ${{ secrets.FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_BASE_64 }} + "android-app-folder": ${{ env.ANDROID_APP_FOLDER }} + "app-env": ${{ env.APP_ENV }} + "build-number": ${{ needs.calculate_build_number.outputs.build_number }} + "firebase-app-id": ${{ env.FIREBASE_APP_ID_STAGING }} + "firebase-tester-groups": ${{ env.FIREBASE_TESTER_GROUPS }} + "service-account": ${{ env.SERVICE_ACCOUNT }} diff --git a/Gemfile.lock b/Gemfile.lock index 680a80d6..52d8ef07 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -276,6 +276,7 @@ GEM PLATFORMS arm64-darwin-22 arm64-darwin-21 + arm64-darwin-22 x86_64-darwin-19 x86_64-darwin-20 x86_64-linux diff --git a/android/fastlane/Fastfile b/android/fastlane/Fastfile index 19c011da..68cd781b 100644 --- a/android/fastlane/Fastfile +++ b/android/fastlane/Fastfile @@ -18,7 +18,6 @@ platform :android do build_flutter( build_platform: 'appbundle', build_type: 'release', - flavor: options[:env], ) end @@ -27,73 +26,55 @@ platform :android do build_flutter( build_platform: 'apk', build_type: 'debug', + env: ENV['APP_ENV'], ) end desc "**Assemble prod apk release version**" lane :build_prod do - build_apk(env: "prod") + build_apk(env: 'prod') end desc "**Assemble dev apk release version**" lane :build_dev do - build_apk(env: "dev") + build_apk(env: 'staging') + integration_test end desc "**Submit a new Beta Build to Firebase app distribution**" desc "#### Envs\n" + - " * **`firebase_app_id`**: Firebase app id.\n" + + " * **`FIREBASE_APP_ID`**: Firebase app id.\n" + " * **`FIREBASE_SERVICE_ACCOUNT_FILE`**: The path to your Google service account json file.\n" + " * **`FIREBASE_TESTERS`**: The email addresses of the testers you want to invite.\n" + " You can specify the testers as a comma-separated list of email addresses: \"ali@example.com, bri@example.com, cal@example.com\"\n" " * **`FIREBASE_TESTER_GROUPS`**: The tester groups you want to invite." + " You can specify the groups as a comma-separated list: \"qa-team, trusted-testers\"\n" - lane :deploy_firebase_app_distribution do |options| - env = options[:env] - firebase_app_id = options[:firebase_app_id] + lane :deploy_firebase_app_distribution do |options| + env = ENV['APP_ENV'] clean - build_apk(env: env, include_git_version_suffix: true, build_type: 'release') + build_apk(env: env, build_type: 'release', build_number: ENV['BUILD_NUMBER']) firebase_app_distribution( - app: firebase_app_id, + app: ENV['FIREBASE_APP_ID'], service_credentials_file: ENV["FIREBASE_SERVICE_ACCOUNT_FILE"], - testers: ENV["FIREBASE_TESTERS"], groups: ENV["FIREBASE_TESTER_GROUPS"], release_notes: generate_snapshot_changelog, apk_path: "../build/app/outputs/flutter-apk/app-#{env}-release.apk", ) end - desc "**Deploy a new dev version to the Firebase App Distribution**" - desc "#### Envs\n" + - " * **`FIREBASE_APP_ID_DEV`**: Dev Firebase app id.\n" + - " * **`FIREBASE_SERVICE_ACCOUNT_FILE`**: The path to your Google service account json file.\n" + - " * **`FIREBASE_TESTERS`**: The email addresses of the testers you want to invite.\n" + - " You can specify the testers as a comma-separated list of email addresses: \"ali@example.com, bri@example.com, cal@example.com\"\n" - " * **`FIREBASE_TESTER_GROUPS`**: The tester groups you want to invite." + - " You can specify the groups as a comma-separated list: \"qa-team, trusted-testers\"\n" - lane :deploy_firebase_dev do - deploy_firebase_app_distribution(env: 'dev', firebase_app_id: ENV["FIREBASE_APP_ID_DEV"]) - end - - desc "**Deploy a new prod version to the Firebase App Distribution**" - desc "#### Envs\n" + - " * **`FIREBASE_APP_ID_PROD`**: Production Firebase app id.\n" - " * **`FIREBASE_SERVICE_ACCOUNT_FILE`**: The path to your Google service account json file.\n" + - " * **`FIREBASE_TESTERS`**: The email addresses of the testers you want to invite." + - " You can specify the testers as a comma-separated list of email addresses: \"ali@example.com, bri@example.com, cal@example.com\"\n" - " * **`FIREBASE_TESTER_GROUPS`**: The tester groups you want to invite." + - " You can specify the groups as a comma-separated list: \"qa-team, trusted-testers\"\n" - lane :deploy_firebase_prod do - deploy_firebase_app_distribution(env: 'prod', firebase_app_id: ENV["FIREBASE_APP_ID_PROD"]) - end - desc "**Deploy a new version to the Google Play**" desc "#### Envs\n" + " * **`GOOGLE_PLAY_TRACK`**: Sets the release track. The default value is `internal`\n" + " * **`GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_FILE`**: Sets the service account file.\n" + " * **`GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT`**: Sets the service account content file." - lane :deploy_prod do - build_appbundle(env: 'prod') + lane :publish_prod_google_play do + build_flutter( + build_platform: 'appbundle', + build_type: 'release', + build_number: ENV.fetch('BUILD_NUMBER') { raise "BUILD_NUMBER is not set" }, + env: ENV['APP_ENV'], + ) + upload_to_play_store( track: ENV['GOOGLE_PLAY_TRACK'] || 'internal', json_key: ENV['GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_FILE'], diff --git a/android/fastlane/README.md b/android/fastlane/README.md index ceddd3f2..be00acd4 100644 --- a/android/fastlane/README.md +++ b/android/fastlane/README.md @@ -13,6 +13,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do # Available Actions +### generate_docs + +```sh +[bundle exec] fastlane generate_docs +``` + + + ### fetch_dependencies ```sh @@ -101,6 +109,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do +### get_next_build_number + +```sh +[bundle exec] fastlane get_next_build_number +``` + +**Get next Build Number** + ---- @@ -161,43 +177,16 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do **Submit a new Beta Build to Firebase app distribution** #### Envs - * **`firebase_app_id`**: Firebase app id. - * **`FIREBASE_SERVICE_ACCOUNT_FILE`**: The path to your Google service account json file. - * **`FIREBASE_TESTERS`**: The email addresses of the testers you want to invite. - You can specify the testers as a comma-separated list of email addresses: "ali@example.com, bri@example.com, cal@example.com" - - -### android deploy_firebase_dev - -```sh -[bundle exec] fastlane android deploy_firebase_dev -``` - -**Deploy a new dev version to the Firebase App Distribution** - -#### Envs - * **`FIREBASE_APP_ID_DEV`**: Dev Firebase app id. + * **`FIREBASE_APP_ID`**: Firebase app id. * **`FIREBASE_SERVICE_ACCOUNT_FILE`**: The path to your Google service account json file. * **`FIREBASE_TESTERS`**: The email addresses of the testers you want to invite. You can specify the testers as a comma-separated list of email addresses: "ali@example.com, bri@example.com, cal@example.com" -### android deploy_firebase_prod - -```sh -[bundle exec] fastlane android deploy_firebase_prod -``` - -**Deploy a new prod version to the Firebase App Distribution** - -#### Envs - * **`FIREBASE_APP_ID_PROD`**: Production Firebase app id. - - -### android deploy_prod +### android publish_prod_google_play ```sh -[bundle exec] fastlane android deploy_prod +[bundle exec] fastlane android publish_prod_google_play ``` **Deploy a new version to the Google Play** diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f59fae8c..827f34c2 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -23,6 +23,12 @@ private_lane :sh_on_root do |options| sh("cd #{full_path} && #{command}") end +lane :generate_docs do + sh("bundle exec fastlane actions docs") + sh("cd ../android && bundle exec fastlane actions docs") + sh("cd ../ios && bundle exec fastlane actions docs") +end + private_lane :flutter_command do |options| command = options[:command] subdirectory = options[:subdirectory] @@ -85,7 +91,6 @@ lane :lint_code_metrics do flutter_command(command: "pub run dart_code_linter:metrics check-unused-code lib --fatal-unused") end - desc "**Run linters**" lane :lints do lint_format @@ -124,9 +129,10 @@ lane :build_flutter do |options| build_platform = options[:build_platform] config_only = options[:config_only] || false - flavor = options[:flavor] || 'dev' config_param = config_only ? '--config-only' : '' - + + flavor = options[:env] || '' + version_name = calculate_version_name( include_git_version_suffix: options[:include_git_version_suffix] || false ) @@ -147,9 +153,9 @@ end private_lane :generate_snapshot_changelog do changelog_from_git_commits( - between: ["HEAD^", "HEAD"], - pretty: "%s%n%nAuthor: %an <%ae>", - date_format: "short", + between: ["HEAD^", "HEAD"], + pretty: "%s%n%nAuthor: %an <%ae>", + date_format: "short", ) header = Actions.lane_context[SharedValues::FL_CHANGELOG] changelog_from_git_commits( @@ -163,3 +169,35 @@ private_lane :generate_snapshot_changelog do ENV["CHANGELOG"] = "#{header}\n\nLast changes:\n#{body}" ENV["CHANGELOG"] end + +desc "**Get next Production Build Number**" +private_lane :get_next_production_build_number do + version = ['prod', 'beta', 'alpha', 'internal'].map { |track| + google_play_track_version_codes( + track: track, + json_key: ENV['GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_FILE'], + json_key_data: ENV['GOOGLE_PLAY_SERVICE_ACCOUNT_CREDENTIALS_CONTENT'] + ) + }.flatten.map(&:to_i).max + 1 + version +end + +desc "**Get next Staging Build Number**" +private_lane :get_next_staging_build_number do + latest_release = firebase_app_distribution_get_latest_release( + app: ENV['FIREBASE_APP_ID'], + service_credentials_file: ENV['SERVICE_ACCOUNT'] + ) + version = latest_release[:buildVersion].to_i + 1 + version +end + +desc "**Get next Build Number**" +lane :get_next_build_number do + next_staging_build_number = get_next_staging_build_number + next_production_build_number = get_next_production_build_number + + version = [next_staging_build_number, next_production_build_number].max + UI.message "Next Build Number: #{version}" + version +end diff --git a/fastlane/README.md b/fastlane/README.md index 8a3d91bf..e069a3ea 100644 --- a/fastlane/README.md +++ b/fastlane/README.md @@ -13,6 +13,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do # Available Actions +### generate_docs + +```sh +[bundle exec] fastlane generate_docs +``` + + + ### fetch_dependencies ```sh @@ -101,6 +109,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do +### get_next_build_number + +```sh +[bundle exec] fastlane get_next_build_number +``` + +**Get next Build Number** + ---- This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. diff --git a/ios/fastlane/Fastfile b/ios/fastlane/Fastfile index 74f26fb8..303fda94 100644 --- a/ios/fastlane/Fastfile +++ b/ios/fastlane/Fastfile @@ -2,16 +2,18 @@ import "../../fastlane/Fastfile" PROD_ENV = 'prod' DEV_ENV = 'dev' -DEFAULT_ENV = PROD_ENV +DEFAULT_ENV = DEV_ENV PROD_BUNDLE_ID = "com.xmartlabs.template" -DEV_BUNDLE_ID = "#{PROD_BUNDLE_ID}.dev" - -default_platform(:ios) +DEV_BUNDLE_ID = "com.xmartlabs.template.dev" +temp_keychain_name = ENV['TEMP_KEYCHAIN_NAME'] || 'build.keychain' +temp_keychain_password = ENV['TEMP_KEYCHAIN_PASSWORD'] || '' ## Define default value +test_groups = ENV['TESTFLIGHT_TESTER_GROUPS'] || '' ## Define default value provisioning_name = 'FastlaneXL' -platform :ios do +default_platform(:ios) +platform :ios do desc "Creates a temporal keystore and import the distribution certificate" desc "#### Envs\n" + "* **`TEMP_KEYCHAIN_NAME`**: The temporal keychain name\n" + @@ -21,17 +23,19 @@ platform :ios do lane :set_signing do safe_delete_keychain create_keychain( - name: ENV['TEMP_KEYCHAIN_NAME'], - password: ENV['TEMP_KEYCHAIN_PASSWORD'], + name: temp_keychain_name, + password: temp_keychain_password, unlock: true, timeout: 3600, lock_when_sleeps: true ) + import_certificate( - keychain_name: ENV['TEMP_KEYCHAIN_NAME'], - keychain_password: ENV['TEMP_KEYCHAIN_PASSWORD'], + keychain_name: temp_keychain_name, + keychain_password: temp_keychain_password, certificate_path: ENV['DIST_CERTIFICATE_PATH'], certificate_password: ENV['DIST_CERTIFICATE_PASSWORD'], + log_output: true, ) end @@ -54,22 +58,13 @@ platform :ios do app_identifier: app_identifier, ) update_code_signing_settings( - use_automatic_signing: false, - path: "Runner.xcodeproj", - team_id: ENV['APPLE_TEAM_ID'], - build_configurations: "Release-#{env}", - ) - update_project_team( + use_automatic_signing: false, path: "Runner.xcodeproj", - teamid: ENV['APPLE_TEAM_ID'], + team_id: ENV['APPLE_TEAM_ID'], + code_sign_identity: "Apple Distribution", + build_configurations: "Release-#{env}", + profile_uuid: lane_context[SharedValues::SIGH_UUID], ) - - update_project_provisioning( - xcodeproj: "Runner.xcodeproj", - build_configuration: "Release-#{env}", - profile: "./#{profile_type_name}_#{app_identifier}.mobileprovision", - code_signing_identity: "Apple Distribution" - ) end private_lane :rollback_provisioning_setup do |options| @@ -95,9 +90,9 @@ platform :ios do "* **`APPSTORE_CONNECT_API_KEY_BASE_64_CONTENT`**: The api key base64 content" lane :generate_release_ipa do |options| upload_symbols = options[:upload_symbols] || true - sign_enabled = options[:setup_signing] || true + sign_enabled = options[:setup_signing] || false if sign_enabled - set_signing + set_signing end env = options[:env] || DEFAULT_ENV @@ -115,15 +110,36 @@ platform :ios do env: env, build_export_method: build_export_method, ) + + if ENV['BUILD_NUMBER'] && ENV['BUILD_NUMBER'].match(/^\d+$/) + autoincrement_version_number = false + build_number = ENV['BUILD_NUMBER'].to_i + else + autoincrement_version_number = options[:autoincrement_version_number] || true + build_number = flutter_version()["version_code"] + if autoincrement_version_number + version_name = calculate_version_name( + include_git_version_suffix: false, + ) + build_number = latest_testflight_build_number(version: version_name) + 1 + end + end + build_flutter( build_platform: 'ipa', build_type: 'release', - flavor: env, - include_git_version_suffix: true, + env: env, + include_git_version_suffix: false, export_method: build_export_method, + sign_disabled: true, + build_number: build_number, + autoincrement_apple_version_number: autoincrement_version_number, ) + increment_build_number(build_number: build_number) + app_identifier = options[:env] == DEV_ENV ? DEV_BUNDLE_ID : PROD_BUNDLE_ID + build_ios_app( clean: false, scheme: env, @@ -131,15 +147,13 @@ platform :ios do export_method: build_export_method, xcargs: "-allowProvisioningUpdates", export_options: { - compileBitcode: true, - uploadBitcode: true, uploadSymbols: true, signingStyle: "manual", }, ) - if upload_symbols - upload_symbols_to_crashlytics(app_id: options[:firebase_app_id], debug: true) - end + if upload_symbols + upload_symbols_to_crashlytics(app_id: options[:firebase_app_id], debug: true) + end ensure rollback_provisioning_setup(env: env) safe_delete_keychain @@ -153,17 +167,17 @@ platform :ios do "* **`APPSTORE_CONNECT_API_KEY_ID`**: The connect api key id\n" + "* **`APPSTORE_CONNECT_API_KEY_ISSUER_ID`**: The connect api key issuer id\n" + "* **`APPSTORE_CONNECT_API_KEY_BASE_64_CONTENT`**: The api key base64 content" - lane :publish_dev_firebase do |options| + lane :deploy_firebase_app_distribution do |options| generate_release_ipa( - env: 'dev', + env: DEFAULT_ENV, build_export_method: 'ad-hoc', - firebase_app_id: ENV['FIREBASE_APP_ID_DEV'], + setup_signing: true, + firebase_app_id: ENV['FIREBASE_APP_ID'], ) firebase_app_distribution( - app: ENV['FIREBASE_APP_ID_DEV'], + app: ENV['FIREBASE_APP_ID'], service_credentials_file: ENV["FIREBASE_SERVICE_ACCOUNT_FILE"], - testers: ENV["FIREBASE_TESTERS"], groups: ENV["FIREBASE_TESTER_GROUPS"], release_notes: generate_snapshot_changelog, apk_path: "../Runner.ipa", @@ -177,19 +191,26 @@ platform :ios do "* **`APPLE_TEAM_ID`**: The apple team id\n" + "* **`APPSTORE_CONNECT_API_KEY_ID`**: The connect api key id\n" + "* **`APPSTORE_CONNECT_API_KEY_ISSUER_ID`**: The connect api key issuer id\n" + - "* **`APPSTORE_CONNECT_API_KEY_BASE_64_CONTENT`**: The api key base64 content" + "* **`APPSTORE_CONNECT_API_KEY_BASE_64_CONTENT`**: The api key base64 content\n" + + "* **`TESTFLIGHT_TESTER_GROUPS`**: Testflight testing groups" lane :publish_prod_testflight do |options| generate_release_ipa( + setup_signing: true, env: 'prod', build_export_method: 'app-store', + firebase_app_id: ENV['FIREBASE_APP_ID'], + ) + upload_to_testflight( + skip_waiting_for_build_processing: true, + build_number: lane_context[SharedValues::BUILD_NUMBER], + groups: ENV["TESTFLIGHT_TESTER_GROUPS"], ) - upload_to_testflight(skip_waiting_for_build_processing: true) end private_lane :safe_delete_keychain do begin - FastlaneCore::Helper.keychain_path(ENV['TEMP_KEYCHAIN_NAME']) - delete_keychain(name: ENV['TEMP_KEYCHAIN_NAME']) + FastlaneCore::Helper.keychain_path(temp_keychain_name) + delete_keychain(name: temp_keychain_name) rescue => ex UI.important('Keystore not found') end diff --git a/ios/fastlane/README.md b/ios/fastlane/README.md index e7771056..9ec8fcc3 100644 --- a/ios/fastlane/README.md +++ b/ios/fastlane/README.md @@ -13,6 +13,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do # Available Actions +### generate_docs + +```sh +[bundle exec] fastlane generate_docs +``` + + + ### fetch_dependencies ```sh @@ -101,6 +109,14 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do +### get_next_build_number + +```sh +[bundle exec] fastlane get_next_build_number +``` + +**Get next Build Number** + ---- @@ -137,10 +153,10 @@ Build deploy version * **`APPSTORE_CONNECT_API_KEY_ISSUER_ID`**: The connect api key issuer id * **`APPSTORE_CONNECT_API_KEY_BASE_64_CONTENT`**: The api key base64 content -### ios publish_dev_firebase +### ios deploy_firebase_app_distribution ```sh -[bundle exec] fastlane ios publish_dev_firebase +[bundle exec] fastlane ios deploy_firebase_app_distribution ``` Push a new beta build to Firebase