Skip to content

Add collectionId to PLAYBACK_PLAY analytics events (#14101) #404

Add collectionId to PLAYBACK_PLAY analytics events (#14101)

Add collectionId to PLAYBACK_PLAY analytics events (#14101) #404

Workflow file for this run

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