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/.github/workflows/deploy_beta.yaml b/.github/workflows/deploy_beta.yaml new file mode 100644 index 000000000..c0d93fd83 --- /dev/null +++ b/.github/workflows/deploy_beta.yaml @@ -0,0 +1,134 @@ +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 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 diff --git a/packages/uni_app/app_version.txt b/packages/uni_app/app_version.txt index 047500185..83d38eff7 100644 --- a/packages/uni_app/app_version.txt +++ b/packages/uni_app/app_version.txt @@ -1 +1 @@ -2.2.0-beta.27+435 +2.2.0+435 \ No newline at end of file