Add collectionId to PLAYBACK_PLAY analytics events (#14101) #404
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: Mobile CI/CD | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - 'packages/mobile/**' | |
| - 'packages/common/**' | |
| - 'packages/harmony/**' | |
| - 'packages/libs/**' | |
| - 'packages/sdk/**' | |
| - 'package-lock.json' | |
| - '.github/workflows/mobile.yml' | |
| - '.circleci/**' | |
| workflow_dispatch: | |
| inputs: | |
| ota_channel: | |
| description: 'OTA channel for manual publish' | |
| required: false | |
| default: 'rc' | |
| type: choice | |
| options: | |
| - rc | |
| - production | |
| env: | |
| NODE_VERSION: '22.21.1' | |
| jobs: | |
| # Initialize: Install dependencies, lint & typecheck | |
| mobile-init: | |
| name: Mobile Init (Install, Lint & Typecheck) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Create concatenated patch file | |
| id: patch-file | |
| run: | | |
| ls -d -- packages/*/patches/*.patch 2>/dev/null | xargs cat > combined-patch-file.txt || touch combined-patch-file.txt | |
| echo "patch_checksum=$(sha256sum combined-patch-file.txt | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache node modules | |
| id: cache-node-modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| node_modules | |
| packages/mobile/node_modules | |
| packages/common/node_modules | |
| packages/libs/node_modules | |
| packages/libs/dist | |
| packages/sdk/node_modules | |
| packages/sdk/dist | |
| packages/harmony/node_modules | |
| packages/dotenv-linter/bin | |
| key: npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}-${{ steps.patch-file.outputs.patch_checksum }} | |
| restore-keys: | | |
| npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}- | |
| npm-cache-mobile-${{ runner.os }}-${{ hashFiles('package-lock.json') }}- | |
| - name: Copy production env | |
| run: | | |
| cd packages/mobile | |
| echo -e "\nAMPLITUDE_WRITE_KEY=${{ secrets.AMPLITUDE_WRITE_KEY_PROD }}" >> .env.prod | |
| - name: Install dependencies | |
| env: | |
| CI: true | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| run: | | |
| if [[ -d node_modules ]]; then | |
| echo "Using cached node_modules, running postinstall..." | |
| npm run postinstall | |
| # Ensure dotenv-linter binary is installed (install script downloads it) | |
| if [[ ! -f packages/dotenv-linter/bin/dotenv-linter ]]; then | |
| echo "dotenv-linter binary missing, running install script..." | |
| (cd packages/dotenv-linter && npm run install) | |
| fi | |
| else | |
| echo "No cache found, running fresh install..." | |
| # Clear npm cache to avoid EEXIST conflicts | |
| npm cache clean --force || true | |
| # Try npm ci first, fallback to npm install if lock file is out of sync | |
| npm ci --prefer-offline || npm install --prefer-offline | |
| fi | |
| - name: Lint & Typecheck | |
| run: npx turbo run verify --filter=@audius/mobile | |
| mobile-version-check: | |
| name: Mobile Version Change Check | |
| runs-on: ubuntu-latest | |
| needs: mobile-init | |
| if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' | |
| outputs: | |
| current_version: ${{ steps.version-check.outputs.current_version }} | |
| previous_version: ${{ steps.version-check.outputs.previous_version }} | |
| version_changed: ${{ steps.version-check.outputs.version_changed }} | |
| version_unchanged: ${{ steps.version-check.outputs.version_unchanged }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 2 | |
| - name: Determine version change | |
| id: version-check | |
| run: | | |
| set -euo pipefail | |
| CURRENT_VERSION=$(jq -r '.version' packages/mobile/package.json) | |
| PREV_VERSION=$(git show HEAD^:packages/mobile/package.json | jq -r '.version') | |
| echo "current_version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" | |
| echo "previous_version=$PREV_VERSION" >> "$GITHUB_OUTPUT" | |
| if [[ "$CURRENT_VERSION" == "$PREV_VERSION" ]]; then | |
| echo "version_changed=false" >> "$GITHUB_OUTPUT" | |
| echo "version_unchanged=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "version_changed=true" >> "$GITHUB_OUTPUT" | |
| echo "version_unchanged=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| # OTA Release: publish JS bundle updates when native app version is unchanged | |
| mobile-ota-release: | |
| name: Mobile OTA Release (CodePush) | |
| runs-on: ubuntu-latest | |
| needs: [mobile-init, mobile-version-check] | |
| if: (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.mobile-version-check.outputs.version_unchanged == 'true') || (github.event_name == 'workflow_dispatch' && (github.event.inputs.ota_channel == 'rc' || github.event.inputs.ota_channel == '')) | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 2 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| env: | |
| CI: true | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| run: npm ci --prefer-offline || npm install --prefer-offline | |
| - 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: us-east-1 | |
| - name: Determine OTA channel | |
| id: ota-channel | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "channel=${{ github.event.inputs.ota_channel || 'rc' }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "channel=rc" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Publish OTA updates (iOS + Android) | |
| env: | |
| OTA_S3_BUCKET: ${{ secrets.OTA_S3_BUCKET }} | |
| OTA_S3_PREFIX: mobile-ota | |
| OTA_PUBLIC_BASE_URL: https://download.audius.co/mobile-ota | |
| run: | | |
| set -euo pipefail | |
| BINARY_VERSION="${{ needs.mobile-version-check.outputs.current_version }}" | |
| OTA_CHANNEL="${{ steps.ota-channel.outputs.channel }}" | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$BINARY_VERSION" | |
| # Keep OTA release versions above binary version while preserving semver format. | |
| OTA_PATCH=$((PATCH + 100000 + GITHUB_RUN_NUMBER)) | |
| OTA_APP_VERSION="${MAJOR}.${MINOR}.${OTA_PATCH}" | |
| cd packages/mobile | |
| npx code-push create-history --binary-version "$BINARY_VERSION" --platform ios --identifier "$OTA_CHANNEL" || true | |
| npx code-push create-history --binary-version "$BINARY_VERSION" --platform android --identifier "$OTA_CHANNEL" || true | |
| npx code-push release \ | |
| --binary-version "$BINARY_VERSION" \ | |
| --app-version "$OTA_APP_VERSION" \ | |
| --platform ios \ | |
| --identifier "$OTA_CHANNEL" \ | |
| --entry-file index.js | |
| npx code-push release \ | |
| --binary-version "$BINARY_VERSION" \ | |
| --app-version "$OTA_APP_VERSION" \ | |
| --platform android \ | |
| --identifier "$OTA_CHANNEL" \ | |
| --entry-file index.js | |
| # OTA Release (Production): manual + environment approval gate | |
| mobile-ota-release-production: | |
| name: Mobile OTA Release (Production, Approved) | |
| runs-on: ubuntu-latest | |
| needs: [mobile-init, mobile-version-check] | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.ota_channel == 'production' | |
| environment: | |
| name: mobile-production-ota | |
| url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 2 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Install dependencies | |
| env: | |
| CI: true | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| run: npm ci --prefer-offline || npm install --prefer-offline | |
| - 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: us-east-1 | |
| - name: Publish OTA updates to production (iOS + Android) | |
| env: | |
| OTA_S3_BUCKET: ${{ secrets.OTA_S3_BUCKET }} | |
| OTA_S3_PREFIX: mobile-ota | |
| OTA_PUBLIC_BASE_URL: https://download.audius.co/mobile-ota | |
| run: | | |
| set -euo pipefail | |
| BINARY_VERSION="${{ needs.mobile-version-check.outputs.current_version }}" | |
| OTA_CHANNEL="production" | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$BINARY_VERSION" | |
| OTA_PATCH=$((PATCH + 100000 + GITHUB_RUN_NUMBER)) | |
| OTA_APP_VERSION="${MAJOR}.${MINOR}.${OTA_PATCH}" | |
| cd packages/mobile | |
| npx code-push create-history --binary-version "$BINARY_VERSION" --platform ios --identifier "$OTA_CHANNEL" || true | |
| npx code-push create-history --binary-version "$BINARY_VERSION" --platform android --identifier "$OTA_CHANNEL" || true | |
| npx code-push release \ | |
| --binary-version "$BINARY_VERSION" \ | |
| --app-version "$OTA_APP_VERSION" \ | |
| --platform ios \ | |
| --identifier "$OTA_CHANNEL" \ | |
| --entry-file index.js | |
| npx code-push release \ | |
| --binary-version "$BINARY_VERSION" \ | |
| --app-version "$OTA_APP_VERSION" \ | |
| --platform android \ | |
| --identifier "$OTA_CHANNEL" \ | |
| --entry-file index.js | |
| # iOS Release Candidate: Build and upload | |
| mobile-build-upload-releasecandidate-ios: | |
| name: iOS Release Candidate Build & Upload | |
| runs-on: macos-15 | |
| needs: [mobile-init, mobile-version-check] | |
| if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && needs.mobile-version-check.outputs.version_changed == 'true' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Create concatenated patch file | |
| id: patch-file | |
| run: | | |
| ls -d -- packages/*/patches/*.patch 2>/dev/null | xargs cat > combined-patch-file.txt || touch combined-patch-file.txt | |
| echo "patch_checksum=$(sha256sum combined-patch-file.txt | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache node modules | |
| id: cache-node-modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| node_modules | |
| packages/mobile/node_modules | |
| packages/common/node_modules | |
| packages/libs/node_modules | |
| packages/libs/dist | |
| packages/sdk/node_modules | |
| packages/sdk/dist | |
| packages/harmony/node_modules | |
| key: npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}-${{ steps.patch-file.outputs.patch_checksum }} | |
| restore-keys: | | |
| npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}- | |
| npm-cache-mobile-${{ runner.os }}-${{ hashFiles('package-lock.json') }}- | |
| - name: Copy production env | |
| run: | | |
| cd packages/mobile | |
| echo -e "\nAMPLITUDE_WRITE_KEY=${{ secrets.AMPLITUDE_WRITE_KEY_PROD }}" >> .env.prod | |
| - name: Configure release-candidate OTA channel | |
| run: | | |
| awk '!/^OTA_CHANNEL=/' packages/mobile/.env.prod > packages/mobile/.env.prod.tmp | |
| mv packages/mobile/.env.prod.tmp packages/mobile/.env.prod | |
| echo "OTA_CHANNEL=rc" >> packages/mobile/.env.prod | |
| - name: Install Python build dependencies | |
| run: | | |
| # Python 3.12+ doesn't include distutils, but setuptools provides it | |
| # Use --break-system-packages for CI (safe since runner is ephemeral) | |
| python3 -m pip install --break-system-packages --upgrade setuptools | |
| - name: Install dependencies | |
| env: | |
| CI: true | |
| SKIP_POD_INSTALL: true | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| run: | | |
| if [[ -d node_modules ]]; then | |
| echo "Using cached node_modules, running postinstall..." | |
| npm run postinstall | |
| # Ensure dotenv-linter binary is installed (install script downloads it) | |
| if [[ ! -f packages/dotenv-linter/bin/dotenv-linter ]]; then | |
| echo "dotenv-linter binary missing, running install script..." | |
| (cd packages/dotenv-linter && npm run install) | |
| fi | |
| else | |
| echo "No cache found, running fresh install..." | |
| # Clear npm cache to avoid EEXIST conflicts | |
| npm cache clean --force || true | |
| # Try npm ci first, fallback to npm install if lock file is out of sync | |
| npm ci --prefer-offline || npm install --prefer-offline | |
| fi | |
| - name: Cache iOS gems | |
| uses: actions/cache@v4 | |
| with: | |
| path: packages/mobile/ios/vendor/bundle | |
| key: gems-ios-${{ hashFiles('packages/mobile/ios/Gemfile.lock') }} | |
| restore-keys: | | |
| gems-ios- | |
| - name: Install gems | |
| run: | | |
| cd packages/mobile/ios | |
| sudo gem install bundler:2.5.16 | |
| bundle check || bundle install --path vendor/bundle | |
| - name: Install CocoaPods dependencies | |
| run: | | |
| cd packages/mobile/ios | |
| RCT_NEW_ARCH_ENABLED=0 bundle exec pod install | |
| - name: Install Sentry CLI | |
| run: | | |
| curl -sL https://sentry.io/get-cli/ | bash | |
| echo "$HOME/.sentry-cli/bin" >> $GITHUB_PATH | |
| - name: Build dependencies | |
| timeout-minutes: 60 | |
| run: npx turbo run build --filter=@audius/mobile | |
| - name: Create iOS bundle | |
| run: cd packages/mobile && npx turbo run bundle:ios | |
| - name: Update fastlane | |
| run: cd packages/mobile/ios && sudo bundle update fastlane | |
| - name: Copy production env to iOS | |
| run: cp packages/mobile/.env.prod packages/mobile/ios/ | |
| - name: Set up SSH for Fastlane Match | |
| run: | | |
| # Set up SSH agent | |
| eval "$(ssh-agent -s)" | |
| # Add GitHub to known_hosts | |
| mkdir -p ~/.ssh | |
| chmod 700 ~/.ssh | |
| echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' >> ~/.ssh/known_hosts | |
| chmod 644 ~/.ssh/known_hosts | |
| # Add SSH private key for accessing certs-and-profiles repository | |
| # Check if secret is set | |
| if [[ -z "${{ secrets.AUDIUS_INFRA_SSH_KEY }}" ]]; then | |
| echo "Error: AUDIUS_INFRA_SSH_KEY secret is not set" | |
| exit 1 | |
| fi | |
| # Write key and remove any carriage returns (works on both macOS and Linux) | |
| echo "${{ secrets.AUDIUS_INFRA_SSH_KEY }}" | tr -d '\r' > ~/.ssh/id_rsa | |
| chmod 600 ~/.ssh/id_rsa | |
| # Add key to agent (will provide detailed error if format is wrong) | |
| ssh-add ~/.ssh/id_rsa | |
| # Configure git to use SSH | |
| git config --global user.email "audius-infra@audius.co" | |
| git config --global user.name "audius-infra" | |
| - name: Build and upload iOS (Release Candidate) | |
| timeout-minutes: 60 | |
| env: | |
| FASTLANE_USER: ${{ secrets.FASTLANE_USER }} | |
| FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} | |
| FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }} | |
| FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }} | |
| MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} | |
| APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }} | |
| APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} | |
| APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_ORG: ${{ secrets.SENTRY_ORG }} | |
| SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} | |
| run: | | |
| cd packages/mobile/ios | |
| bundle exec fastlane build_and_upload bundle_id:co.audius.audiusmusic.releasecandidate | |
| - name: Slack notification | |
| if: success() | |
| env: | |
| SLACK_WEBHOOK: ${{ secrets.SLACK_DAILY_DEPLOY_WEBHOOK }} | |
| run: | | |
| deploying_version=$(jq -r '.version' packages/mobile/package.json) | |
| job_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| json_content="{ \"blocks\": [{ \"type\": \"section\", \"text\": { \"type\": \"mrkdwn\", \"text\": \"Deployed co.audius.audiusmusic.releasecandidate <${job_url}|v${deploying_version}> to mobile ios\" } }]}" | |
| curl -f -X POST -H 'Content-type: application/json' --data "$json_content" $SLACK_WEBHOOK | |
| # Android Release Candidate: Build and upload | |
| mobile-build-upload-releasecandidate-android: | |
| name: Android Release Candidate Build & Upload | |
| runs-on: ubuntu-latest | |
| needs: [mobile-init, mobile-version-check] | |
| if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && needs.mobile-version-check.outputs.version_changed == 'true' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: 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: Cache Gradle | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: gradle-${{ runner.os }}-${{ hashFiles('packages/mobile/android/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('packages/mobile/android/build.gradle') }} | |
| restore-keys: | | |
| gradle-${{ runner.os }}- | |
| - name: Cache Android SDK | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.android/sdk | |
| key: android-sdk-${{ runner.os }}-${{ hashFiles('packages/mobile/android/build.gradle') }} | |
| restore-keys: | | |
| android-sdk-${{ runner.os }}- | |
| - name: Free disk space | |
| run: | | |
| echo "Disk space before cleanup:" | |
| df -h | |
| # Remove unnecessary packages and caches | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf /opt/ghc | |
| sudo rm -rf /usr/local/share/boost | |
| sudo rm -rf "$AGENT_TOOLSDIRECTORY" | |
| sudo rm -rf /opt/hostedtoolcache/CodeQL | |
| sudo rm -rf /opt/microsoft | |
| # Clean apt cache | |
| sudo apt-get clean | |
| sudo rm -rf /var/lib/apt/lists/* | |
| # Clean npm cache | |
| npm cache clean --force || true | |
| # Clean pip cache | |
| pip cache purge || true | |
| echo "Disk space after cleanup:" | |
| df -h | |
| - name: Setup Java (after disk cleanup) | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '17' | |
| - name: Create concatenated patch file | |
| id: patch-file | |
| run: | | |
| ls -d -- packages/*/patches/*.patch 2>/dev/null | xargs cat > combined-patch-file.txt || touch combined-patch-file.txt | |
| echo "patch_checksum=$(sha256sum combined-patch-file.txt | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache node modules | |
| id: cache-node-modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| node_modules | |
| packages/mobile/node_modules | |
| packages/common/node_modules | |
| packages/libs/node_modules | |
| packages/libs/dist | |
| packages/sdk/node_modules | |
| packages/sdk/dist | |
| packages/harmony/node_modules | |
| key: npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}-${{ steps.patch-file.outputs.patch_checksum }} | |
| restore-keys: | | |
| npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}- | |
| npm-cache-mobile-${{ runner.os }}-${{ hashFiles('package-lock.json') }}- | |
| - name: Cache Android libs | |
| uses: actions/cache@v4 | |
| with: | |
| path: packages/mobile/android/libs | |
| key: ffmpeg-aar-${{ hashFiles('packages/mobile/android/build.gradle') }} | |
| restore-keys: | | |
| ffmpeg-aar- | |
| - name: Cache Android gems | |
| uses: actions/cache@v4 | |
| with: | |
| path: packages/mobile/android/vendor/bundle | |
| key: gems-android-${{ hashFiles('packages/mobile/android/Gemfile.lock') }} | |
| restore-keys: | | |
| gems-android- | |
| - name: Install dependencies | |
| env: | |
| CI: true | |
| SKIP_ANDROID_INSTALL: true | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| run: | | |
| if [[ -d node_modules ]]; then | |
| echo "Using cached node_modules, running postinstall..." | |
| npm run postinstall | |
| else | |
| echo "No cache found, running fresh install..." | |
| # Clear npm cache to avoid EEXIST conflicts | |
| npm cache clean --force || true | |
| # Try npm ci first, fallback to npm install if lock file is out of sync | |
| npm ci --prefer-offline || npm install --prefer-offline | |
| fi | |
| - name: Configure release-candidate OTA channel | |
| run: | | |
| awk '!/^OTA_CHANNEL=/' packages/mobile/.env.prod > packages/mobile/.env.prod.tmp | |
| mv packages/mobile/.env.prod.tmp packages/mobile/.env.prod | |
| echo "OTA_CHANNEL=rc" >> packages/mobile/.env.prod | |
| - name: Update bundler | |
| run: | | |
| sudo gem install bundler:2.5.16 | |
| sudo gem uninstall bundler -v 1.17.3 --force || true | |
| - name: Install pip and ninja | |
| run: | | |
| sudo apt update | |
| sudo apt install -y python3-pip ninja-build | |
| - name: Install Android gems | |
| run: | | |
| cd packages/mobile/android | |
| bundle install --path vendor/bundle | |
| - name: Update fastlane | |
| run: cd packages/mobile/android && sudo bundle update fastlane | |
| - name: Migrate support libraries for AndroidX | |
| run: cd packages/mobile && npm run jetifier | |
| - name: Install ffmpeg-aar | |
| run: | | |
| cd packages/mobile/android | |
| ./gradlew :app:downloadAar | |
| - name: Setup Play Store API | |
| run: | | |
| echo "${{ secrets.FASTLANE_PLAYSTORE_JSON }}" | base64 --decode > packages/mobile/android/app/api.json | |
| - name: Build dependencies | |
| timeout-minutes: 60 | |
| run: npx turbo run build --filter=@audius/mobile | |
| - name: Clean Gradle build cache before build | |
| run: | | |
| cd packages/mobile/android | |
| ./gradlew clean || true | |
| # Clean Gradle build cache to free space | |
| rm -rf app/build | |
| rm -rf .gradle/buildOutputCleanup | |
| - name: Check disk space before build | |
| run: | | |
| echo "Disk space before Android build:" | |
| df -h | |
| echo "Gradle cache size:" | |
| du -sh ~/.gradle/caches 2>/dev/null || echo "No Gradle cache found" | |
| echo "Android SDK size:" | |
| if [ -n "$ANDROID_HOME" ]; then | |
| du -sh "$ANDROID_HOME" 2>/dev/null || echo "ANDROID_HOME set but path not found" | |
| fi | |
| if [ -d ~/.android/sdk ]; then | |
| du -sh ~/.android/sdk 2>/dev/null || echo "Default SDK path not found" | |
| fi | |
| - 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: us-east-1 | |
| - name: Release Android (Release Candidate) | |
| timeout-minutes: 60 | |
| env: | |
| FASTLANE_PLAYSTORE_JSON: ${{ secrets.FASTLANE_PLAYSTORE_JSON }} | |
| run: | | |
| cd packages/mobile/android | |
| bundle exec fastlane releaseCandidate track:internal | |
| - name: Slack notification | |
| if: success() | |
| env: | |
| SLACK_WEBHOOK: ${{ secrets.SLACK_DAILY_DEPLOY_WEBHOOK }} | |
| run: | | |
| deploying_version=$(jq -r '.version' packages/mobile/package.json) | |
| job_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| json_content="{ \"blocks\": [{ \"type\": \"section\", \"text\": { \"type\": \"mrkdwn\", \"text\": \"Deployed releaseCandidate <${job_url}|v${deploying_version}> to mobile android\" } }]}" | |
| curl -f -X POST -H 'Content-type: application/json' --data "$json_content" $SLACK_WEBHOOK | |
| # iOS Production: Build and upload (requires approval) | |
| mobile-build-upload-production-ios-main: | |
| name: iOS Production Build & Upload | |
| runs-on: macos-15 | |
| needs: [mobile-init, mobile-version-check] | |
| if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && needs.mobile-version-check.outputs.version_changed == 'true' | |
| environment: | |
| name: mobile-production-ios | |
| url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.json | |
| - name: Create concatenated patch file | |
| id: patch-file | |
| run: | | |
| ls -d -- packages/*/patches/*.patch 2>/dev/null | xargs cat > combined-patch-file.txt || touch combined-patch-file.txt | |
| echo "patch_checksum=$(sha256sum combined-patch-file.txt | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache node modules | |
| id: cache-node-modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| node_modules | |
| packages/mobile/node_modules | |
| packages/common/node_modules | |
| packages/libs/node_modules | |
| packages/libs/dist | |
| packages/sdk/node_modules | |
| packages/sdk/dist | |
| packages/harmony/node_modules | |
| key: npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}-${{ steps.patch-file.outputs.patch_checksum }} | |
| restore-keys: | | |
| npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}- | |
| npm-cache-mobile-${{ runner.os }}-${{ hashFiles('package-lock.json') }}- | |
| - name: Copy production env | |
| run: | | |
| cd packages/mobile | |
| echo -e "\nAMPLITUDE_WRITE_KEY=${{ secrets.AMPLITUDE_WRITE_KEY_PROD }}" >> .env.prod | |
| - name: Install Python build dependencies | |
| run: | | |
| # Python 3.12+ doesn't include distutils, but setuptools provides it | |
| # Use --break-system-packages for CI (safe since runner is ephemeral) | |
| python3 -m pip install --break-system-packages --upgrade setuptools | |
| - name: Install dependencies | |
| env: | |
| CI: true | |
| SKIP_POD_INSTALL: true | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| run: | | |
| if [[ -d node_modules ]]; then | |
| echo "Using cached node_modules, running postinstall..." | |
| npm run postinstall | |
| # Ensure dotenv-linter binary is installed (install script downloads it) | |
| if [[ ! -f packages/dotenv-linter/bin/dotenv-linter ]]; then | |
| echo "dotenv-linter binary missing, running install script..." | |
| (cd packages/dotenv-linter && npm run install) | |
| fi | |
| else | |
| echo "No cache found, running fresh install..." | |
| # Clear npm cache to avoid EEXIST conflicts | |
| npm cache clean --force || true | |
| # Try npm ci first, fallback to npm install if lock file is out of sync | |
| npm ci --prefer-offline || npm install --prefer-offline | |
| fi | |
| - name: Cache iOS gems | |
| uses: actions/cache@v4 | |
| with: | |
| path: packages/mobile/ios/vendor/bundle | |
| key: gems-ios-${{ hashFiles('packages/mobile/ios/Gemfile.lock') }} | |
| restore-keys: | | |
| gems-ios- | |
| - name: Install gems | |
| run: | | |
| cd packages/mobile/ios | |
| sudo gem install bundler:2.5.16 | |
| bundle check || bundle install --path vendor/bundle | |
| - name: Install CocoaPods dependencies | |
| run: | | |
| cd packages/mobile/ios | |
| RCT_NEW_ARCH_ENABLED=0 bundle exec pod install | |
| - name: Install Sentry CLI | |
| run: | | |
| curl -sL https://sentry.io/get-cli/ | bash | |
| echo "$HOME/.sentry-cli/bin" >> $GITHUB_PATH | |
| - name: Build dependencies | |
| timeout-minutes: 60 | |
| run: npx turbo run build --filter=@audius/mobile | |
| - name: Create iOS bundle | |
| run: cd packages/mobile && npx turbo run bundle:ios | |
| - name: Update fastlane | |
| run: cd packages/mobile/ios && sudo bundle update fastlane | |
| - name: Copy production env to iOS | |
| run: cp packages/mobile/.env.prod packages/mobile/ios/ | |
| - name: Set up SSH for Fastlane Match | |
| run: | | |
| # Set up SSH agent | |
| eval "$(ssh-agent -s)" | |
| # Add GitHub to known_hosts | |
| mkdir -p ~/.ssh | |
| chmod 700 ~/.ssh | |
| echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' >> ~/.ssh/known_hosts | |
| chmod 644 ~/.ssh/known_hosts | |
| # Add SSH private key for accessing certs-and-profiles repository | |
| # Check if secret is set | |
| if [[ -z "${{ secrets.AUDIUS_INFRA_SSH_KEY }}" ]]; then | |
| echo "Error: AUDIUS_INFRA_SSH_KEY secret is not set" | |
| exit 1 | |
| fi | |
| # Write key and remove any carriage returns (works on both macOS and Linux) | |
| echo "${{ secrets.AUDIUS_INFRA_SSH_KEY }}" | tr -d '\r' > ~/.ssh/id_rsa | |
| chmod 600 ~/.ssh/id_rsa | |
| # Add key to agent (will provide detailed error if format is wrong) | |
| ssh-add ~/.ssh/id_rsa | |
| # Configure git to use SSH | |
| git config --global user.email "audius-infra@audius.co" | |
| git config --global user.name "audius-infra" | |
| - name: Build and upload iOS (Production) | |
| timeout-minutes: 60 | |
| env: | |
| FASTLANE_USER: ${{ secrets.FASTLANE_USER }} | |
| FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }} | |
| FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD }} | |
| FASTLANE_SESSION: ${{ secrets.FASTLANE_SESSION }} | |
| MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} | |
| APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY_ID }} | |
| APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} | |
| APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_ORG: ${{ secrets.SENTRY_ORG }} | |
| SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} | |
| run: | | |
| cd packages/mobile/ios | |
| bundle exec fastlane build_and_upload bundle_id:co.audius.audiusmusic | |
| # Android Production: Build and upload (requires approval) | |
| mobile-build-upload-production-android-main: | |
| name: Android Production Build & Upload | |
| runs-on: ubuntu-latest | |
| needs: [mobile-init, mobile-version-check] | |
| if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && needs.mobile-version-check.outputs.version_changed == 'true' | |
| environment: | |
| name: mobile-production-android | |
| url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: 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: Cache Gradle | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.gradle/caches | |
| ~/.gradle/wrapper | |
| key: gradle-${{ runner.os }}-${{ hashFiles('packages/mobile/android/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('packages/mobile/android/build.gradle') }} | |
| restore-keys: | | |
| gradle-${{ runner.os }}- | |
| - name: Cache Android SDK | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.android/sdk | |
| key: android-sdk-${{ runner.os }}-${{ hashFiles('packages/mobile/android/build.gradle') }} | |
| restore-keys: | | |
| android-sdk-${{ runner.os }}- | |
| - name: Free disk space | |
| run: | | |
| echo "Disk space before cleanup:" | |
| df -h | |
| # Remove unnecessary packages and caches | |
| sudo rm -rf /usr/share/dotnet | |
| sudo rm -rf /opt/ghc | |
| sudo rm -rf /usr/local/share/boost | |
| sudo rm -rf "$AGENT_TOOLSDIRECTORY" | |
| sudo rm -rf /opt/hostedtoolcache/CodeQL | |
| sudo rm -rf /opt/microsoft | |
| # Clean apt cache | |
| sudo apt-get clean | |
| sudo rm -rf /var/lib/apt/lists/* | |
| # Clean npm cache | |
| npm cache clean --force || true | |
| # Clean pip cache | |
| pip cache purge || true | |
| echo "Disk space after cleanup:" | |
| df -h | |
| - name: Setup Java (after disk cleanup) | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '17' | |
| - name: Create concatenated patch file | |
| id: patch-file | |
| run: | | |
| ls -d -- packages/*/patches/*.patch 2>/dev/null | xargs cat > combined-patch-file.txt || touch combined-patch-file.txt | |
| echo "patch_checksum=$(sha256sum combined-patch-file.txt | cut -d' ' -f1)" >> $GITHUB_OUTPUT | |
| - name: Cache node modules | |
| id: cache-node-modules | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| node_modules | |
| packages/mobile/node_modules | |
| packages/common/node_modules | |
| packages/libs/node_modules | |
| packages/libs/dist | |
| packages/sdk/node_modules | |
| packages/sdk/dist | |
| packages/harmony/node_modules | |
| key: npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}-${{ steps.patch-file.outputs.patch_checksum }} | |
| restore-keys: | | |
| npm-cache-mobile-${{ runner.os }}-node-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}- | |
| npm-cache-mobile-${{ runner.os }}-${{ hashFiles('package-lock.json') }}- | |
| - name: Cache Android libs | |
| uses: actions/cache@v4 | |
| with: | |
| path: packages/mobile/android/libs | |
| key: ffmpeg-aar-${{ hashFiles('packages/mobile/android/build.gradle') }} | |
| restore-keys: | | |
| ffmpeg-aar- | |
| - name: Cache Android gems | |
| uses: actions/cache@v4 | |
| with: | |
| path: packages/mobile/android/vendor/bundle | |
| key: gems-android-${{ hashFiles('packages/mobile/android/Gemfile.lock') }} | |
| restore-keys: | | |
| gems-android- | |
| - name: Install dependencies | |
| env: | |
| CI: true | |
| SKIP_ANDROID_INSTALL: true | |
| NODE_OPTIONS: --max-old-space-size=8192 | |
| run: | | |
| if [[ -d node_modules ]]; then | |
| echo "Using cached node_modules, running postinstall..." | |
| npm run postinstall | |
| else | |
| echo "No cache found, running fresh install..." | |
| # Clear npm cache to avoid EEXIST conflicts | |
| npm cache clean --force || true | |
| # Try npm ci first, fallback to npm install if lock file is out of sync | |
| npm ci --prefer-offline || npm install --prefer-offline | |
| fi | |
| - name: Update bundler | |
| run: | | |
| sudo gem install bundler:2.5.16 | |
| sudo gem uninstall bundler -v 1.17.3 --force || true | |
| - name: Install pip and ninja | |
| run: | | |
| sudo apt update | |
| sudo apt install -y python3-pip ninja-build | |
| - name: Install Android gems | |
| run: | | |
| cd packages/mobile/android | |
| bundle install --path vendor/bundle | |
| - name: Update fastlane | |
| run: cd packages/mobile/android && sudo bundle update fastlane | |
| - name: Migrate support libraries for AndroidX | |
| run: cd packages/mobile && npm run jetifier | |
| - name: Install ffmpeg-aar | |
| run: | | |
| cd packages/mobile/android | |
| ./gradlew :app:downloadAar | |
| - name: Setup Play Store API | |
| run: | | |
| echo "${{ secrets.FASTLANE_PLAYSTORE_JSON }}" | base64 --decode > packages/mobile/android/app/api.json | |
| - name: Build dependencies | |
| timeout-minutes: 60 | |
| run: npx turbo run build --filter=@audius/mobile | |
| - name: Clean Gradle build cache before build | |
| run: | | |
| cd packages/mobile/android | |
| ./gradlew clean || true | |
| # Clean Gradle build cache to free space | |
| rm -rf app/build | |
| rm -rf .gradle/buildOutputCleanup | |
| - name: Check disk space before build | |
| run: | | |
| echo "Disk space before Android build:" | |
| df -h | |
| echo "Gradle cache size:" | |
| du -sh ~/.gradle/caches 2>/dev/null || echo "No Gradle cache found" | |
| echo "Android SDK size:" | |
| if [ -n "$ANDROID_HOME" ]; then | |
| du -sh "$ANDROID_HOME" 2>/dev/null || echo "ANDROID_HOME set but path not found" | |
| fi | |
| if [ -d ~/.android/sdk ]; then | |
| du -sh ~/.android/sdk 2>/dev/null || echo "Default SDK path not found" | |
| fi | |
| - 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: us-east-1 | |
| - name: Release Android (Production) | |
| timeout-minutes: 60 | |
| env: | |
| FASTLANE_PLAYSTORE_JSON: ${{ secrets.FASTLANE_PLAYSTORE_JSON }} | |
| run: | | |
| cd packages/mobile/android | |
| bundle exec fastlane prod track:alpha |