From 235436886ea31ef8346be3bb5f524f0f36cee61e Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sun, 30 Nov 2025 11:38:16 +0000 Subject: [PATCH 01/12] ci: added own bump script instead of relying on third party repos. removed the suffix from the app version on beta to stick to ios guidelines globally, meaning it affects both platforms now too --- .github/scripts/bump_version.sh | 57 ++++++++++++++++++++++++++++++++ packages/uni_app/app_version.txt | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100755 .github/scripts/bump_version.sh diff --git a/.github/scripts/bump_version.sh b/.github/scripts/bump_version.sh new file mode 100755 index 000000000..ce6ee0603 --- /dev/null +++ b/.github/scripts/bump_version.sh @@ -0,0 +1,57 @@ +#!/bin/bash +set -e + +# Usage: ./bump_version.sh [develop|production] [major|minor|patch] + +MODE=$1 +TYPE=${2:-patch} + +PUBSPEC_FILE="pubspec.yaml" +VERSION_FILE="app_version.txt" + +# 1. Read current version from app_version.txt +if [ ! -f "$VERSION_FILE" ]; then + echo "Error: $VERSION_FILE not found." + exit 1 +fi + +FULL_VERSION=$(cat "$VERSION_FILE") + +# Validate format +if [[ $FULL_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)\+([0-9]+)$ ]]; then + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + PATCH="${BASH_REMATCH[3]}" + BUILD="${BASH_REMATCH[4]}" +else + echo "Error: Invalid version format in $VERSION_FILE: $FULL_VERSION" + echo "Expected format: X.Y.Z+BUILD" + exit 1 +fi + +# 2. Calculate New Version +NEW_BUILD=$((BUILD + 1)) + +if [ "$MODE" == "production" ]; then + if [ "$TYPE" == "major" ]; then + MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 + elif [ "$TYPE" == "minor" ]; then + MINOR=$((MINOR + 1)); PATCH=0 + else + PATCH=$((PATCH + 1)) + fi + NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD" +else + NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD" +fi + +echo "Bumping version: $FULL_VERSION -> $NEW_VERSION" + +# 3. Update Files +echo "$NEW_VERSION" > "$VERSION_FILE" +sed -i -E "s/^version: .*/version: $NEW_VERSION/" "$PUBSPEC_FILE" + +# 4. Output for GitHub Actions +if [ -n "$GITHUB_OUTPUT" ]; then + echo "NEW_VERSION=$NEW_VERSION" >> "$GITHUB_OUTPUT" +fi \ No newline at end of file diff --git a/packages/uni_app/app_version.txt b/packages/uni_app/app_version.txt index 9b76bdeee..f9f81ac8e 100644 --- a/packages/uni_app/app_version.txt +++ b/packages/uni_app/app_version.txt @@ -1 +1 @@ -2.2.0-beta.25+433 +2.2.0+433 From 18f810aec8eafcea99ca80ef2c4198c50a497585 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:38:47 +0000 Subject: [PATCH 02/12] ci: added new workflow for deploying the develop branch to android beta and ios testflight with version validation from both stores --- .github/workflows/deploy_beta.yaml | 162 ++++++++++++++++++++++++ .github/workflows/validate_version.yaml | 124 ++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 .github/workflows/deploy_beta.yaml create mode 100644 .github/workflows/validate_version.yaml diff --git a/.github/workflows/deploy_beta.yaml b/.github/workflows/deploy_beta.yaml new file mode 100644 index 000000000..1f13f272b --- /dev/null +++ b/.github/workflows/deploy_beta.yaml @@ -0,0 +1,162 @@ + +# CI: Deploy Beta Workflow +name: "CI: Deploy Beta" + + +on: + push: + branches: [develop] + + +# Ensure only one workflow runs per branch at a time +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + +jobs: + # 1. Validate Version + validate_version: + name: "Validate Version" + uses: ./.github/workflows/validate_version.yaml + with: + working-directory: ./packages/uni_app + secrets: + google_service_account_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} + asc_key_id: ${{ secrets.ASC_KEY_ID }} + asc_issuer_id: ${{ secrets.ASC_ISSUER_ID }} + asc_key_content: ${{ secrets.ASC_KEY_CONTENT }} + + + # 2. Bump Version + bump_version: + name: "Bump Version" + needs: validate_version + runs-on: ubuntu-latest + permissions: + contents: write + defaults: + run: + working-directory: ./packages/uni_app + outputs: + new_version: ${{ steps.bump.outputs.NEW_VERSION }} + commit_sha: ${{ steps.commit.outputs.COMMIT_SHA }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.NIAEFEUPBOT_PAT }} + fetch-depth: 0 + + - name: Run Bump Version Script (simulated) + id: bump + run: | + chmod +x ../../.github/scripts/bump_version.sh + ../../.github/scripts/bump_version.sh develop + + - name: Commit & Push Version Bump + id: commit + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + NEW_VERSION=${{ steps.bump.outputs.NEW_VERSION }} + + git add app_version.txt pubspec.yaml + git commit -m "chore(ci): Bump build to $NEW_VERSION [skip ci]" + + REMOTE_URL="https://x-access-token:${{ secrets.NIAEFEUPBOT_PAT }}@github.com/${{ github.repository }}.git" + + git push $REMOTE_URL HEAD:develop + + echo "COMMIT_SHA=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + + # 3. Deploy Android Beta + android_beta: + name: "Deploy Android (Beta)" + needs: bump_version + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./packages/uni_app + steps: + - name: Checkout repository at bumped commit + uses: actions/checkout@v4 + with: + ref: ${{ needs.bump_version.outputs.commit_sha }} + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + working-directory: ./packages/uni_app + + - name: Create .env file + run: echo "${{ vars.UNI_ENV_FILE }}" > ./assets/env/.env + + - name: Deploy to Android Beta (simulated Fastlane) + env: + ANDROID_JSON_KEY_FILE: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} + ANDROID_KEYSTORE_FILE: ${{ secrets.UPLOAD_KEYSTORE_BASE64 }} + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }} + ANDROID_KEY_ALIAS: upload + ANDROID_KEY_PASSWORD: ${{ secrets.UPLOAD_KEY_PASSWORD }} + run: | + echo "$ANDROID_KEYSTORE_FILE" | base64 --decode > /tmp/upload-keystore.jks + export ANDROID_KEYSTORE_FILE_PATH=/tmp/upload-keystore.jks + bundle exec fastlane android deploy_beta + + + # 4. Deploy iOS TestFlight + ios_beta: + name: "Deploy iOS (TestFlight)" + needs: bump_version + runs-on: macos-latest + defaults: + run: + working-directory: ./packages/uni_app + steps: + - name: Checkout repository at bumped commit + uses: actions/checkout@v4 + with: + ref: ${{ needs.bump_version.outputs.commit_sha }} + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + working-directory: ./packages/uni_app + + - name: Create .env file + run: echo "${{ vars.UNI_ENV_FILE }}" > ./assets/env/.env + + - name: Deploy to iOS TestFlight (simulated Fastlane) + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} + ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} + ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} + ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }} + run: | + bundle exec fastlane ios deploy_testflight \ No newline at end of file diff --git a/.github/workflows/validate_version.yaml b/.github/workflows/validate_version.yaml new file mode 100644 index 000000000..ab96e112b --- /dev/null +++ b/.github/workflows/validate_version.yaml @@ -0,0 +1,124 @@ + +# Validate Version Workflow +name: "Validate Version" + + +on: + workflow_call: + inputs: + working-directory: + description: "Path to the uni_app working directory" + required: false + type: string + default: ./packages/uni_app + android_track: + description: "Google Play track to query (internal, beta, production)" + required: false + type: string + default: beta + ios_channel: + description: "iOS source to query (testflight, appstore)" + required: false + type: string + default: testflight + secrets: + google_service_account_json: + description: "Google service account JSON for Play Console" + required: true + asc_key_id: + description: "App Store Connect API Key ID" + required: true + asc_issuer_id: + description: "App Store Connect Issuer ID" + required: true + asc_key_content: + description: "App Store Connect API Key content (p8)" + required: true + outputs: + current_version: + description: "Version read from app_version.txt" + value: ${{ jobs.validate.outputs.current_version }} + build_number: + description: "Build number parsed from version" + value: ${{ jobs.validate.outputs.build_number }} + android_store_build_number: + description: "Latest Android beta build number from Play Console" + value: ${{ jobs.validate.outputs.android_store_build_number }} + ios_store_build_number: + description: "Latest iOS build number from selected channel" + value: ${{ jobs.validate.outputs.ios_store_build_number }} + + +jobs: + validate: + name: "Validate Version" + runs-on: ubuntu-latest + defaults: + run: + working-directory: ${{ inputs.working-directory }} + outputs: + current_version: ${{ steps.read_version.outputs.CURRENT_VERSION }} + build_number: ${{ steps.read_version.outputs.BUILD_NUMBER }} + android_store_build_number: ${{ steps.android_store.outputs.ANDROID_STORE_BUILD_NUMBER }} + ios_store_build_number: ${{ steps.ios_store.outputs.IOS_STORE_BUILD_NUMBER }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Read Current Version + id: read_version + run: | + # Read version from app_version.txt and extract build number + FULL_VERSION=$(cat app_version.txt) + echo "CURRENT_VERSION=$FULL_VERSION" >> $GITHUB_OUTPUT + BUILD_NUMBER=$(echo "$FULL_VERSION" | cut -d'+' -f2) + echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_OUTPUT + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + working-directory: ${{ inputs.working-directory }} + + - name: Get Latest Android Store Version + id: android_store + env: + GOOGLE_SERVICE_ACCOUNT_JSON: ${{ secrets.google_service_account_json }} + run: | + echo "$GOOGLE_SERVICE_ACCOUNT_JSON" > /tmp/google_service_account.json + export SUPPLY_JSON_KEY=/tmp/google_service_account.json + ANDROID_BUILD=$(bundle exec fastlane run google_play_track_version_codes track:${{ inputs.android_track }} -q 2>/dev/null | grep -oE '[0-9]+' | head -1 || echo "0") + echo "ANDROID_STORE_BUILD_NUMBER=$ANDROID_BUILD" >> $GITHUB_OUTPUT + rm /tmp/google_service_account.json + + - name: Get Latest iOS Store Version + id: ios_store + env: + ASC_KEY_ID: ${{ secrets.asc_key_id }} + ASC_ISSUER_ID: ${{ secrets.asc_issuer_id }} + ASC_KEY_CONTENT: ${{ secrets.asc_key_content }} + run: | + IOS_BUILD="0" + if [ "${{ inputs.ios_channel }}" = "appstore" ]; then + IOS_BUILD=$(bundle exec fastlane run app_store_build_number -q 2>/dev/null | grep -oE '[0-9]+' | tail -1 || echo "0") + else + IOS_BUILD=$(bundle exec fastlane run latest_testflight_build_number -q 2>/dev/null | grep -oE '[0-9]+' | tail -1 || echo "0") + fi + echo "IOS_STORE_BUILD_NUMBER=$IOS_BUILD" >> $GITHUB_OUTPUT + + - name: Validate Build Numbers + run: | + # Ensure current build number is greater than both store versions + CURRENT_BUILD="${{ steps.read_version.outputs.BUILD_NUMBER }}" + ANDROID_BUILD="${{ steps.android_store.outputs.ANDROID_STORE_BUILD_NUMBER }}" + IOS_BUILD="${{ steps.ios_store.outputs.IOS_STORE_BUILD_NUMBER }}" + + echo "Current build: $CURRENT_BUILD" + echo "Android store build: $ANDROID_BUILD" + echo "iOS store build: $IOS_BUILD" + + if [ "$CURRENT_BUILD" -le "$ANDROID_BUILD" ] || [ "$CURRENT_BUILD" -le "$IOS_BUILD" ]; then + echo "Error: Current build number ($CURRENT_BUILD) must be greater than both store versions (Android: $ANDROID_BUILD, iOS: $IOS_BUILD)" + exit 1 + fi From e7a55217e8a7acd760b132cdf9247075b971986f Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sun, 30 Nov 2025 14:20:40 +0000 Subject: [PATCH 03/12] ci: changed how the deploy_beta works to use matrix strategy --- .github/workflows/deploy_beta.yaml | 108 +++++++++++------------------ 1 file changed, 40 insertions(+), 68 deletions(-) diff --git a/.github/workflows/deploy_beta.yaml b/.github/workflows/deploy_beta.yaml index 1f13f272b..c0d93fd83 100644 --- a/.github/workflows/deploy_beta.yaml +++ b/.github/workflows/deploy_beta.yaml @@ -1,19 +1,13 @@ - -# CI: Deploy Beta Workflow name: "CI: Deploy Beta" - on: push: branches: [develop] - -# Ensure only one workflow runs per branch at a time concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - jobs: # 1. Validate Version validate_version: @@ -27,7 +21,6 @@ jobs: asc_issuer_id: ${{ secrets.ASC_ISSUER_ID }} asc_key_content: ${{ secrets.ASC_KEY_CONTENT }} - # 2. Bump Version bump_version: name: "Bump Version" @@ -39,7 +32,6 @@ jobs: run: working-directory: ./packages/uni_app outputs: - new_version: ${{ steps.bump.outputs.NEW_VERSION }} commit_sha: ${{ steps.commit.outputs.COMMIT_SHA }} steps: - name: Checkout repository @@ -48,9 +40,9 @@ jobs: token: ${{ secrets.NIAEFEUPBOT_PAT }} fetch-depth: 0 - - name: Run Bump Version Script (simulated) - id: bump + - name: Run Bump Version Script run: | + # Script path must now be relative to packages/uni_app chmod +x ../../.github/scripts/bump_version.sh ../../.github/scripts/bump_version.sh develop @@ -59,29 +51,40 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - NEW_VERSION=${{ steps.bump.outputs.NEW_VERSION }} - + git add app_version.txt pubspec.yaml - git commit -m "chore(ci): Bump build to $NEW_VERSION [skip ci]" - - REMOTE_URL="https://x-access-token:${{ secrets.NIAEFEUPBOT_PAT }}@github.com/${{ github.repository }}.git" - - git push $REMOTE_URL HEAD:develop - - echo "COMMIT_SHA=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - - # 3. Deploy Android Beta - android_beta: - name: "Deploy Android (Beta)" + + if ! git diff --staged --quiet; then + NEW_VERSION=$(cat app_version.txt) + git commit -m "chore(ci): Bump build to $NEW_VERSION [skip ci]" + git push origin HEAD:develop + echo "COMMIT_SHA=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + else + echo "No version change detected." + echo "COMMIT_SHA=${{ github.sha }}" >> $GITHUB_OUTPUT + fi + + # 3. Deploy Matrix (Android & iOS) + deploy_beta: + name: "Deploy ${{ matrix.platform }} (Beta)" needs: bump_version - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} defaults: run: working-directory: ./packages/uni_app + strategy: + fail-fast: false + matrix: + include: + - platform: android + os: ubuntu-latest + lane: deploy_beta + - platform: ios + os: macos-latest + lane: deploy_testflight + steps: - - name: Checkout repository at bumped commit + - name: Checkout repository uses: actions/checkout@v4 with: ref: ${{ needs.bump_version.outputs.commit_sha }} @@ -108,55 +111,24 @@ jobs: - name: Create .env file run: echo "${{ vars.UNI_ENV_FILE }}" > ./assets/env/.env - - name: Deploy to Android Beta (simulated Fastlane) + - name: Setup Android Keystore + if: matrix.platform == 'android' + run: | + echo "${{ secrets.UPLOAD_KEYSTORE_BASE64 }}" | base64 --decode > /tmp/upload-keystore.jks + + - name: Run Fastlane env: + # Android Secrets ANDROID_JSON_KEY_FILE: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} - ANDROID_KEYSTORE_FILE: ${{ secrets.UPLOAD_KEYSTORE_BASE64 }} + ANDROID_KEYSTORE_FILE_PATH: /tmp/upload-keystore.jks ANDROID_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: upload ANDROID_KEY_PASSWORD: ${{ secrets.UPLOAD_KEY_PASSWORD }} - run: | - echo "$ANDROID_KEYSTORE_FILE" | base64 --decode > /tmp/upload-keystore.jks - export ANDROID_KEYSTORE_FILE_PATH=/tmp/upload-keystore.jks - bundle exec fastlane android deploy_beta - - - # 4. Deploy iOS TestFlight - ios_beta: - name: "Deploy iOS (TestFlight)" - needs: bump_version - runs-on: macos-latest - defaults: - run: - working-directory: ./packages/uni_app - steps: - - name: Checkout repository at bumped commit - uses: actions/checkout@v4 - with: - ref: ${{ needs.bump_version.outputs.commit_sha }} - - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable - cache: true - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - working-directory: ./packages/uni_app - - - name: Create .env file - run: echo "${{ vars.UNI_ENV_FILE }}" > ./assets/env/.env - - - name: Deploy to iOS TestFlight (simulated Fastlane) - env: + # iOS Secrets MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }} run: | - bundle exec fastlane ios deploy_testflight \ No newline at end of file + bundle exec fastlane ${{ matrix.platform }} ${{ matrix.lane }} \ No newline at end of file From b20b1c31dadcc1ab8109a44ece463d9f3702b94f Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:36:07 +0000 Subject: [PATCH 04/12] feat: add version validating workflow with fastlane files --- .github/workflows/scripts/bump_version.sh | 57 +++++ .github/workflows/validate_version.yaml | 58 +++-- packages/uni_app/Gemfile | 3 + packages/uni_app/Gemfile.lock | 234 +++++++++++++++++++++ packages/uni_app/android/fastlane/Appfile | 1 + packages/uni_app/android/fastlane/Fastfile | 14 ++ packages/uni_app/app_version.txt | 2 +- packages/uni_app/ios/fastlane/Appfile | 1 + packages/uni_app/ios/fastlane/Fastfile | 22 ++ 9 files changed, 361 insertions(+), 31 deletions(-) create mode 100755 .github/workflows/scripts/bump_version.sh create mode 100644 packages/uni_app/Gemfile create mode 100644 packages/uni_app/Gemfile.lock create mode 100644 packages/uni_app/android/fastlane/Appfile create mode 100644 packages/uni_app/android/fastlane/Fastfile create mode 100644 packages/uni_app/ios/fastlane/Appfile create mode 100644 packages/uni_app/ios/fastlane/Fastfile diff --git a/.github/workflows/scripts/bump_version.sh b/.github/workflows/scripts/bump_version.sh new file mode 100755 index 000000000..300763202 --- /dev/null +++ b/.github/workflows/scripts/bump_version.sh @@ -0,0 +1,57 @@ +#!/bin/bash +set -e + +# Usage: ./bump_version.sh [develop|production] [major|minor|patch] + +MODE=$1 +TYPE=${2:-patch} + +PUBSPEC_FILE="pubspec.yaml" +VERSION_FILE="app_version.txt" + +# 1. Read current version from app_version.txt +if [ ! -f "$VERSION_FILE" ]; then + echo "Error: $VERSION_FILE not found." + exit 1 +fi + +FULL_VERSION=$(cat "$VERSION_FILE") + +# Validate format +if [[ $FULL_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)\+([0-9]+)$ ]]; then + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + PATCH="${BASH_REMATCH[3]}" + BUILD="${BASH_REMATCH[4]}" +else + echo "Error: Invalid version format in $VERSION_FILE: $FULL_VERSION" + echo "Expected format: X.Y.Z+BUILD" + exit 1 +fi + +# 2. Calculate New Version +NEW_BUILD=$((BUILD + 1)) + +if [ "$MODE" == "production" ]; then + if [ "$TYPE" == "major" ]; then + MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 + elif [ "$TYPE" == "minor" ]; then + MINOR=$((MINOR + 1)); PATCH=0 + else + PATCH=$((PATCH + 1)) + fi + NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD" +else + NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD" +fi + +echo "Bumping version: $FULL_VERSION -> $NEW_VERSION" + +# 3. Update Files +echo "$NEW_VERSION" > "$VERSION_FILE" +sed -i -E "s/^version: .*/version: $NEW_VERSION/" "$PUBSPEC_FILE" + +# 4. Output for GitHub Actions +if [ -n "$GITHUB_OUTPUT" ]; then + echo "NEW_VERSION=$NEW_VERSION" >> "$GITHUB_OUTPUT" +fi diff --git a/.github/workflows/validate_version.yaml b/.github/workflows/validate_version.yaml index ab96e112b..7535fe7d7 100644 --- a/.github/workflows/validate_version.yaml +++ b/.github/workflows/validate_version.yaml @@ -1,18 +1,15 @@ - # Validate Version Workflow name: "Validate Version" - on: workflow_call: inputs: working-directory: - description: "Path to the uni_app working directory" required: false type: string default: ./packages/uni_app android_track: - description: "Google Play track to query (internal, beta, production)" + description: "Google Play track to query (beta, production)" required: false type: string default: beta @@ -25,14 +22,8 @@ on: google_service_account_json: description: "Google service account JSON for Play Console" required: true - asc_key_id: - description: "App Store Connect API Key ID" - required: true - asc_issuer_id: - description: "App Store Connect Issuer ID" - required: true - asc_key_content: - description: "App Store Connect API Key content (p8)" + asc_json: + description: "App Store Connect JSON" required: true outputs: current_version: @@ -48,7 +39,6 @@ on: description: "Latest iOS build number from selected channel" value: ${{ jobs.validate.outputs.ios_store_build_number }} - jobs: validate: name: "Validate Version" @@ -68,7 +58,6 @@ jobs: - name: Read Current Version id: read_version run: | - # Read version from app_version.txt and extract build number FULL_VERSION=$(cat app_version.txt) echo "CURRENT_VERSION=$FULL_VERSION" >> $GITHUB_OUTPUT BUILD_NUMBER=$(echo "$FULL_VERSION" | cut -d'+' -f2) @@ -86,30 +75,35 @@ jobs: env: GOOGLE_SERVICE_ACCOUNT_JSON: ${{ secrets.google_service_account_json }} run: | + trap 'rm -f /tmp/google_service_account.json' EXIT echo "$GOOGLE_SERVICE_ACCOUNT_JSON" > /tmp/google_service_account.json - export SUPPLY_JSON_KEY=/tmp/google_service_account.json - ANDROID_BUILD=$(bundle exec fastlane run google_play_track_version_codes track:${{ inputs.android_track }} -q 2>/dev/null | grep -oE '[0-9]+' | head -1 || echo "0") + + cd android + + bundle exec fastlane get_version_code track:${{ inputs.android_track }} key_path:/tmp/google_service_account.json + + ANDROID_BUILD=$(cat android_version_code.txt) + echo "Final Parsed Android Build: $ANDROID_BUILD" echo "ANDROID_STORE_BUILD_NUMBER=$ANDROID_BUILD" >> $GITHUB_OUTPUT - rm /tmp/google_service_account.json - name: Get Latest iOS Store Version id: ios_store env: - ASC_KEY_ID: ${{ secrets.asc_key_id }} - ASC_ISSUER_ID: ${{ secrets.asc_issuer_id }} - ASC_KEY_CONTENT: ${{ secrets.asc_key_content }} + ASC_JSON: ${{ secrets.asc_json }} run: | - IOS_BUILD="0" - if [ "${{ inputs.ios_channel }}" = "appstore" ]; then - IOS_BUILD=$(bundle exec fastlane run app_store_build_number -q 2>/dev/null | grep -oE '[0-9]+' | tail -1 || echo "0") - else - IOS_BUILD=$(bundle exec fastlane run latest_testflight_build_number -q 2>/dev/null | grep -oE '[0-9]+' | tail -1 || echo "0") - fi + trap 'rm -f /tmp/ios_api_key.json' EXIT + echo "$ASC_JSON" > /tmp/ios_api_key.json + + cd ios + + bundle exec fastlane get_version_code channel:${{ inputs.ios_channel }} key_path:/tmp/ios_api_key.json + + IOS_BUILD=$(cat ios_build_number.txt) + echo "Final Parsed iOS Build: $IOS_BUILD" echo "IOS_STORE_BUILD_NUMBER=$IOS_BUILD" >> $GITHUB_OUTPUT - name: Validate Build Numbers run: | - # Ensure current build number is greater than both store versions CURRENT_BUILD="${{ steps.read_version.outputs.BUILD_NUMBER }}" ANDROID_BUILD="${{ steps.android_store.outputs.ANDROID_STORE_BUILD_NUMBER }}" IOS_BUILD="${{ steps.ios_store.outputs.IOS_STORE_BUILD_NUMBER }}" @@ -117,8 +111,12 @@ jobs: echo "Current build: $CURRENT_BUILD" echo "Android store build: $ANDROID_BUILD" echo "iOS store build: $IOS_BUILD" + + curr=${CURRENT_BUILD:-0} + and=${ANDROID_BUILD:-0} + ios=${IOS_BUILD:-0} - if [ "$CURRENT_BUILD" -le "$ANDROID_BUILD" ] || [ "$CURRENT_BUILD" -le "$IOS_BUILD" ]; then - echo "Error: Current build number ($CURRENT_BUILD) must be greater than both store versions (Android: $ANDROID_BUILD, iOS: $IOS_BUILD)" + if [ "$curr" -le "$and" ] || [ "$curr" -le "$ios" ]; then + echo "Error: Current build number ($curr) must be greater than both store versions (Android: $and, iOS: $ios)" exit 1 - fi + fi \ No newline at end of file diff --git a/packages/uni_app/Gemfile b/packages/uni_app/Gemfile new file mode 100644 index 000000000..7a118b49b --- /dev/null +++ b/packages/uni_app/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "fastlane" diff --git a/packages/uni_app/Gemfile.lock b/packages/uni_app/Gemfile.lock new file mode 100644 index 000000000..3303f297b --- /dev/null +++ b/packages/uni_app/Gemfile.lock @@ -0,0 +1,234 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + abbrev (0.1.2) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1209.0) + aws-sdk-core (3.241.4) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.121.0) + aws-sdk-core (~> 3, >= 3.241.4) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.212.0) + aws-sdk-core (~> 3, >= 3.241.4) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + benchmark (0.5.0) + bigdecimal (4.0.1) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + csv (3.3.5) + declarative (0.0.20) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.112.0) + faraday (1.8.0) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0.1) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.1) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + multipart-post (>= 1.2, < 3) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.8) + faraday (>= 0.8.0) + http-cookie (>= 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.1) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.0) + fastlane (2.231.1) + CFPropertyList (>= 2.3, < 4.0.0) + abbrev (~> 0.1.2) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2.0) + benchmark (>= 0.1.0) + bundler (>= 1.17.3, < 5.0.0) + colored (~> 1.2) + commander (~> 4.6) + csv (~> 3.3) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, < 2.0.0) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + logger (>= 1.6, < 2.0) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + mutex_m (~> 0.3.0) + naturally (~> 2.2) + nkf (~> 0.2.0) + optparse (>= 0.1.1, < 1.0.0) + ostruct (>= 0.1.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.5.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + httpclient (2.9.0) + mutex_m + jmespath (1.6.2) + json (2.18.0) + jwt (2.10.2) + base64 + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + multi_json (1.19.1) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + nkf (0.2.0) + optparse (0.8.1) + os (1.1.4) + ostruct (0.6.3) + plist (3.7.2) + public_suffix (7.0.2) + rake (13.3.1) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.4.4) + rouge (3.28.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + security (0.1.5) + signet (0.21.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + sysrandom (1.0.5) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + uber (0.1.0) + unicode-display_width (2.6.0) + word_wrap (1.0.0) + xcodeproj (1.27.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + fastlane + +BUNDLED WITH + 4.0.3 diff --git a/packages/uni_app/android/fastlane/Appfile b/packages/uni_app/android/fastlane/Appfile new file mode 100644 index 000000000..dbb9b4372 --- /dev/null +++ b/packages/uni_app/android/fastlane/Appfile @@ -0,0 +1 @@ +package_name("pt.up.fe.ni.uni") \ No newline at end of file diff --git a/packages/uni_app/android/fastlane/Fastfile b/packages/uni_app/android/fastlane/Fastfile new file mode 100644 index 000000000..9e6e0fe1c --- /dev/null +++ b/packages/uni_app/android/fastlane/Fastfile @@ -0,0 +1,14 @@ +default_platform(:android) + +platform :android do + desc "Get latest version code from Google Play" + lane :get_version_code do |options| + codes = google_play_track_version_codes( + track: options[:track], + json_key: options[:key_path] + ) + latest_code = codes.max || 0 + + File.write("../android_version_code.txt", latest_code.to_s) + end +end diff --git a/packages/uni_app/app_version.txt b/packages/uni_app/app_version.txt index 83d38eff7..f8d762719 100644 --- a/packages/uni_app/app_version.txt +++ b/packages/uni_app/app_version.txt @@ -1 +1 @@ -2.2.0+435 \ No newline at end of file +2.2.0+447 \ No newline at end of file diff --git a/packages/uni_app/ios/fastlane/Appfile b/packages/uni_app/ios/fastlane/Appfile new file mode 100644 index 000000000..6587469e0 --- /dev/null +++ b/packages/uni_app/ios/fastlane/Appfile @@ -0,0 +1 @@ +app_identifier("pt.up.fe.ni.uni") \ No newline at end of file diff --git a/packages/uni_app/ios/fastlane/Fastfile b/packages/uni_app/ios/fastlane/Fastfile new file mode 100644 index 000000000..4df8bf71b --- /dev/null +++ b/packages/uni_app/ios/fastlane/Fastfile @@ -0,0 +1,22 @@ +default_platform(:ios) + +platform :ios do + desc "Get latest build number from TestFlight or App Store" + lane :get_version_code do |options| + build_number = 0 + if options[:channel] == "appstore" + build_number = app_store_build_number( + api_key_path: options[:key_path], + live: true + ) + else + build_number = latest_testflight_build_number( + api_key_path: options[:key_path] + ) + end + + build_number = (build_number || 0).to_i + + File.write("../ios_build_number.txt", build_number.to_s) + end +end From 64351efdbc57a06512efb486c340d75f1bd40853 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:38:05 +0000 Subject: [PATCH 05/12] fix: removed unused script --- .github/workflows/scripts/bump_version.sh | 57 ----------------------- 1 file changed, 57 deletions(-) delete mode 100755 .github/workflows/scripts/bump_version.sh diff --git a/.github/workflows/scripts/bump_version.sh b/.github/workflows/scripts/bump_version.sh deleted file mode 100755 index 300763202..000000000 --- a/.github/workflows/scripts/bump_version.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash -set -e - -# Usage: ./bump_version.sh [develop|production] [major|minor|patch] - -MODE=$1 -TYPE=${2:-patch} - -PUBSPEC_FILE="pubspec.yaml" -VERSION_FILE="app_version.txt" - -# 1. Read current version from app_version.txt -if [ ! -f "$VERSION_FILE" ]; then - echo "Error: $VERSION_FILE not found." - exit 1 -fi - -FULL_VERSION=$(cat "$VERSION_FILE") - -# Validate format -if [[ $FULL_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)\+([0-9]+)$ ]]; then - MAJOR="${BASH_REMATCH[1]}" - MINOR="${BASH_REMATCH[2]}" - PATCH="${BASH_REMATCH[3]}" - BUILD="${BASH_REMATCH[4]}" -else - echo "Error: Invalid version format in $VERSION_FILE: $FULL_VERSION" - echo "Expected format: X.Y.Z+BUILD" - exit 1 -fi - -# 2. Calculate New Version -NEW_BUILD=$((BUILD + 1)) - -if [ "$MODE" == "production" ]; then - if [ "$TYPE" == "major" ]; then - MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 - elif [ "$TYPE" == "minor" ]; then - MINOR=$((MINOR + 1)); PATCH=0 - else - PATCH=$((PATCH + 1)) - fi - NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD" -else - NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD" -fi - -echo "Bumping version: $FULL_VERSION -> $NEW_VERSION" - -# 3. Update Files -echo "$NEW_VERSION" > "$VERSION_FILE" -sed -i -E "s/^version: .*/version: $NEW_VERSION/" "$PUBSPEC_FILE" - -# 4. Output for GitHub Actions -if [ -n "$GITHUB_OUTPUT" ]; then - echo "NEW_VERSION=$NEW_VERSION" >> "$GITHUB_OUTPUT" -fi From bf3318f2e07d352d052cea6ef7b3e410c98f1520 Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:38:08 +0000 Subject: [PATCH 06/12] chore: removed unused files --- .dockerignore | 6 - .github/workflows/deploy.yaml | 181 ------------------------ .github/workflows/deploy_beta.yaml | 134 ------------------ .github/workflows/validate_version.yaml | 122 ---------------- Dockerfile | 9 -- 5 files changed, 452 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .github/workflows/deploy.yaml delete mode 100644 .github/workflows/deploy_beta.yaml delete mode 100644 .github/workflows/validate_version.yaml delete mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 4b7588ebf..000000000 --- a/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -uni/.packages -uni/.flutter-plugins -uni/.metadata -uni/*.log -uni/.vscode -uni/.idea \ No newline at end of file diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml deleted file mode 100644 index fac76818e..000000000 --- a/.github/workflows/deploy.yaml +++ /dev/null @@ -1,181 +0,0 @@ -on: - push: - branches: [master, develop] - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - -name: Deploy Action -jobs: - build: - permissions: - actions: "write" - name: "Bump version and Build App Bundle" - runs-on: ubuntu-latest - environment: - name: ${{ github.ref_name }} - env: - PROPERTIES_PATH: android/key.properties - JAVA_VERSION: 21.x - APP_VERSION_PATH: app_version.txt - PUBSPEC_PATH: pubspec.yaml - defaults: - run: - working-directory: ./packages/uni_app - steps: - - uses: actions/checkout@v4 - with: - token: ${{ secrets.NIAEFEUPBOT_PAT }} - fetch-depth: 0 - - - name: Get develop hash - if: github.ref == 'refs/heads/master' - # We get the master hash by assuming that the last commit is always a - # merge commit. This is assured by requiring pull requests. You should NOT - # use rebase or squash merges onto the master branch. - run: | - git fetch origin develop - git pull origin master - echo "DEVELOP_HASH=$(git rev-parse origin/develop)" >> $GITHUB_ENV - echo "MASTER_HASH=$(git rev-parse origin/master^2)" >> $GITHUB_ENV - - - name: Get latest version (develop) - if: github.ref != 'refs/heads/master' - uses: LuisDuarte1/google-play-latest-version-code@v0.2.1 - id: latest-beta-version - with: - google_service_account_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} - package_name: ${{ secrets.ANDROID_PACKAGE_NAME }} - track: "beta" - - - name: Get latest production version - uses: LuisDuarte1/google-play-latest-version-code@v0.2.1 - id: latest-production-version - with: - google_service_account_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} - package_name: ${{ secrets.ANDROID_PACKAGE_NAME }} - track: "production" - - - name: Bump beta version - uses: LuisDuarte1/semver-bump-environment@v1.0.0 - if: github.ref != 'refs/heads/master' - id: bump-beta-version - with: - current_environment: staging - production_version: ${{ steps.latest-production-version.outputs.latest_version_name }} - staging_version: ${{ steps.latest-beta-version.outputs.latest_version_name }} - bump_type: prerelease - - - name: Bump prod version (from develop) - uses: LuisDuarte1/semver-bump-environment@v1.0.0 - if: github.ref == 'refs/heads/master' && env.MASTER_HASH == env.DEVELOP_HASH - id: bump-prod-major-version - with: - current_environment: production - production_version: ${{ steps.latest-production-version.outputs.latest_version_name }} - bump_type: minor - - - name: Bump prod version (patch) - uses: LuisDuarte1/semver-bump-environment@v1.0.0 - if: github.ref == 'refs/heads/master' && env.MASTER_HASH != env.DEVELOP_HASH - id: bump-prod-patch-version - with: - current_environment: production - production_version: ${{ steps.latest-production-version.outputs.latest_version_name }} - bump_type: patch - - - name: Combine output and write new version into file - run: | - export NEW_VERSION_NAME=${{ - (steps.bump-beta-version.outcome == 'success' && steps.bump-beta-version.outputs.new_version) || - (steps.bump-prod-minor-version.outcome == 'success' && steps.bump-prod-minor-version.outputs.new_version) || - (steps.bump-prod-patch-version.outcome == 'success' && steps.bump-prod-patch-version.outputs.new_version) || - (steps.bump-prod-major-version.outcome == 'success' && steps.bump-prod-major-version.outputs.new_version) - }} - echo "$NEW_VERSION_NAME+$((${{steps.latest-production-version.outputs.latest_version_code}} + 1))" > ${{env.APP_VERSION_PATH}} - - - name: Copy app version to pubspec - run: cat ${{ env.APP_VERSION_PATH }} | tr -d '\n' | perl -i -pe 's/^(version:\s+)(.+)$/$1.()/e' ${{ env.PUBSPEC_PATH }} - - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: "Bump app version [no ci]" - - - uses: actions/setup-java@v4 - with: - java-version: ${{env.JAVA_VERSION}} - distribution: "zulu" - - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable - flutter-version-file: packages/uni_app/pubspec.yaml - cache: true - - - name: Download Android keystore - run: echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > /tmp/key.jks - - - name: Create key.properties - run: | - rm -f -- ${{env.PROPERTIES_PATH}} - touch ${{env.PROPERTIES_PATH}} - echo "storeFile=/tmp/key.jks" >> ${{env.PROPERTIES_PATH}} - echo "storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" >> ${{env.PROPERTIES_PATH}} - echo "keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}" >> ${{env.PROPERTIES_PATH}} - echo "keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}" >> ${{env.PROPERTIES_PATH}} - - - name: Create .env file - run: echo "${{vars.UNI_ENV_FILE}}" > ./assets/env/.env - - - name: Build Android App Bundle - run: | - flutter pub get - flutter build appbundle - - - name: Upload App Bundle - uses: actions/upload-artifact@v4 - with: - name: appbundle - if-no-files-found: error - path: packages/uni_app/build/app/outputs/bundle/release/app-release.aab - - deploy_play_store: - name: "Deploy to Google Play Store" - runs-on: ubuntu-latest - needs: [build] - steps: - - uses: actions/checkout@v4 - - name: Get App Bundle - uses: actions/download-artifact@v4 - with: - name: appbundle - - - name: Release app to beta track - if: github.ref == 'refs/heads/develop' - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJsonPlainText: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} - packageName: ${{ secrets.ANDROID_PACKAGE_NAME }} - releaseFiles: app-release.aab - whatsNewDirectory: whatsnew - track: beta - status: completed - - - name: Release app to production track - if: github.ref == 'refs/heads/master' - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJsonPlainText: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} - packageName: ${{ secrets.ANDROID_PACKAGE_NAME }} - releaseFiles: app-release.aab - whatsNewDirectory: whatsnew - track: production - status: completed - - - name: Propagate version to develop - if: github.ref == 'refs/heads/master' - run: | - echo '${{ secrets.NIAEFEUPBOT_PAT }}' | gh auth login --with-token - gh workflow run 'Deploy Action' --ref develop diff --git a/.github/workflows/deploy_beta.yaml b/.github/workflows/deploy_beta.yaml deleted file mode 100644 index c0d93fd83..000000000 --- a/.github/workflows/deploy_beta.yaml +++ /dev/null @@ -1,134 +0,0 @@ -name: "CI: Deploy Beta" - -on: - push: - branches: [develop] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - # 1. Validate Version - validate_version: - name: "Validate Version" - uses: ./.github/workflows/validate_version.yaml - with: - working-directory: ./packages/uni_app - secrets: - google_service_account_json: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} - asc_key_id: ${{ secrets.ASC_KEY_ID }} - asc_issuer_id: ${{ secrets.ASC_ISSUER_ID }} - asc_key_content: ${{ secrets.ASC_KEY_CONTENT }} - - # 2. Bump Version - bump_version: - name: "Bump Version" - needs: validate_version - runs-on: ubuntu-latest - permissions: - contents: write - defaults: - run: - working-directory: ./packages/uni_app - outputs: - commit_sha: ${{ steps.commit.outputs.COMMIT_SHA }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.NIAEFEUPBOT_PAT }} - fetch-depth: 0 - - - name: Run Bump Version Script - run: | - # Script path must now be relative to packages/uni_app - chmod +x ../../.github/scripts/bump_version.sh - ../../.github/scripts/bump_version.sh develop - - - name: Commit & Push Version Bump - id: commit - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - git add app_version.txt pubspec.yaml - - if ! git diff --staged --quiet; then - NEW_VERSION=$(cat app_version.txt) - git commit -m "chore(ci): Bump build to $NEW_VERSION [skip ci]" - git push origin HEAD:develop - echo "COMMIT_SHA=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - else - echo "No version change detected." - echo "COMMIT_SHA=${{ github.sha }}" >> $GITHUB_OUTPUT - fi - - # 3. Deploy Matrix (Android & iOS) - deploy_beta: - name: "Deploy ${{ matrix.platform }} (Beta)" - needs: bump_version - runs-on: ${{ matrix.os }} - defaults: - run: - working-directory: ./packages/uni_app - strategy: - fail-fast: false - matrix: - include: - - platform: android - os: ubuntu-latest - lane: deploy_beta - - platform: ios - os: macos-latest - lane: deploy_testflight - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - ref: ${{ needs.bump_version.outputs.commit_sha }} - - - name: Set up Java - uses: actions/setup-java@v4 - with: - distribution: "temurin" - java-version: "17" - - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable - cache: true - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - working-directory: ./packages/uni_app - - - name: Create .env file - run: echo "${{ vars.UNI_ENV_FILE }}" > ./assets/env/.env - - - name: Setup Android Keystore - if: matrix.platform == 'android' - run: | - echo "${{ secrets.UPLOAD_KEYSTORE_BASE64 }}" | base64 --decode > /tmp/upload-keystore.jks - - - name: Run Fastlane - env: - # Android Secrets - ANDROID_JSON_KEY_FILE: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} - ANDROID_KEYSTORE_FILE_PATH: /tmp/upload-keystore.jks - ANDROID_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }} - ANDROID_KEY_ALIAS: upload - ANDROID_KEY_PASSWORD: ${{ secrets.UPLOAD_KEY_PASSWORD }} - # iOS Secrets - MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} - MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} - ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} - ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} - ASC_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }} - run: | - bundle exec fastlane ${{ matrix.platform }} ${{ matrix.lane }} \ No newline at end of file diff --git a/.github/workflows/validate_version.yaml b/.github/workflows/validate_version.yaml deleted file mode 100644 index 7535fe7d7..000000000 --- a/.github/workflows/validate_version.yaml +++ /dev/null @@ -1,122 +0,0 @@ -# Validate Version Workflow -name: "Validate Version" - -on: - workflow_call: - inputs: - working-directory: - required: false - type: string - default: ./packages/uni_app - android_track: - description: "Google Play track to query (beta, production)" - required: false - type: string - default: beta - ios_channel: - description: "iOS source to query (testflight, appstore)" - required: false - type: string - default: testflight - secrets: - google_service_account_json: - description: "Google service account JSON for Play Console" - required: true - asc_json: - description: "App Store Connect JSON" - required: true - outputs: - current_version: - description: "Version read from app_version.txt" - value: ${{ jobs.validate.outputs.current_version }} - build_number: - description: "Build number parsed from version" - value: ${{ jobs.validate.outputs.build_number }} - android_store_build_number: - description: "Latest Android beta build number from Play Console" - value: ${{ jobs.validate.outputs.android_store_build_number }} - ios_store_build_number: - description: "Latest iOS build number from selected channel" - value: ${{ jobs.validate.outputs.ios_store_build_number }} - -jobs: - validate: - name: "Validate Version" - runs-on: ubuntu-latest - defaults: - run: - working-directory: ${{ inputs.working-directory }} - outputs: - current_version: ${{ steps.read_version.outputs.CURRENT_VERSION }} - build_number: ${{ steps.read_version.outputs.BUILD_NUMBER }} - android_store_build_number: ${{ steps.android_store.outputs.ANDROID_STORE_BUILD_NUMBER }} - ios_store_build_number: ${{ steps.ios_store.outputs.IOS_STORE_BUILD_NUMBER }} - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Read Current Version - id: read_version - run: | - FULL_VERSION=$(cat app_version.txt) - echo "CURRENT_VERSION=$FULL_VERSION" >> $GITHUB_OUTPUT - BUILD_NUMBER=$(echo "$FULL_VERSION" | cut -d'+' -f2) - echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_OUTPUT - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - working-directory: ${{ inputs.working-directory }} - - - name: Get Latest Android Store Version - id: android_store - env: - GOOGLE_SERVICE_ACCOUNT_JSON: ${{ secrets.google_service_account_json }} - run: | - trap 'rm -f /tmp/google_service_account.json' EXIT - echo "$GOOGLE_SERVICE_ACCOUNT_JSON" > /tmp/google_service_account.json - - cd android - - bundle exec fastlane get_version_code track:${{ inputs.android_track }} key_path:/tmp/google_service_account.json - - ANDROID_BUILD=$(cat android_version_code.txt) - echo "Final Parsed Android Build: $ANDROID_BUILD" - echo "ANDROID_STORE_BUILD_NUMBER=$ANDROID_BUILD" >> $GITHUB_OUTPUT - - - name: Get Latest iOS Store Version - id: ios_store - env: - ASC_JSON: ${{ secrets.asc_json }} - run: | - trap 'rm -f /tmp/ios_api_key.json' EXIT - echo "$ASC_JSON" > /tmp/ios_api_key.json - - cd ios - - bundle exec fastlane get_version_code channel:${{ inputs.ios_channel }} key_path:/tmp/ios_api_key.json - - IOS_BUILD=$(cat ios_build_number.txt) - echo "Final Parsed iOS Build: $IOS_BUILD" - echo "IOS_STORE_BUILD_NUMBER=$IOS_BUILD" >> $GITHUB_OUTPUT - - - name: Validate Build Numbers - run: | - CURRENT_BUILD="${{ steps.read_version.outputs.BUILD_NUMBER }}" - ANDROID_BUILD="${{ steps.android_store.outputs.ANDROID_STORE_BUILD_NUMBER }}" - IOS_BUILD="${{ steps.ios_store.outputs.IOS_STORE_BUILD_NUMBER }}" - - echo "Current build: $CURRENT_BUILD" - echo "Android store build: $ANDROID_BUILD" - echo "iOS store build: $IOS_BUILD" - - curr=${CURRENT_BUILD:-0} - and=${ANDROID_BUILD:-0} - ios=${IOS_BUILD:-0} - - if [ "$curr" -le "$and" ] || [ "$curr" -le "$ios" ]; then - echo "Error: Current build number ($curr) must be greater than both store versions (Android: $and, iOS: $ios)" - exit 1 - fi \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index e42768562..000000000 --- a/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM cirrusci/flutter:3.3.2 - -WORKDIR /app - -COPY ./app_feup/ . - -RUN flutter pub get - -CMD [ "sh", "-c" , "flutter analyze --no-pub --preamble . && flutter test"] From 5300ebebe76ef429e0f8b1897c1bed3fd7fc34ad Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:40:16 +0000 Subject: [PATCH 07/12] feat: add bump_version script for version management --- {.github/scripts => scripts}/bump_version.sh | 33 ++++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) rename {.github/scripts => scripts}/bump_version.sh (54%) mode change 100755 => 100644 diff --git a/.github/scripts/bump_version.sh b/scripts/bump_version.sh old mode 100755 new mode 100644 similarity index 54% rename from .github/scripts/bump_version.sh rename to scripts/bump_version.sh index ce6ee0603..b1d2450a1 --- a/.github/scripts/bump_version.sh +++ b/scripts/bump_version.sh @@ -1,15 +1,20 @@ #!/bin/bash set -e -# Usage: ./bump_version.sh [develop|production] [major|minor|patch] +# Usage: ./bump_version.sh [TYPE] -MODE=$1 -TYPE=${2:-patch} +PROJECT_DIR=$1 +MODE=$2 +TYPE=${3:-patch} -PUBSPEC_FILE="pubspec.yaml" -VERSION_FILE="app_version.txt" +if [ -z "$PROJECT_DIR" ]; then + echo "Error: Project directory argument missing." + exit 1 +fi + +VERSION_FILE="$PROJECT_DIR/app_version.txt" +PUBSPEC_FILE="$PROJECT_DIR/pubspec.yaml" -# 1. Read current version from app_version.txt if [ ! -f "$VERSION_FILE" ]; then echo "Error: $VERSION_FILE not found." exit 1 @@ -17,19 +22,16 @@ fi FULL_VERSION=$(cat "$VERSION_FILE") -# Validate format if [[ $FULL_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)\+([0-9]+)$ ]]; then MAJOR="${BASH_REMATCH[1]}" MINOR="${BASH_REMATCH[2]}" PATCH="${BASH_REMATCH[3]}" BUILD="${BASH_REMATCH[4]}" else - echo "Error: Invalid version format in $VERSION_FILE: $FULL_VERSION" - echo "Expected format: X.Y.Z+BUILD" + echo "Error: Invalid format $FULL_VERSION. Expected X.Y.Z+BUILD" exit 1 fi -# 2. Calculate New Version NEW_BUILD=$((BUILD + 1)) if [ "$MODE" == "production" ]; then @@ -40,18 +42,15 @@ if [ "$MODE" == "production" ]; then else PATCH=$((PATCH + 1)) fi - NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD" -else - NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD" fi -echo "Bumping version: $FULL_VERSION -> $NEW_VERSION" +NEW_VERSION="$MAJOR.$MINOR.$PATCH+$NEW_BUILD" +echo "Bumping: $FULL_VERSION -> $NEW_VERSION" -# 3. Update Files echo "$NEW_VERSION" > "$VERSION_FILE" -sed -i -E "s/^version: .*/version: $NEW_VERSION/" "$PUBSPEC_FILE" -# 4. Output for GitHub Actions +sed -i.bak -E "s/^version: .*/version: $NEW_VERSION/" "$PUBSPEC_FILE" && rm "${PUBSPEC_FILE}.bak" + if [ -n "$GITHUB_OUTPUT" ]; then echo "NEW_VERSION=$NEW_VERSION" >> "$GITHUB_OUTPUT" fi \ No newline at end of file From 31f2e729710cd4b6988ab7bf69af391fa869bb5a Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:55:29 +0000 Subject: [PATCH 08/12] feat: implement pull request checks workflow with version integrity, formatting, linting, testing and label recognition for releases --- .github/workflows/app_version_integrity.yaml | 18 --- .github/workflows/format_lint_test.yaml | 81 ----------- .github/workflows/pr-checks.yaml | 137 +++++++++++++++++++ 3 files changed, 137 insertions(+), 99 deletions(-) delete mode 100644 .github/workflows/app_version_integrity.yaml delete mode 100644 .github/workflows/format_lint_test.yaml create mode 100644 .github/workflows/pr-checks.yaml diff --git a/.github/workflows/app_version_integrity.yaml b/.github/workflows/app_version_integrity.yaml deleted file mode 100644 index 15da3831d..000000000 --- a/.github/workflows/app_version_integrity.yaml +++ /dev/null @@ -1,18 +0,0 @@ -on: pull_request - -jobs: - app_version_integrity: - name: "Version integrity" - runs-on: ubuntu-latest - env: - APP_VERSION_PATH: "packages/uni_app/app_version.txt" - steps: - - uses: actions/checkout@v4 - - - name: Fetch origin target branch - run: | - git fetch origin ${{ github.base_ref }} - - - name: Ensure app_version was not changed manually or was created just now - run: | - (! git cat-file -e FETCH_HEAD:${{ env.APP_VERSION_PATH }} && git cat-file -e HEAD:${{env.APP_VERSION_PATH}}) || git diff --quiet FETCH_HEAD:${{ env.APP_VERSION_PATH }} HEAD:${{ env.APP_VERSION_PATH }} || (echo "App version file was modified manually. Skipping deploy" && exit 1) diff --git a/.github/workflows/format_lint_test.yaml b/.github/workflows/format_lint_test.yaml deleted file mode 100644 index 21c1270b9..000000000 --- a/.github/workflows/format_lint_test.yaml +++ /dev/null @@ -1,81 +0,0 @@ -on: - pull_request: - push: - branches: [master, develop] - -env: - JAVA_VERSION: 21.x - -jobs: - format: - name: Format - runs-on: ubuntu-latest - steps: - - name: Clone repository - uses: actions/checkout@v4 - - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable - flutter-version-file: packages/uni_app/pubspec.yaml - cache: true - - - run: flutter pub get - - - run: dart format $(find . -type f -name "*.dart" -a -not -name "*.g.dart" -a -not -name "*.mocks.dart") --set-exit-if-changed - - lint: - name: Lint - runs-on: ubuntu-latest - needs: format - steps: - - name: Clone repository - uses: actions/checkout@v4 - - - uses: actions/setup-java@v4 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: zulu - - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable - flutter-version-file: packages/uni_app/pubspec.yaml - cache: true - - - run: flutter pub get - - - run: | - flutter analyze . - - test: - name: Test - runs-on: ubuntu-latest - needs: lint - defaults: - run: - working-directory: ./packages/uni_app - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - java-version: ${{ env.JAVA_VERSION }} - distribution: zulu - - - name: Set up Flutter - uses: subosito/flutter-action@v2 - with: - channel: stable - flutter-version-file: packages/uni_app/pubspec.yaml - cache: true - - - name: Test with coverage - run: flutter test --coverage - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - fail_ci_if_error: true diff --git a/.github/workflows/pr-checks.yaml b/.github/workflows/pr-checks.yaml new file mode 100644 index 000000000..daa417392 --- /dev/null +++ b/.github/workflows/pr-checks.yaml @@ -0,0 +1,137 @@ +name: Pull Request Checks + +on: + pull_request: + types: [opened, synchronize, reopened] + push: + branches: [master, develop] + +env: + JAVA_VERSION: 21.x + WORKING_DIR: packages/uni_app + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integrity_check: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate Version Integrity + run: | + FILE_PATH="${{ env.WORKING_DIR }}/app_version.txt" + git fetch origin ${{ github.base_ref }} + if ! git diff --quiet origin/${{ github.base_ref }} -- $FILE_PATH; then + echo "Error: Manual changes to $FILE_PATH are not allowed." + echo "Versioning is handled automatically by the CD pipeline." + exit 1 + fi + + format: + name: Format + runs-on: ubuntu-latest + needs: integrity_check + defaults: + run: + working-directory: ${{ env.WORKING_DIR }} + steps: + - uses: actions/checkout@v4 + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version-file: ${{ env.WORKING_DIR }}/pubspec.yaml + cache: true + + - name: Install Dependencies + run: flutter pub get + + - name: Check Formatting + run: | + dart format $(find . -type f -name "*.dart" -a -not -name "*.g.dart" -a -not -name "*.mocks.dart") --set-exit-if-changed + + lint: + name: Lint + runs-on: ubuntu-latest + needs: format + defaults: + run: + working-directory: ${{ env.WORKING_DIR }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'zulu' + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version-file: ${{ env.WORKING_DIR }}/pubspec.yaml + cache: true + + - name: Install Dependencies + run: flutter pub get + + - name: Analyze + run: flutter analyze . + + test: + name: ๐Ÿงช Test + runs-on: ubuntu-latest + needs: lint + defaults: + run: + working-directory: ${{ env.WORKING_DIR }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'zulu' + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version-file: ${{ env.WORKING_DIR }}/pubspec.yaml + cache: true + + - name: Install Dependencies + run: flutter pub get + + - name: Run Tests + run: flutter test --coverage + + - name: Upload Coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true + + label_check: + if: github.event_name == 'pull_request' && github.base_ref == 'main' + runs-on: ubuntu-latest + steps: + - name: Verify Release Label + run: | + labels="${{ join(github.event.pull_request.labels.*.name, ',') }}" + count=0 + if [[ $labels == *"release:major"* ]]; then count=$((count+1)); fi + if [[ $labels == *"release:minor"* ]]; then count=$((count+1)); fi + if [[ $labels == *"release:patch"* ]]; then count=$((count+1)); fi + + if [ $count -ne 1 ]; then + echo "Error: PR to main must have exactly one label: release:major, release:minor, or release:patch" + exit 1 + fi From e6e6e9124cb005ebd526ad3618c0cc9ede3ac56b Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:58:21 +0000 Subject: [PATCH 09/12] fix: update names of steps --- .github/workflows/pr-checks.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-checks.yaml b/.github/workflows/pr-checks.yaml index daa417392..ba62c86f3 100644 --- a/.github/workflows/pr-checks.yaml +++ b/.github/workflows/pr-checks.yaml @@ -16,6 +16,7 @@ concurrency: jobs: integrity_check: + name: Version Integrity Check if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: @@ -86,7 +87,7 @@ jobs: run: flutter analyze . test: - name: ๐Ÿงช Test + name: Test runs-on: ubuntu-latest needs: lint defaults: @@ -120,6 +121,7 @@ jobs: fail_ci_if_error: true label_check: + name: Release Label Check if: github.event_name == 'pull_request' && github.base_ref == 'main' runs-on: ubuntu-latest steps: From d71a5160dbfbdaea20b368141f529f4102c8f55f Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:19:18 +0000 Subject: [PATCH 10/12] feat: add CI workflows for beta, production, and pull request checks --- .github/workflows/beta.yaml | 40 ++++++ .../workflows/{pr-checks.yaml => checks.yaml} | 6 +- .github/workflows/deploy.yaml | 136 ++++++++++++++++++ .github/workflows/production.yaml | 89 ++++++++++++ 4 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/beta.yaml rename .github/workflows/{pr-checks.yaml => checks.yaml} (96%) create mode 100644 .github/workflows/deploy.yaml create mode 100644 .github/workflows/production.yaml diff --git a/.github/workflows/beta.yaml b/.github/workflows/beta.yaml new file mode 100644 index 000000000..9791031ec --- /dev/null +++ b/.github/workflows/beta.yaml @@ -0,0 +1,40 @@ +name: Beta + +on: + push: + branches: [ develop ] + +concurrency: + group: beta + cancel-in-progress: false + +jobs: + bump: + name: Bump Version + runs-on: ubuntu-latest + outputs: + new_version: ${{ steps.bump.outputs.NEW_VERSION }} + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.NIAEFEUPBOT_PAT }} + + - name: Bump Version + id: bump + run: ./scripts/bump_version.sh packages/uni_app develop + + - name: Commit & Push + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: "chore: bump build number to ${{ steps.bump.outputs.NEW_VERSION }} [skip ci]" + branch: develop + file_pattern: 'packages/uni_app/app_version.txt packages/uni_app/pubspec.yaml' + + deploy_beta: + name: Deploy to Beta + needs: bump + uses: ./.github/workflows/deploy.yml + with: + environment: staging + lane: beta + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/pr-checks.yaml b/.github/workflows/checks.yaml similarity index 96% rename from .github/workflows/pr-checks.yaml rename to .github/workflows/checks.yaml index ba62c86f3..a98d89be4 100644 --- a/.github/workflows/pr-checks.yaml +++ b/.github/workflows/checks.yaml @@ -2,7 +2,7 @@ name: Pull Request Checks on: pull_request: - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, labeled, unlabeled] push: branches: [master, develop] @@ -61,7 +61,7 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - needs: format + needs: integrity_check defaults: run: working-directory: ${{ env.WORKING_DIR }} @@ -89,7 +89,7 @@ jobs: test: name: Test runs-on: ubuntu-latest - needs: lint + needs: integrity_check defaults: run: working-directory: ${{ env.WORKING_DIR }} diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 000000000..b8f3d005d --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,136 @@ +name: Deploy + +on: + workflow_call: + inputs: + environment: + required: true + type: string + lane: + required: true + type: string + secrets: + NIAEFEUPBOT_PAT: + required: true + + IOS_P12_BASE64: + required: true + IOS_P12_PASSWORD: + required: true + IOS_PROVISIONING_PROFILE_BASE64: + required: true + IOS_BUNDLE_ID: + required: true + APP_STORE_CONNECT_API_KEY_KEY_ID: + required: true + APP_STORE_CONNECT_API_KEY_ISSUER_ID: + required: true + APP_STORE_CONNECT_API_KEY_CONTENT: + required: true + + GOOGLE_SERVICE_ACCOUNT_JSON: + required: true + ANDROID_KEYSTORE_BASE64: + required: true + ANDROID_KEYSTORE_PASSWORD: + required: true + ANDROID_KEY_ALIAS: + required: true + ANDROID_KEY_PASSWORD: + required: true + +jobs: + deploy: + name: Deploy to ${{ inputs.lane }} + runs-on: macos-latest + environment: ${{ inputs.environment }} + timeout-minutes: 45 + defaults: + run: + working-directory: ./packages/uni_app + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '17' + cache: 'gradle' + + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true + working-directory: ./packages/uni_app + + - name: Dependencies + run: flutter pub get + + - name: Cache iOS Pods + uses: actions/cache@v3 + with: + path: packages/uni_app/ios/Pods + key: ${{ runner.os }}-pods-${{ hashFiles('packages/uni_app/ios/Podfile.lock') }} + restore-keys: ${{ runner.os }}-pods- + + - name: Setup Android Signing + env: + ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} + ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + GOOGLE_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }} + run: | + echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > android/app/upload-keystore.jks + + echo "storePassword=$ANDROID_KEYSTORE_PASSWORD" > android/key.properties + echo "keyPassword=$ANDROID_KEY_PASSWORD" >> android/key.properties + echo "keyAlias=$ANDROID_KEY_ALIAS" >> android/key.properties + echo "storeFile=upload-keystore.jks" >> android/key.properties + + echo "$GOOGLE_SERVICE_ACCOUNT_JSON" > android/fastlane/api.json + + - name: Setup iOS Signing + env: + P12_BASE64: ${{ secrets.IOS_P12_BASE64 }} + PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }} + run: | + mkdir -p ios/certs + echo "$P12_BASE64" | base64 --decode > ios/certs/distribution.p12 + echo "$PROFILE_BASE64" | base64 --decode > ios/certs/app.mobileprovision + + - name: Deploy + env: + IOS_P12_PATH: "certs/distribution.p12" + IOS_P12_PASSWORD: ${{ secrets.IOS_P12_PASSWORD }} + IOS_PROFILE_PATH: "certs/app.mobileprovision" + APP_BUNDLE_ID: ${{ secrets.IOS_BUNDLE_ID }} + + ASC_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }} + ASC_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} + ASC_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }} + run: | + cd android + bundle exec fastlane ${{ inputs.lane }} & + PID_ANDROID=$! + + cd ../ios + bundle exec fastlane ${{ inputs.lane }} & + PID_IOS=$! + + wait $PID_ANDROID + ANDROID_STATUS=$? + + wait $PID_IOS + IOS_STATUS=$? + + if [ $ANDROID_STATUS -ne 0 ] || [ $IOS_STATUS -ne 0 ]; then + echo "One or more deployments failed." + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml new file mode 100644 index 000000000..9e48a78db --- /dev/null +++ b/.github/workflows/production.yaml @@ -0,0 +1,89 @@ +name: Production + +on: + pull_request: + branches: [ main ] + types: [closed] + +concurrency: + group: production-${{ github.ref }} + cancel-in-progress: false + +jobs: + bump: + name: Bump Version + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: main + token: ${{ secrets.NIAEFEUPBOT_PAT }} + + - name: Determine Release Type + id: type + run: | + labels="${{ join(github.event.pull_request.labels.*.name, ',') }}" + if [[ $labels == *"release:major"* ]]; then echo "level=major" >> $GITHUB_OUTPUT; fi + if [[ $labels == *"release:minor"* ]]; then echo "level=minor" >> $GITHUB_OUTPUT; fi + if [[ $labels == *"release:patch"* ]]; then echo "level=patch" >> $GITHUB_OUTPUT; fi + + - name: Bump Version + id: bump + run: ./scripts/bump_version.sh packages/uni_app production ${{ steps.type.outputs.level }} + + - name: Commit & Push + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: "chore(release): ${{ steps.bump.outputs.NEW_VERSION }} [skip ci]" + branch: main + tagging_message: "v${{ steps.bump.outputs.NEW_VERSION }}" + file_pattern: 'packages/uni_app/app_version.txt packages/uni_app/pubspec.yaml' + + + deploy_prod: + name: Deploy to Production + needs: bump + uses: ./.github/workflows/deploy.yml + with: + environment: production + lane: production + secrets: inherit + + sync_develop: + name: Sync develop with main + needs: deploy_prod + runs-on: ubuntu-latest + concurrency: + group: production-sync-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Rebase develop onto main + run: | + set -euo pipefail + + git fetch origin + + git checkout develop + git reset --hard origin/develop + + git rebase origin/main || { + echo "Rebase conflict detected!" + echo "Manual resolution required." + exit 1 + } + + git push --force-with-lease origin develop + + echo "โœ… Develop successfully rebased onto main" From a51fb34ac3d889c26a2e97dbbfff36442c2565cf Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:25:44 +0000 Subject: [PATCH 11/12] feat: updated fastlane lanes --- packages/uni_app/android/fastlane/Appfile | 1 + packages/uni_app/android/fastlane/Fastfile | 35 ++++++--- packages/uni_app/ios/fastlane/Appfile | 3 +- packages/uni_app/ios/fastlane/Fastfile | 84 ++++++++++++++++++---- 4 files changed, 99 insertions(+), 24 deletions(-) diff --git a/packages/uni_app/android/fastlane/Appfile b/packages/uni_app/android/fastlane/Appfile index dbb9b4372..ba572264c 100644 --- a/packages/uni_app/android/fastlane/Appfile +++ b/packages/uni_app/android/fastlane/Appfile @@ -1 +1,2 @@ +json_key_file("fastlane/api.json") package_name("pt.up.fe.ni.uni") \ No newline at end of file diff --git a/packages/uni_app/android/fastlane/Fastfile b/packages/uni_app/android/fastlane/Fastfile index 9e6e0fe1c..b861be943 100644 --- a/packages/uni_app/android/fastlane/Fastfile +++ b/packages/uni_app/android/fastlane/Fastfile @@ -1,14 +1,33 @@ default_platform(:android) platform :android do - desc "Get latest version code from Google Play" - lane :get_version_code do |options| - codes = google_play_track_version_codes( - track: options[:track], - json_key: options[:key_path] + + desc "Beta Deployment (Internal Track)" + lane :beta do + Dir.chdir("..") do + sh "flutter build appbundle --release" + end + + upload_to_play_store( + track: 'internal', + release_status: 'draft', + aab: '../build/app/outputs/bundle/release/app-release.aab', + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true ) - latest_code = codes.max || 0 + end + + desc "Production Deployment" + lane :production do + Dir.chdir("..") do + sh "flutter build appbundle --release" + end - File.write("../android_version_code.txt", latest_code.to_s) + upload_to_play_store( + track: 'production', + release_status: 'draft', + aab: '../build/app/outputs/bundle/release/app-release.aab' + ) end -end +end \ No newline at end of file diff --git a/packages/uni_app/ios/fastlane/Appfile b/packages/uni_app/ios/fastlane/Appfile index 6587469e0..fd20debe6 100644 --- a/packages/uni_app/ios/fastlane/Appfile +++ b/packages/uni_app/ios/fastlane/Appfile @@ -1 +1,2 @@ -app_identifier("pt.up.fe.ni.uni") \ No newline at end of file +app_identifier("pt.up.fe.ni.uni") +apple_id("ni@aefeup.pt") \ No newline at end of file diff --git a/packages/uni_app/ios/fastlane/Fastfile b/packages/uni_app/ios/fastlane/Fastfile index 4df8bf71b..d63056e48 100644 --- a/packages/uni_app/ios/fastlane/Fastfile +++ b/packages/uni_app/ios/fastlane/Fastfile @@ -1,22 +1,76 @@ default_platform(:ios) platform :ios do - desc "Get latest build number from TestFlight or App Store" - lane :get_version_code do |options| - build_number = 0 - if options[:channel] == "appstore" - build_number = app_store_build_number( - api_key_path: options[:key_path], - live: true - ) - else - build_number = latest_testflight_build_number( - api_key_path: options[:key_path] - ) + before_all do + if is_ci + setup_manual_signing end + end + + desc "Setup Manual Signing (CI Only)" + lane :setup_manual_signing do + create_keychain( + name: "ci_keychain", + password: "temp_password", + default_keychain: true, + unlock: true, + timeout: 3600, + lock_when_sleeps: false + ) + + import_certificate( + certificate_path: ENV["IOS_P12_PATH"], + certificate_password: ENV["IOS_P12_PASSWORD"], + keychain_name: "ci_keychain", + keychain_password: "temp_password" + ) + + install_provisioning_profile( + profile_path: ENV["IOS_PROFILE_PATH"] + ) + + update_code_signing_settings( + use_automatic_signing: false, + targets: ["Runner"], + path: "Runner.xcodeproj", + bundle_identifier: ENV["APP_BUNDLE_ID"], + profile_name: "AppStore Profile", + code_sign_identity: "Apple Distribution" + ) + end + + desc "Beta Deployment (TestFlight)" + lane :beta do + + build_app( + workspace: "Runner.xcworkspace", + scheme: "Runner", + export_method: "app-store", + clean: true + ) + + upload_to_testflight( + skip_waiting_for_build_processing: true, + skip_submission: true + ) + end + + desc "Production Deployment (App Store)" + lane :production do - build_number = (build_number || 0).to_i + build_app( + workspace: "Runner.xcworkspace", + scheme: "Runner", + export_method: "app-store", + clean: true + ) - File.write("../ios_build_number.txt", build_number.to_s) + upload_to_app_store( + force: true, + submit_for_review: false, + reject_if_possible: true, + skip_metadata: false, + skip_screenshots: false + ) end -end +end \ No newline at end of file From bfe33aa59b183e7aff139110f922216feaa6fb2e Mon Sep 17 00:00:00 2001 From: Pedro Monteiro <83165668+pedroafmonteiro@users.noreply.github.com> Date: Sat, 14 Feb 2026 18:31:18 +0000 Subject: [PATCH 12/12] fix: update references from 'main' to 'master' in CI workflows --- .github/workflows/checks.yaml | 4 ++-- .github/workflows/production.yaml | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index a98d89be4..0b893fee2 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -122,7 +122,7 @@ jobs: label_check: name: Release Label Check - if: github.event_name == 'pull_request' && github.base_ref == 'main' + if: github.event_name == 'pull_request' && github.base_ref == 'master' runs-on: ubuntu-latest steps: - name: Verify Release Label @@ -134,6 +134,6 @@ jobs: if [[ $labels == *"release:patch"* ]]; then count=$((count+1)); fi if [ $count -ne 1 ]; then - echo "Error: PR to main must have exactly one label: release:major, release:minor, or release:patch" + echo "Error: PR to master must have exactly one label: release:major, release:minor, or release:patch" exit 1 fi diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml index 9e48a78db..dadfcb6f8 100644 --- a/.github/workflows/production.yaml +++ b/.github/workflows/production.yaml @@ -2,7 +2,7 @@ name: Production on: pull_request: - branches: [ main ] + branches: [ master ] types: [closed] concurrency: @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: main + ref: master token: ${{ secrets.NIAEFEUPBOT_PAT }} - name: Determine Release Type @@ -36,7 +36,7 @@ jobs: uses: stefanzweifel/git-auto-commit-action@v4 with: commit_message: "chore(release): ${{ steps.bump.outputs.NEW_VERSION }} [skip ci]" - branch: main + branch: master tagging_message: "v${{ steps.bump.outputs.NEW_VERSION }}" file_pattern: 'packages/uni_app/app_version.txt packages/uni_app/pubspec.yaml' @@ -51,7 +51,7 @@ jobs: secrets: inherit sync_develop: - name: Sync develop with main + name: Sync develop with master needs: deploy_prod runs-on: ubuntu-latest concurrency: @@ -69,7 +69,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Rebase develop onto main + - name: Rebase develop onto master run: | set -euo pipefail @@ -78,7 +78,7 @@ jobs: git checkout develop git reset --hard origin/develop - git rebase origin/main || { + git rebase origin/master || { echo "Rebase conflict detected!" echo "Manual resolution required." exit 1 @@ -86,4 +86,4 @@ jobs: git push --force-with-lease origin develop - echo "โœ… Develop successfully rebased onto main" + echo "โœ… Develop successfully rebased onto master"