latest v1.2.5 #18 #23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Profile Release | |
| on: | |
| push: | |
| branches: | |
| - 'app/*' | |
| # Manual workflow dispatch for individual profile testing | |
| workflow_dispatch: | |
| inputs: | |
| profile: | |
| description: 'Profile to build and deploy' | |
| required: true | |
| type: string | |
| build_type: | |
| description: 'Build type' | |
| required: true | |
| default: 'debug' | |
| type: choice | |
| options: | |
| - debug | |
| - release | |
| deploy: | |
| description: 'Deploy to app stores' | |
| required: true | |
| default: false | |
| type: boolean | |
| release_track: | |
| description: 'Release track for deployment' | |
| required: false | |
| default: 'internal' | |
| type: choice | |
| options: | |
| - internal | |
| - alpha | |
| - beta | |
| - production | |
| version: | |
| description: 'Version Code (optional override)' | |
| required: false | |
| type: string | |
| build_number: | |
| description: 'Build Number (optional override)' | |
| required: false | |
| type: string | |
| env: | |
| S3_BUCKET: flb-storefront-app-profiles | |
| NODE_VERSION: '18' | |
| JAVA_VERSION: '21' | |
| FALLBACK_VERSION_NAME: '2.0.9' | |
| FALLBACK_BUILD_NUMBER: '16' | |
| jobs: | |
| # Detect profile and release configuration | |
| detect-release: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| profile: ${{ steps.detect.outputs.profile }} | |
| build_type: ${{ steps.detect.outputs.build_type }} | |
| deploy: ${{ steps.detect.outputs.deploy }} | |
| release_track: ${{ steps.detect.outputs.release_track }} | |
| is_release: ${{ steps.detect.outputs.is_release }} | |
| version: ${{ steps.detect.outputs.version }} | |
| build_number: ${{ steps.detect.outputs.build_number }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 10 | |
| - name: Detect profile and configuration | |
| id: detect | |
| run: | | |
| if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then | |
| # Manual dispatch | |
| echo "profile=${{ github.event.inputs.profile }}" >> $GITHUB_OUTPUT | |
| echo "build_type=${{ github.event.inputs.build_type }}" >> $GITHUB_OUTPUT | |
| echo "deploy=${{ github.event.inputs.deploy }}" >> $GITHUB_OUTPUT | |
| echo "release_track=${{ github.event.inputs.release_track }}" >> $GITHUB_OUTPUT | |
| # Version detection hierarchy for manual dispatch: | |
| # 1. Use latest Git tag for version name | |
| LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v${FALLBACK_VERSION_NAME}") | |
| echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT | |
| # 2. Build number detection hierarchy (manual dispatch) | |
| # Keep inputs independent: version = semver string, build_number = integer | |
| if [ -n "${{ github.event.inputs.version }}" ] && [ "${{ github.event.inputs.version }}" != "" ]; then | |
| echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT | |
| fi | |
| if [ -n "${{ github.event.inputs.build_number }}" ] && [ "${{ github.event.inputs.build_number }}" != "" ]; then | |
| echo "build_number=${{ github.event.inputs.build_number }}" >> $GITHUB_OUTPUT | |
| fi | |
| # If neither provided, try to derive both from tag later | |
| if [ -z "${{ github.event.inputs.version }}" ] && [ -z "${{ github.event.inputs.build_number }}" ]; then | |
| TAG_COMMIT_MSG=$(git tag -l --format='%(contents)' "$LATEST_TAG" 2>/dev/null || echo "") | |
| # Accept both "[Build 16]" and "[Build No 16]" | |
| BUILD_FROM_TAG=$(echo "$TAG_COMMIT_MSG" | grep -o '\[Build\( No\)\? [0-9]\+\]' | grep -o '[0-9]\+' || echo "") | |
| if [ -n "$BUILD_FROM_TAG" ]; then | |
| echo "build_number=$BUILD_FROM_TAG" >> $GITHUB_OUTPUT | |
| else | |
| echo "build_number=" >> $GITHUB_OUTPUT | |
| fi | |
| # version (semver) continues to come from LATEST_TAG as version name | |
| fi | |
| echo "is_release=${{ github.event.inputs.build_type == 'release' }}" >> $GITHUB_OUTPUT | |
| else | |
| # Extract profile from branch name (app/fleetbase -> fleetbase) | |
| PROFILE=${GITHUB_REF#refs/heads/app/} | |
| echo "profile=$PROFILE" >> $GITHUB_OUTPUT | |
| # Check if this is a release merge by examining commit message | |
| COMMIT_MSG=$(git log -1 --pretty=%B) | |
| echo "Latest commit message: $COMMIT_MSG" | |
| if [[ "$COMMIT_MSG" == *"🚀 Release v"* ]] || [[ "$COMMIT_MSG" =~ \[[Vv][0-9]+\.[0-9]+\.[0-9]+([.-]?(alpha|beta|rc)[0-9]*)?\] ]]; then | |
| # This is a release merge from tag dispatcher | |
| echo "🚀 Release merge detected" | |
| # Extract version from commit message | |
| VERSION=$(echo "$COMMIT_MSG" | grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+[^[:space:]]*' | head -1) | |
| if [ -z "$VERSION" ]; then | |
| VERSION=$(echo "$COMMIT_MSG" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+[^[:space:]]*' | head -1) | |
| VERSION="v$VERSION" | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "build_type=release" >> $GITHUB_OUTPUT | |
| echo "deploy=true" >> $GITHUB_OUTPUT | |
| echo "is_release=true" >> $GITHUB_OUTPUT | |
| echo "build_number=" >> $GITHUB_OUTPUT | |
| # Determine release track based on version | |
| if [[ "$VERSION" == *"alpha"* ]] || [[ "$VERSION" == *"beta"* ]] || [[ "$VERSION" == *"rc"* ]]; then | |
| echo "release_track=internal" >> $GITHUB_OUTPUT | |
| else | |
| echo "release_track=production" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| # Regular development push | |
| echo "🔧 Development push detected" | |
| echo "version=v${FALLBACK_VERSION_NAME}-dev" >> $GITHUB_OUTPUT | |
| echo "build_type=debug" >> $GITHUB_OUTPUT | |
| echo "deploy=false" >> $GITHUB_OUTPUT | |
| echo "release_track=internal" >> $GITHUB_OUTPUT | |
| echo "is_release=false" >> $GITHUB_OUTPUT | |
| echo "build_number=" >> $GITHUB_OUTPUT | |
| fi | |
| fi | |
| echo "📋 Configuration:" | |
| echo " Profile: $(cat $GITHUB_OUTPUT | grep 'profile=' | cut -d'=' -f2)" | |
| echo " Version: $(cat $GITHUB_OUTPUT | grep 'version=' | cut -d'=' -f2)" | |
| echo " Build Type: $(cat $GITHUB_OUTPUT | grep 'build_type=' | cut -d'=' -f2)" | |
| echo " Deploy: $(cat $GITHUB_OUTPUT | grep 'deploy=' | cut -d'=' -f2)" | |
| echo " Release Track: $(cat $GITHUB_OUTPUT | grep 'release_track=' | cut -d'=' -f2)" | |
| echo " Fallback Version Name: ${FALLBACK_VERSION_NAME}" | |
| echo " Fallback Build Number: ${FALLBACK_BUILD_NUMBER}" | |
| # Setup base dependencies | |
| setup: | |
| runs-on: ubuntu-latest | |
| needs: detect-release | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'yarn' | |
| - name: Enable Corepack and install dependencies | |
| run: | | |
| corepack enable | |
| corepack prepare [email protected] --activate | |
| yarn install --immutable | |
| # Resolve versions once to avoid races (parity across platforms) | |
| resolve-versions: | |
| runs-on: ubuntu-latest | |
| needs: detect-release | |
| outputs: | |
| version_name: ${{ steps.outvars.outputs.version_name }} | |
| build_number: ${{ steps.outvars.outputs.build_number }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 # need tags | |
| fetch-tags: true | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| aws-region: ${{ secrets.AWS_REGION || 'ap-southeast-1' }} | |
| - name: Download profile configuration | |
| run: | | |
| echo "📦 Downloading profile: ${{ needs.detect-release.outputs.profile }}" | |
| aws s3 sync s3://${{ env.S3_BUCKET }}/${{ needs.detect-release.outputs.profile }}/ ./profile-config/ | |
| test -f ./profile-config/.env || { echo "❌ Missing ./profile-config/.env"; exit 1; } | |
| echo "✅ Profile config ready" | |
| - name: Resolve cross-platform versions | |
| id: resolve | |
| run: | | |
| echo "🔧 Resolving versions with parity (iOS/Android)" | |
| # ---- STEP 1: VERSION_NAME (semver) ---- | |
| VERSION="${{ needs.detect-release.outputs.version }}" | |
| if [[ "$VERSION" == v* ]]; then VERSION_NAME=${VERSION#v}; else VERSION_NAME="$VERSION"; fi | |
| # .env fallback if dev/empty; last resort default | |
| if [ "$VERSION_NAME" = "dev" ] || [ -z "$VERSION_NAME" ]; then | |
| ENV_VER=$(grep -E '^ANDROID_VERSION_NAME=' ./profile-config/.env | cut -d'=' -f2 || true) | |
| if [ -n "$ENV_VER" ]; then | |
| VERSION_NAME="$ENV_VER" | |
| echo "✅ Version fallback from .env: $VERSION_NAME" | |
| else | |
| VERSION_NAME="${FALLBACK_VERSION_NAME}" | |
| echo "⚠️ Version fallback defaulted to: $VERSION_NAME" | |
| fi | |
| fi | |
| # manual override already reflected in detect-release.outputs.version | |
| # ---- STEP 2: BUILD_NUMBER (strict int, NO AUTOINCREMENT) ---- | |
| if [ -n "${{ needs.detect-release.outputs.build_number }}" ] && [ "${{ needs.detect-release.outputs.build_number }}" != "" ]; then | |
| BUILD_NUMBER="${{ needs.detect-release.outputs.build_number }}" | |
| echo "✅ Build number override (manual): $BUILD_NUMBER" | |
| else | |
| TAG="${{ needs.detect-release.outputs.version }}" | |
| TAG_MSG=$(git tag -l --format='%(contents)' "$TAG" 2>/dev/null || echo "") | |
| BUILD_FROM_TAG=$(echo "$TAG_MSG" | grep -o '\[Build\( No\)\? [0-9]\+\]' | grep -o '[0-9]\+' || echo "") | |
| if [ -n "$BUILD_FROM_TAG" ]; then | |
| BUILD_NUMBER="$BUILD_FROM_TAG" | |
| echo "✅ Build number from tag message: $BUILD_FROM_TAG" | |
| else | |
| # Use value from profile .env without increment | |
| CUR=$(grep -E '^ANDROID_VERSION_CODE=' ./profile-config/.env | cut -d'=' -f2 2>/dev/null || echo "") | |
| if [[ "$CUR" =~ ^[0-9]+$ ]]; then | |
| BUILD_NUMBER="$CUR" | |
| echo "ℹ️ Using build number from ANDROID_VERSION_CODE in .env: $BUILD_NUMBER" | |
| else | |
| BUILD_NUMBER="${FALLBACK_BUILD_NUMBER:-1}" | |
| echo "⚠️ No valid build number from inputs, tag, or .env; falling back to global: $BUILD_NUMBER" | |
| fi | |
| fi | |
| fi | |
| # Final validation | |
| if ! [[ "$BUILD_NUMBER" =~ ^[0-9]+$ ]]; then | |
| echo "❌ ERROR: BUILD_NUMBER not integer: '$BUILD_NUMBER'" | |
| exit 1 | |
| fi | |
| echo "🎯 Final parity:" | |
| echo " iOS_VERSION / ANDROID_VERSION_NAME = $VERSION_NAME" | |
| echo " IOS_BUILD_NUMBER / ANDROID_VERSION_CODE = $BUILD_NUMBER" | |
| # Update profile-config/.env (parity) | |
| grep -q '^ANDROID_VERSION_NAME=' ./profile-config/.env && \ | |
| sed -i "s/^ANDROID_VERSION_NAME=.*/ANDROID_VERSION_NAME=$VERSION_NAME/" ./profile-config/.env || \ | |
| echo "ANDROID_VERSION_NAME=$VERSION_NAME" >> ./profile-config/.env | |
| grep -q '^ANDROID_VERSION_CODE=' ./profile-config/.env && \ | |
| sed -i "s/^ANDROID_VERSION_CODE=.*/ANDROID_VERSION_CODE=$BUILD_NUMBER/" ./profile-config/.env || \ | |
| echo "ANDROID_VERSION_CODE=$BUILD_NUMBER" >> ./profile-config/.env | |
| grep -q '^IOS_VERSION=' ./profile-config/.env && \ | |
| sed -i "s/^IOS_VERSION=.*/IOS_VERSION=$VERSION_NAME/" ./profile-config/.env || \ | |
| echo "IOS_VERSION=$VERSION_NAME" >> ./profile-config/.env | |
| grep -q '^IOS_BUILD_NUMBER=' ./profile-config/.env && \ | |
| sed -i "s/^IOS_BUILD_NUMBER=.*/IOS_BUILD_NUMBER=$BUILD_NUMBER/" ./profile-config/.env || \ | |
| echo "IOS_BUILD_NUMBER=$BUILD_NUMBER" >> ./profile-config/.env | |
| # SECURITY FIX: Create a separate versions file with only version info | |
| # This prevents sensitive .env data from being exposed in logs | |
| printf "IOS_VERSION=%s\n" "$VERSION_NAME" > versions.env | |
| printf "IOS_BUILD_NUMBER=%s\n" "$BUILD_NUMBER" >> versions.env | |
| printf "ANDROID_VERSION_NAME=%s\n" "$VERSION_NAME" >> versions.env | |
| printf "ANDROID_VERSION_CODE=%s\n" "$BUILD_NUMBER" >> versions.env | |
| - name: Upload resolved versions | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: versions | |
| path: versions.env | |
| - name: Set outputs | |
| id: outvars | |
| run: | | |
| echo "version_name=$(grep '^ANDROID_VERSION_NAME=' versions.env | cut -d'=' -f2)" >> "$GITHUB_OUTPUT" | |
| echo "build_number=$(grep '^ANDROID_VERSION_CODE=' versions.env | cut -d'=' -f2)" >> "$GITHUB_OUTPUT" | |
| # Android build and deployment | |
| build-android: | |
| runs-on: ubuntu-latest | |
| needs: [detect-release, setup, resolve-versions] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'yarn' | |
| - name: Enable Corepack and install dependencies | |
| run: | | |
| corepack enable | |
| corepack prepare [email protected] --activate | |
| yarn install --immutable | |
| - name: Setup JDK | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: ${{ env.JAVA_VERSION }} | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| aws-region: ${{ secrets.AWS_REGION || 'ap-southeast-1' }} | |
| - name: Download profile configuration | |
| run: | | |
| echo "📦 Downloading profile: ${{ needs.detect-release.outputs.profile }}" | |
| aws s3 sync s3://${{ env.S3_BUCKET }}/${{ needs.detect-release.outputs.profile }}/ ./profile-config/ | |
| # Validate required files | |
| required_files=( | |
| "./profile-config/.env" | |
| "./profile-config/google-services.json" | |
| "./profile-config/play-store-credentials.json" | |
| "./profile-config/app-icon.png" | |
| "./profile-config/splash-screen.png" | |
| "./profile-config/android-Fastfile" | |
| "./profile-config/android-Appfile" | |
| "./profile-config/appstore-api-key.p8" | |
| ) | |
| # Add release.keystore for release builds | |
| if [ "${{ needs.detect-release.outputs.build_type }}" == "release" ]; then | |
| required_files+=("./profile-config/release.keystore") | |
| fi | |
| for file in "${required_files[@]}"; do | |
| if [ ! -f "$file" ]; then | |
| echo "❌ Missing required file: $file" | |
| exit 1 | |
| fi | |
| done | |
| echo "✅ All required files found for profile: ${{ needs.detect-release.outputs.profile }}" | |
| - name: Download resolved versions | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: versions | |
| path: . | |
| - name: Source environment | |
| run: | | |
| echo "🔧 Setting up profile environment securely" | |
| # Load .env variables into process environment WITHOUT exposing in logs | |
| set -a | |
| source ./profile-config/.env | |
| set +a | |
| # Load version overrides from secure artifact | |
| source ./versions.env | |
| echo "Android building: $ANDROID_VERSION_NAME ($ANDROID_VERSION_CODE)" | |
| # Export versions | |
| echo "ANDROID_VERSION_NAME=$ANDROID_VERSION_NAME" >> $GITHUB_ENV | |
| echo "ANDROID_VERSION_CODE=$ANDROID_VERSION_CODE" >> $GITHUB_ENV | |
| - name: Setup profile environment | |
| run: | | |
| echo "🔧 Setting up profile environment" | |
| # Copy configuration files to correct locations | |
| cp ./profile-config/.env ./.env | |
| cp ./profile-config/google-services.json ./android/app/google-services.json | |
| cp ./profile-config/play-store-credentials.json ./android/app/play-store-credentials.json | |
| cp ./profile-config/app-icon.png ./assets/app-icon.png | |
| cp ./profile-config/splash-screen.png ./assets/splash-screen.png | |
| # Copy release keystore for release builds | |
| if [ "${{ needs.detect-release.outputs.build_type }}" == "release" ]; then | |
| cp ./profile-config/release.keystore ./android/app/release.keystore | |
| fi | |
| # Setup Fastlane files | |
| mkdir -p android/fastlane | |
| cp ./profile-config/android-Fastfile android/fastlane/Fastfile | |
| cp ./profile-config/android-Appfile android/fastlane/Appfile | |
| echo "✅ Profile environment setup completed for: ${{ needs.detect-release.outputs.profile }}" | |
| echo "📱 Version: $ANDROID_VERSION_NAME ($ANDROID_VERSION_CODE)" | |
| - name: Install ImageMagick & Ninja (Ubuntu) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y imagemagick ninja-build | |
| - name: Ensure Android SDK/NDK/CMake toolchain | |
| shell: bash | |
| env: | |
| ANDROID_SDK_ROOT: /usr/local/lib/android/sdk | |
| run: | | |
| set -e | |
| # Find a working sdkmanager (latest if present, else first found) | |
| if [ -x "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" ]; then | |
| SDKMGR="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" | |
| else | |
| SDKMGR=$(ls -1d "$ANDROID_SDK_ROOT/cmdline-tools"/*/bin/sdkmanager 2>/dev/null | head -n1 || true) | |
| if [ -z "$SDKMGR" ]; then | |
| echo "No cmdline-tools found; installing 'cmdline-tools;latest' with the preinstalled sdkmanager..." | |
| # Use the prebundled sdkmanager path on GH runners | |
| SDKMGR="$ANDROID_SDK_ROOT/cmdline-tools/bin/sdkmanager" | |
| "$SDKMGR" "cmdline-tools;latest" | |
| SDKMGR="$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager" | |
| fi | |
| fi | |
| echo "Using sdkmanager at: $SDKMGR" | |
| # Accept licenses; don't fail on SIGPIPE from 'yes' | |
| yes | "$SDKMGR" --licenses >/dev/null 2>&1 || true | |
| # Helper: install component if missing | |
| need() { | |
| local p="$1" | |
| if ! "$SDKMGR" --list_installed | grep -q "^ $p$"; then | |
| echo "Installing $p" | |
| "$SDKMGR" "$p" | |
| else | |
| echo "✓ $p already installed" | |
| fi | |
| } | |
| # Match root gradle pins | |
| need "platforms;android-35" | |
| need "build-tools;35.0.0" | |
| need "ndk;27.1.12297006" | |
| # CMake version | |
| need "cmake;3.22.1" | |
| # Sanity output | |
| "$ANDROID_SDK_ROOT/cmake/3.22.1/bin/cmake" --version || true | |
| test -f "$ANDROID_SDK_ROOT/ndk/27.1.12297006/source.properties" && echo "✅ NDK 27.1.12297006 installed" | |
| - name: Generate app icon and splash screen | |
| run: | | |
| echo "🎨 Generating app icon and splash screen" | |
| yarn generate:app-icon | |
| yarn generate:launch-screen | |
| echo "✅ App icon and splash screen generated" | |
| - name: Cache Gradle dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} | |
| restore-keys: | | |
| ${{ runner.os }}-gradle- | |
| - name: Build or Deploy Android app | |
| run: | | |
| set -e | |
| # Load .env variables into step | |
| set -a | |
| source ./.env | |
| set +a | |
| cd android | |
| chmod +x ./gradlew | |
| # Sanity check | |
| ./gradlew :app:signingReport --no-daemon | |
| # Install Fastlane for deployment | |
| if [ "${{ needs.detect-release.outputs.deploy }}" == "true" ]; then | |
| echo "🚀 Installing Fastlane for deployment" | |
| sudo gem install fastlane | |
| echo "📋 Available Fastlane lanes:" | |
| fastlane lanes | |
| echo "🚀 Deploying to Google Play Store" | |
| echo "Profile: ${{ needs.detect-release.outputs.profile }}" | |
| echo "Release Track: ${{ needs.detect-release.outputs.release_track }}" | |
| # Use Fastlane to build and deploy (single step) | |
| case "${{ needs.detect-release.outputs.release_track }}" in | |
| "production") | |
| echo "📱 Deploying to production track" | |
| fastlane deploy_production | |
| ;; | |
| "beta") | |
| echo "📱 Deploying to beta track" | |
| fastlane deploy_beta | |
| ;; | |
| "alpha") | |
| echo "📱 Deploying to alpha track" | |
| fastlane deploy_alpha | |
| ;; | |
| "internal"|*) | |
| echo "📱 Deploying to internal testing track" | |
| fastlane deploy_internal | |
| ;; | |
| esac | |
| echo "✅ Successfully deployed to Google Play" | |
| else | |
| # Just build for testing/artifacts | |
| echo "🔨 Building Android app (no deployment)" | |
| case "${{ needs.detect-release.outputs.build_type }}" in | |
| "release") | |
| ./gradlew clean bundleRelease --no-daemon --stacktrace | |
| echo "AAB_PATH=android/app/build/outputs/bundle/release/app-release.aab" >> $GITHUB_ENV | |
| echo "BUILD_OUTPUT=Release AAB" >> $GITHUB_ENV | |
| ;; | |
| *) | |
| ./gradlew clean assembleDebug --no-daemon --stacktrace | |
| echo "APK_PATH=android/app/build/outputs/apk/debug/app-debug.apk" >> $GITHUB_ENV | |
| echo "BUILD_OUTPUT=Debug APK" >> $GITHUB_ENV | |
| ;; | |
| esac | |
| fi | |
| - name: Upload build artifacts | |
| if: needs.detect-release.outputs.deploy != 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-${{ needs.detect-release.outputs.profile }}-${{ needs.detect-release.outputs.build_type }}-${{ needs.detect-release.outputs.version }} | |
| path: ${{ env.APK_PATH || env.AAB_PATH }} | |
| retention-days: 30 | |
| # iOS build and deployment | |
| build-ios: | |
| runs-on: macos-latest | |
| needs: [detect-release, setup, resolve-versions] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Xcode | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: latest-stable | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'yarn' | |
| - name: Enable Corepack and install dependencies | |
| run: | | |
| corepack enable | |
| corepack prepare [email protected] --activate | |
| yarn install --immutable | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| aws-region: ${{ secrets.AWS_REGION || 'ap-southeast-1' }} | |
| - name: Download profile configuration | |
| run: | | |
| echo "📦 Downloading profile: ${{ needs.detect-release.outputs.profile }}" | |
| aws s3 sync s3://${{ env.S3_BUCKET }}/${{ needs.detect-release.outputs.profile }}/ ./profile-config/ | |
| # Validate required files (simplified structure) | |
| required_files=( | |
| "./profile-config/.env" | |
| "./profile-config/app-icon.png" | |
| "./profile-config/splash-screen.png" | |
| "./profile-config/ios-Fastfile" | |
| "./profile-config/ios-Appfile" | |
| "./profile-config/appstore-api-key.p8" | |
| ) | |
| for file in "${required_files[@]}"; do | |
| if [ ! -f "$file" ]; then | |
| echo "❌ Missing required file: $file" | |
| exit 1 | |
| fi | |
| done | |
| - name: Download resolved versions | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: versions | |
| - name: Source versions & bump Info.plist | |
| run: | | |
| echo "🔧 Setting up profile environment securely" | |
| # Load .env variables into process environment WITHOUT exposing in logs | |
| set -a | |
| source ./profile-config/.env | |
| set +a | |
| # Load version overrides from secure artifact | |
| source ./versions.env | |
| echo "iOS building: $IOS_VERSION ($IOS_BUILD_NUMBER)" | |
| # enforce parity in the app: | |
| /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $IOS_VERSION" ios/StorefrontApp/Info.plist | |
| /usr/libexec/PlistBuddy -c "Set :CFBundleVersion $IOS_BUILD_NUMBER" ios/StorefrontApp/Info.plist || true | |
| - name: Setup profile environment | |
| run: | | |
| echo "🔧 Setting up profile environment" | |
| # Copy configuration files | |
| cp ./profile-config/.env ./.env | |
| cp ./profile-config/app-icon.png ./assets/app-icon.png | |
| cp ./profile-config/splash-screen.png ./assets/splash-screen.png | |
| # Setup Fastlane files | |
| mkdir -p ios/fastlane | |
| cp ./profile-config/ios-Fastfile ios/fastlane/Fastfile | |
| cp ./profile-config/ios-Appfile ios/fastlane/Appfile | |
| echo "✅ Profile environment setup completed for: ${{ needs.detect-release.outputs.profile }}" | |
| echo "📱 Version: $IOS_VERSION ($IOS_BUILD_NUMBER)" | |
| - name: Set up iOS authentication | |
| run: | | |
| # Load .env variables into step | |
| set -a | |
| source ./.env | |
| set +a | |
| echo "🔐 Setting up iOS authentication (required for all iOS builds)" | |
| # Check if Apple credentials are available | |
| if [ -z "$APPSTORE_API_KEY_ID" ] || [ -z "$APPSTORE_API_ISSUER_ID" ]; then | |
| echo "❌ Missing Apple credentials in .env file" | |
| echo "Required: APPSTORE_API_KEY_ID, APPSTORE_API_ISSUER_ID" | |
| exit 1 | |
| fi | |
| # Check if API key file exists | |
| if [ ! -f "./profile-config/appstore-api-key.p8" ]; then | |
| echo "❌ Missing appstore-api-key.p8 file" | |
| exit 1 | |
| fi | |
| # Create the API key file for Fastlane and Xcode | |
| mkdir -p ~/.appstoreconnect/private_keys | |
| cp ./profile-config/appstore-api-key.p8 ~/.appstoreconnect/private_keys/AuthKey_${APPSTORE_API_KEY_ID}.p8 | |
| # Set proper permissions | |
| chmod 600 ~/.appstoreconnect/private_keys/AuthKey_${APPSTORE_API_KEY_ID}.p8 | |
| echo "✅ App Store Connect API key configured for all iOS builds" | |
| - name: Install ImageMagick (macOS) | |
| run: | | |
| brew update | |
| brew install imagemagick | |
| - name: Generate app icon and splash screen | |
| run: | | |
| echo "🎨 Generating app icon and splash screen" | |
| yarn generate:app-icon | |
| yarn generate:launch-screen | |
| echo "✅ App icon and splash screen generated" | |
| - name: Cache CocoaPods | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ios/Pods | |
| ~/Library/Caches/CocoaPods | |
| ~/.cocoapods | |
| key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pods- | |
| - name: Install iOS dependencies | |
| run: | | |
| echo "📦 Installing iOS dependencies" | |
| cd ios && pod install --repo-update | |
| - name: Create Gemfile and install Fastlane + plugins | |
| run: | | |
| set -e | |
| cat > Gemfile <<'EOF' | |
| source "https://rubygems.org" | |
| gem "fastlane", "~> 2.228" | |
| EOF | |
| # Optional: speed up / be deterministic | |
| export BUNDLE_PATH="vendor/bundle" | |
| export BUNDLE_JOBS=4 | |
| export BUNDLE_RETRY=3 | |
| bundle install | |
| - name: Build or Deploy iOS app | |
| env: | |
| PROFILE: ${{ needs.detect-release.outputs.profile }} | |
| FASTLANE_SKIP_UPDATE_CHECK: '1' | |
| FASTLANE_DISABLE_PROMPTS: '1' | |
| MATCH_S3_BUCKET: ${{ env.S3_BUCKET }} | |
| MATCH_S3_OBJECT_PREFIX: '${{ needs.detect-release.outputs.profile }}/certs/' | |
| MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} | |
| MATCH_S3_REGION: ${{ secrets.AWS_REGION || 'ap-southeast-1' }} | |
| run: | | |
| set -e | |
| # Load .env variables into step | |
| set -a | |
| source ./.env | |
| set +a | |
| cd ios | |
| echo "📋 Available Fastlane lanes:" | |
| bundle exec fastlane lanes | |
| if [ "${{ needs.detect-release.outputs.deploy }}" = "true" ]; then | |
| echo "🚀 Deploying to App Store Connect" | |
| echo "Profile: ${{ needs.detect-release.outputs.profile }}" | |
| echo "Release Track: ${{ needs.detect-release.outputs.release_track }}" | |
| case "${{ needs.detect-release.outputs.release_track }}" in | |
| "production") | |
| echo "📱 Deploying to App Store for review" | |
| bundle exec fastlane deploy_appstore | |
| ;; | |
| "internal"|"alpha"|"beta"|*) | |
| echo "📱 Deploying to TestFlight" | |
| bundle exec fastlane deploy_testflight | |
| ;; | |
| esac | |
| echo "✅ Fastlane finished" | |
| else | |
| echo "🔨 Building iOS app (no deployment)" | |
| case "${{ needs.detect-release.outputs.build_type }}" in | |
| "release") | |
| echo "🔨 Building iOS Release Archive with automatic signing" | |
| xcodebuild -workspace StorefrontApp.xcworkspace \ | |
| -scheme StorefrontApp \ | |
| -configuration Release \ | |
| -destination "generic/platform=iOS" \ | |
| -archivePath build/StorefrontApp.xcarchive \ | |
| -allowProvisioningUpdates \ | |
| CODE_SIGN_STYLE=Automatic \ | |
| DEVELOPMENT_TEAM="$DEVELOPMENT_TEAM_ID" \ | |
| archive | |
| echo "IOS_BUILD_PATH=ios/build/StorefrontApp.xcarchive" >> $GITHUB_ENV | |
| echo "BUILD_OUTPUT=Archive" >> $GITHUB_ENV | |
| ;; | |
| *) | |
| echo "🔨 Building iOS Debug for Simulator (with automatic signing)" | |
| SIMULATOR_ID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -o '[A-F0-9-]\{36\}' | head -1) | |
| if [ -n "$SIMULATOR_ID" ]; then | |
| DESTINATION="id=$SIMULATOR_ID" | |
| echo "📱 Using iPhone simulator: $SIMULATOR_ID" | |
| else | |
| DESTINATION="generic/platform=iOS Simulator" | |
| echo "📱 Using generic iOS Simulator" | |
| fi | |
| xcodebuild -workspace StorefrontApp.xcworkspace \ | |
| -scheme StorefrontApp \ | |
| -configuration Debug \ | |
| -sdk iphonesimulator \ | |
| -destination "$DESTINATION" \ | |
| -derivedDataPath build \ | |
| -allowProvisioningUpdates \ | |
| CODE_SIGN_STYLE=Automatic \ | |
| DEVELOPMENT_TEAM="$DEVELOPMENT_TEAM_ID" \ | |
| build | |
| echo "IOS_BUILD_PATH=ios/build/Build/Products/Debug-iphonesimulator/" >> $GITHUB_ENV | |
| echo "BUILD_OUTPUT=Debug Build for Simulator" >> $GITHUB_ENV | |
| ;; | |
| esac | |
| fi | |
| - name: Upload build artifacts | |
| if: needs.detect-release.outputs.deploy != 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ios-${{ needs.detect-release.outputs.profile }}-${{ needs.detect-release.outputs.build_type }}-${{ needs.detect-release.outputs.version }} | |
| path: ${{ env.IOS_BUILD_PATH }} | |
| retention-days: 30 | |
| # Send Discord notification | |
| notify: | |
| runs-on: ubuntu-latest | |
| needs: [detect-release, build-android, build-ios] | |
| if: always() | |
| steps: | |
| - name: Determine overall status | |
| run: | | |
| ANDROID_STATUS="${{ needs.build-android.result }}" | |
| IOS_STATUS="${{ needs.build-ios.result }}" | |
| if [ "$ANDROID_STATUS" == "success" ] && [ "$IOS_STATUS" == "success" ]; then | |
| echo "OVERALL_STATUS=success" >> $GITHUB_ENV | |
| echo "STATUS_EMOJI=✅" >> $GITHUB_ENV | |
| elif [ "$ANDROID_STATUS" == "failure" ] || [ "$IOS_STATUS" == "failure" ]; then | |
| echo "OVERALL_STATUS=failure" >> $GITHUB_ENV | |
| echo "STATUS_EMOJI=❌" >> $GITHUB_ENV | |
| else | |
| echo "OVERALL_STATUS=partial" >> $GITHUB_ENV | |
| echo "STATUS_EMOJI=⚠️" >> $GITHUB_ENV | |
| fi | |
| - name: Send Discord notification | |
| uses: tsickert/[email protected] | |
| with: | |
| webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| username: Fleetbase | |
| content: | | |
| ${{ env.STATUS_EMOJI }} **Storefront App Release** | |
| **Profile:** `${{ needs.detect-release.outputs.profile }}` | |
| **Version:** `${{ needs.detect-release.outputs.version }}` | |
| **Build Number:** `${{ needs.detect-release.outputs.build_number }}` | |
| **Build Type:** `${{ needs.detect-release.outputs.build_type }}` | |
| **Deploy:** `${{ needs.detect-release.outputs.deploy }}` | |
| **Build Status:** | |
| • **Android:** ${{ needs.build-android.result == 'success' && '✅ Success' || needs.build-android.result == 'failure' && '❌ Failed' || '⚠️ Skipped' }} | |
| • **iOS:** ${{ needs.build-ios.result == 'success' && '✅ Success' || needs.build-ios.result == 'failure' && '❌ Failed' || '⚠️ Skipped' }} | |
| ${{ needs.detect-release.outputs.deploy == 'true' && format('**Release Track:** `{0}`', needs.detect-release.outputs.release_track) || '**Deployment:** Disabled' }} | |
| ${{ needs.detect-release.outputs.is_release == 'true' && '🚀 **Release Build**' || '🔧 **Development Build**' }} | |
| **Triggered by:** ${{ github.event_name == 'workflow_dispatch' && 'Manual dispatch' || 'Branch push' }} | |
| **Branch:** `${{ github.ref_name }}` | |
| **Commit:** [`${{ github.sha }}`](${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}) | |
| [View Workflow](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) |