name: Frontend Client - EAS Build on: push: branches: [main, pre-prod] paths: - "mobile/**" pull_request: branches: [main, pre-prod] paths: - "mobile/**" jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: npm cache-dependency-path: mobile/package-lock.json - name: Setup Java uses: actions/setup-java@v4 with: distribution: temurin java-version: 17 - name: Setup Android SDK uses: android-actions/setup-android@v3 - name: Install EAS CLI & tooling run: | n=0 until [ $n -ge 3 ]; do npm install -g eas-cli && break n=$((n + 1)) echo "npm install -g eas-cli failed (attempt $n/3), cleaning up and retrying in 5s..." npm uninstall -g eas-cli >/dev/null 2>&1 || true rm -rf "$(npm root -g)/eas-cli" "$(npm root -g)"/.eas-cli-* 2>/dev/null || true sleep 5 done command -v eas >/dev/null || { echo "::error::eas-cli installation failed after 3 attempts"; exit 1; } pip install -r scripts/requirements.txt awscli --quiet - name: Install dependencies working-directory: mobile run: npm ci - name: Typecheck working-directory: mobile run: npx tsc --noEmit - name: Determine config id: config run: | if [ "${{ github.ref_name }}" = "main" ] || [ "${{ github.base_ref }}" = "main" ]; then echo "profile=production" >> $GITHUB_OUTPUT echo "channel=production-client" >> $GITHUB_OUTPUT echo "api_url=${{ secrets.PROD_API_URL }}" >> $GITHUB_OUTPUT echo "ota_api_url=${{ secrets.PROD_API_URL }}" >> $GITHUB_OUTPUT echo "xavia_url=https://ota-mobile-prod.uber-stup.club" >> $GITHUB_OUTPUT echo "xavia_key=${{ secrets.XAVIA_KEY_MOBILE_PROD }}" >> $GITHUB_OUTPUT echo "apk_name=mobile-production-$(date +%Y%m%d-%H%M).apk" >> $GITHUB_OUTPUT echo "message=Production update $(date +%Y%m%d-%H%M)" >> $GITHUB_OUTPUT else echo "profile=pre-prod" >> $GITHUB_OUTPUT echo "channel=pre-prod-client" >> $GITHUB_OUTPUT echo "api_url=${{ secrets.PREPROD_API_URL }}" >> $GITHUB_OUTPUT echo "ota_api_url=${{ secrets.PREPROD_API_URL }}" >> $GITHUB_OUTPUT echo "xavia_url=https://ota-mobile-preprod.uber-stup.club" >> $GITHUB_OUTPUT echo "xavia_key=${{ secrets.XAVIA_KEY_MOBILE_PREPROD }}" >> $GITHUB_OUTPUT echo "apk_name=mobile-pre-prod-$(date +%Y%m%d-%H%M).apk" >> $GITHUB_OUTPUT echo "message=Pre-prod update $(date +%Y%m%d-%H%M)" >> $GITHUB_OUTPUT fi - name: Inject EAS project ID working-directory: mobile run: | jq '.expo.extra.eas.projectId = "${{ secrets.EXPO_PROJECT_ID_CLIENT }}"' app.json > app.tmp.json mv app.tmp.json app.json - name: Select code signing certificate # certs/certificate.pem (committé) correspond à la clé de signature # du serveur OTA mobile de production ; le serveur pre-prod signe # avec une clé différente (PRIVATE_KEY_MOBILE_PREPROD côté ota-uber), # donc les builds pre-prod doivent embarquer # certs/certificate-preprod.pem à la place, sous peine de voir toute # MAJ OTA rejetée silencieusement (signature invalide) sur ce canal. working-directory: mobile run: | if [ "${{ steps.config.outputs.profile }}" = "pre-prod" ]; then cp certs/certificate-preprod.pem certs/certificate.pem fi - name: Restore Gradle cache (RustFS) env: AWS_ACCESS_KEY_ID: ${{ secrets.RUSTFS_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.RUSTFS_SECRET_KEY }} S3_ENDPOINT: https://rustfs.uber-stup.club S3_BUCKET: apk-builds run: python scripts/eas_cache.py restore --app mobile - name: Build APK (local) working-directory: mobile env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} EXPO_PUBLIC_API_URL: ${{ steps.config.outputs.api_url }} EXPO_PUBLIC_UPDATE_URL: ${{ steps.config.outputs.xavia_url }} EAS_BUILD_NO_EXPO_GO_WARNING: true NODE_OPTIONS: "--max-old-space-size=2048" GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=2 -Dorg.gradle.internal.repository.max.retries=10 -Dorg.gradle.internal.repository.initial.backoff.ms=1000" JAVA_TOOL_OPTIONS: "-Xmx3g" run: eas build --platform android --profile ${{ steps.config.outputs.profile }} --local --non-interactive - name: Save Gradle cache (RustFS) if: success() || failure() env: AWS_ACCESS_KEY_ID: ${{ secrets.RUSTFS_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.RUSTFS_SECRET_KEY }} S3_ENDPOINT: https://rustfs.uber-stup.club S3_BUCKET: apk-builds run: python scripts/eas_cache.py save --app mobile - name: Rename & upload APK to RustFS working-directory: mobile env: AWS_ACCESS_KEY_ID: ${{ secrets.RUSTFS_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.RUSTFS_SECRET_KEY }} AWS_DEFAULT_REGION: us-east-1 run: | mv *.apk ${{ steps.config.outputs.apk_name }} aws s3 cp ${{ steps.config.outputs.apk_name }} \ s3://apk-builds/${{ steps.config.outputs.profile }}/${{ steps.config.outputs.apk_name }} \ --endpoint-url https://rustfs.uber-stup.club \ --no-verify-ssl - name: Publish OTA update to Xavia if: github.event_name == 'push' working-directory: mobile env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} EXPO_PUBLIC_API_URL: ${{ steps.config.outputs.ota_api_url }} EXPO_PUBLIC_UPDATE_URL: ${{ steps.config.outputs.xavia_url }} NODE_OPTIONS: "--max-old-space-size=2048" run: | RUNTIME_VERSION=$(jq -r '.expo.runtimeVersion' app.json) npx expo export --platform android --output-dir dist npx expo config --json > dist/expoconfig.json cd dist && zip -r ../bundle.zip . && cd .. curl -X POST "${{ steps.config.outputs.xavia_url }}/api/upload" \ -H "Authorization: Bearer ${{ steps.config.outputs.xavia_key }}" \ -F "file=@bundle.zip" \ -F "runtimeVersion=$RUNTIME_VERSION" \ -F "channel=${{ steps.config.outputs.channel }}" \ -F "commitHash=${{ github.sha }}" \ -F "commitMessage=${{ steps.config.outputs.message }}" \ --fail