--- name: Build and Submit iOS on: workflow_dispatch: inputs: profile: type: choice description: Build profile to use options: - testflight - production jobs: build: if: github.repository == 'bluesky-social/social-app' name: Build and Submit iOS runs-on: macos-26-xlarge concurrency: group: ios-build cancel-in-progress: false steps: - name: Check for EXPO_TOKEN run: > if [ -z "${{ secrets.EXPO_TOKEN }}" ]; then echo "You must provide an EXPO_TOKEN secret linked to this project's Expo account in this repo's secrets. Learn more: https://docs.expo.dev/eas-update/github-actions" exit 1 fi - name: ⬇️ Checkout uses: actions/checkout@v5 with: fetch-depth: 5 - uses: pnpm/action-setup@v6 - name: 🔧 Setup Node uses: actions/setup-node@v6 with: node-version-file: package.json cache: pnpm - name: 🪛 Setup jq uses: dcarbone/install-jq-action@v2 - name: ⚙️ Install dependencies run: pnpm install --frozen-lockfile - name: 🔨 Setup Expo CLI uses: expo/expo-github-action@v9 with: eas-version: '19.0.5' packager: 'pnpm --allow-build=dtrace-provider' token: ${{ secrets.EXPO_TOKEN }} - uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: "26.4" - name: ☕️ Setup Cocoapods uses: maxim-lobanov/setup-cocoapods@v1 with: version: 1.16.2 - name: 💾 Cache Pods uses: actions/cache@v5 id: pods-cache with: path: ./ios/Pods # We'll use the pnpm-lock.yaml for our hash since we don't yet have a Podfile.lock. Pod versions will not # change unless the pnpm version changes as well. key: ${{ runner.os }}-pods-${{ hashFiles('pnpm-lock.yaml') }} - name: 🔤 Compile translations run: pnpm intl:build 2>&1 | tee i18n.log - name: Check for i18n compilation errors run: if grep -q "invalid syntax" "i18n.log"; then echo "\n\nFound compilation errors!\n\n" && exit 1; else echo "\n\nNo compilation errors!\n\n"; fi # EXPO_PUBLIC_ENV is handled in eas.json - name: ✏️ Write environment variables id: env run: | echo "${{ secrets.ENV_TOKEN }}" > .env echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> .env echo "EXPO_PUBLIC_RELEASE_VERSION=$(jq -r '.version' package.json)" >> $GITHUB_OUTPUT echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> .env echo "EXPO_PUBLIC_BUNDLE_IDENTIFIER=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT echo "EXPO_PUBLIC_BUNDLE_DATE=$(date -u +"%y%m%d%H")" >> .env echo "EXPO_PUBLIC_SENTRY_DSN=${{ secrets.SENTRY_DSN }}" >> .env echo "EXPO_PUBLIC_BITDRIFT_API_KEY=${{ secrets.BITDRIFT_API_KEY }}" >> .env echo "EXPO_PUBLIC_GCP_PROJECT_ID=${{ secrets.EXPO_PUBLIC_GCP_PROJECT_ID }}" >> .env echo "${{ secrets.GOOGLE_SERVICES_TOKEN }}" > google-services.json - name: 🏗️ EAS Build env: PROFILE: ${{ inputs.profile || 'testflight' }} run: > SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_RELEASE=${{ steps.env.outputs.EXPO_PUBLIC_RELEASE_VERSION }} SENTRY_DIST=${{ steps.env.outputs.EXPO_PUBLIC_BUNDLE_IDENTIFIER }} pnpm use-build-number-with-bump pnpm eas build -p ios --profile $PROFILE --local --output build.tar.gz --non-interactive - name: 📂 Extract build artifact run: | if [ -f "build.tar.gz" ]; then echo "Extracting build.tar.gz..." rm -rf ios-build mkdir -p ios-build tar -xzf build.tar.gz -C ios-build echo "Extraction completed successfully" echo "" echo "Top-level extracted files:" find ios-build -maxdepth 3 -print echo "" echo "Searching for IPA..." IPA_PATH="$(find ios-build -type f -name '*.ipa' -print -quit)" if [ -z "$IPA_PATH" ]; then echo "ERROR: No .ipa found anywhere under ios-build." echo "Archive contents:" tar -tzf build.tar.gz | sed -n '1,200p' exit 1 fi BUILD_DIR="$(dirname "$IPA_PATH")" echo "Found IPA at: $IPA_PATH" echo "Build dir: $BUILD_DIR" echo "" echo "Build dir contents:" ls -la "$BUILD_DIR" echo "BUILD_DIR=$BUILD_DIR" >> $GITHUB_ENV else echo "Archive file not found!" exit 1 fi - name: 🚀 Deploy run: pnpm eas submit -p ios --non-interactive --path "$BUILD_DIR/Bluesky.ipa" - name: 🪲 Upload dSYM to Sentry run: > SENTRY_ORG=blueskyweb SENTRY_PROJECT=app SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} pnpm sentry-cli debug-files upload "$BUILD_DIR/Bluesky.app.dSYM.zip" --include-sources - name: 📚 Get version from package.json id: get-build-info run: bash scripts/setGitHubOutput.sh - name: 🔔 Notify Slack of Production Build if: ${{ inputs.profile == 'production' }} uses: slackapi/slack-github-action@v3.0.3 with: webhook: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }} webhook-type: incoming-webhook payload-templated: true payload: | {"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}```"} - name: ⬇️ Restore Cache id: get-base-commit uses: actions/cache@v5 if: ${{ inputs.profile == 'testflight' }} with: path: most-recent-testflight-commit.txt key: most-recent-testflight-commit - name: ✏️ Write commit hash to cache env: GITHUB_SHA: ${{ github.sha }} if: ${{ inputs.profile == 'testflight' }} run: echo $GITHUB_SHA > most-recent-testflight-commit.txt